客户对项目进行安全检查的时候,忽然间提出了密码不能明文传输的要求。说实在的,在研发的过程中,还真的没有重视过这个问题,既然提出来了,那就得解决。既然要解决,那就不能随便忽悠,要严肃的解决。经过筛选,选择了相对完善,并且开发难度较低的方案:使用非对称加密算法,在浏览器端使用公钥对密码原文加密,并将加密后的密文传递到后来,后台收到密文后使用私钥进行解密得到密码原文,然后继续处理现有逻辑。
后端常见加解密的工具比较丰富,前端浏览器中使用的相对较少,百度了一番,很容易就发现了jsencrypt.js,并且使用非常简单:
1var encrypt=new JSEncrypt();
2encrypt.setPublicKey("RSA的公钥");
3encrypt.encrypt("密码原文");
4 1import javax.crypto.Cipher;
2import java.nio.charset.Charset;
3import java.security.*;
4import java.security.interfaces.RSAKey;
5import java.security.spec.InvalidKeySpecException;
6import java.security.spec.PKCS8EncodedKeySpec;
7import java.security.spec.X509EncodedKeySpec;
8import java.util.ArrayList;
9import java.util.Base64;
10import java.util.List;
11import java.util.Objects;
12
13public class RSAHelper {
14 protected static final String DEFAULT_ALGORITHM = "RSA";
15 private final KeyFactory keyFactory;
16
17 public RSAHelper() {
18 this.keyFactory = generateKeyFactory(DEFAULT_ALGORITHM);
19 }
20
21 public RSAHelper(String algorithm) {
22 this.keyFactory = generateKeyFactory(Objects.requireNonNull(algorithm, "密码算法不能为null"));
23 }
24
25 public RSAHelper(KeyFactory keyFactory) {
26 this.keyFactory = Objects.requireNonNull(keyFactory, "密钥工厂不能为null");
27 }
28
29 public PublicKey loadPublicKey(String key, Charset charset) {
30 return loadPublicKey(key.getBytes(charset));
31 }
32
33 public PublicKey loadPublicKey(byte[] key) {
34 byte[] keyBytes = Base64.getDecoder().decode(key);
35 X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
36 try {
37 return keyFactory.generatePublic(x509EncodedKeySpec);
38 } catch (InvalidKeySpecException ex) {
39 throw new SecurityException("公钥配置错误", ex);
40 }
41 }
42
43 public PrivateKey loadPrivateKey(String key, Charset charset) {
44 return loadPrivateKey(key.getBytes(charset));
45 }
46
47 public PrivateKey loadPrivateKey(byte[] key) {
48 byte[] keyBytes = Base64.getDecoder().decode(key);
49 PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
50 try {
51 return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
52 } catch (InvalidKeySpecException ex) {
53 throw new SecurityException("私钥配置错误", ex);
54 }
55 }
56
57 protected byte[] doRSA(int mode, Key key, byte[] data) {
58 try {
59 Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
60 cipher.init(mode, key);
61
62 RSAKey rsaKey = (RSAKey) key;
63 int modulusByteLength = rsaKey.getModulus().bitLength() / 8;
64 List<byte[]> byteList = new ArrayList<>();
65 for (byte[] part : split(data, modulusByteLength)) {
66 byte[] finalPart = cipher.doFinal(part);
67 byteList.add(finalPart);
68 }
69 return concat(byteList);
70 } catch (GeneralSecurityException ex) {
71 throw new SecurityException("出错啦", ex);
72 }
73 }
74
75 private byte[] concat(List<byte[]> bytes) {
76 int totalLength = bytes.stream().map(arr -> arr.length).reduce((l1, l2) -> l1 + l2).get();
77 byte[] result = new byte[totalLength];
78 int coyiedLength = 0;
79 for (byte[] part : bytes) {
80 System.arraycopy(part, 0, result, coyiedLength, part.length);
81 coyiedLength = coyiedLength + part.length;
82 }
83 return result;
84 }
85
86 private List<byte[]> split(byte[] bytes, int groupSize) {
87 int lastGroupLength = bytes.length % groupSize;
88 int groupCount = bytes.length / groupSize + (lastGroupLength != 0 ? 1 : 0);
89 List<byte[]> result = new ArrayList<>();
90
91 for (int ii = 0; ii < groupCount; ii++) {
92 int copyLength = ((ii == groupCount - 1) && lastGroupLength != 0) ? lastGroupLength : groupSize;
93 byte[] array = new byte[copyLength];
94 System.arraycopy(bytes, ii * groupSize, array, 0, copyLength);
95 result.add(ii, array);
96 }
97 return result;
98 }
99
100 private KeyFactory generateKeyFactory(String algorithm) {
101 if (algorithm == null) {
102 throw new SecurityException("加密算法不能为空");
103 }
104 try {
105 return KeyFactory.getInstance(algorithm);
106 } catch (NoSuchAlgorithmException ex) {
107 throw new SecurityException(String.format("加密算法不存在:", DEFAULT_ALGORITHM), ex);
108 }
109 }
110
111 public String getAlgorithm() {
112 return keyFactory.getAlgorithm();
113 }
114}最后介绍RSA算法步骤:
- 随意选择两个大的质数p和q,p不等于q,计算N=pq。
- 根据欧拉函数,不大于N且与N互质的整数個数為(p-1)(q-1)。
- 选择一个整数e与(p-1)(q-1)互质,并且e小于(p-1)(q-1)。
- 用以下这个公式计算d:d× e ≡ 1 (mod (p-1)(q-1))。
- 将p和q的记录销毁。
- (N,e)是公钥,(N,d)是私钥。