客户对项目进行安全检查的时候,忽然间提出了密码不能明文传输的要求。说实在的,在研发的过程中,还真的没有重视过这个问题,既然提出来了,那就得解决。既然要解决,那就不能随便忽悠,要严肃的解决。经过筛选,选择了相对完善,并且开发难度较低的方案:使用非对称加密算法,在浏览器端使用公钥对密码原文加密,并将加密后的密文传递到后来,后台收到密文后使用私钥进行解密得到密码原文,然后继续处理现有逻辑。

       后端常见加解密的工具比较丰富,前端浏览器中使用的相对较少,百度了一番,很容易就发现了jsencrypt.js,并且使用非常简单:

1var encrypt=new JSEncrypt();
2encrypt.setPublicKey("RSA的公钥");
3encrypt.encrypt("密码原文");
4
       前端工具选好后,算法也就定了,还好是常见的RSA,JDK天然携带。由于日常编程这方面的接触较少,相关API不是太熟悉,于是百度。几番折腾之后,发现实战的文章较多,描述系统且代码整洁的不多。只能放弃偷懒的想法,动手写了个工具类。

  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算法步骤:

  1. 随意选择两个大的质数p和q,p不等于q,计算N=pq。
  2. 根据欧拉函数,不大于N且与N互质的整数個数為(p-1)(q-1)。
  3. 选择一个整数e与(p-1)(q-1)互质,并且e小于(p-1)(q-1)。
  4. 用以下这个公式计算d:d× e ≡ 1 (mod (p-1)(q-1))。
  5. 将p和q的记录销毁。
  6. (N,e)是公钥,(N,d)是私钥。