近期我负责的项目在客户验收途中使用各种安全扫描工具对其进行安全扫描发现了很多安全漏洞,接下来几篇文章我将介绍下我遇到的安全漏洞以及如何一步一步处理最终达到客户的验收要求。
登录用户名密码明文传输
思考
根据这个用户名密码的漏洞问题可以看出我们需要对用户名密码进行加密传输,才能变得更安全。
加密方式选择
在客户端加密之后为了方便在服务端中进行解密,我们选择可以实现公钥加密、私钥解密的对称加密算法,因此我们选择了使用RSA加密算法。
写代码前几个步骤
- 初始化生成公钥私钥对
- 将公钥私钥关系维护在redis中
- 登录时给前端返回公钥 publicKey
- 客户端使用公钥与密码、用户名等字符串进行RSA加密(使用JSEncrypt)
- 服务端拿到公钥、已加密字符串,通过公钥拿到私钥进行解密
- 服务端解密之后常规验证逻辑
1. 初始化RSA公钥私钥对
使用RSA算法生成RSA密钥对
// number 表示生成密钥对的个数
public static Map<String, String> initKeyMap(int number) throws Exception {
Map<String, String> keyMap = new HashMap<String, String>();
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(1024);
for (int i = 0; i < number; i++) {
KeyPair keyPair = keyPairGen.generateKeyPair();
// 公钥
String publicKey = encryptBASE64(keyPair.getPublic().getEncoded());
// 私钥
String privateKey = encryptBASE64(keyPair.getPrivate().getEncoded());
keyMap.put(publicKey, privateKey);
}
return keyMap;
}
2. 将生成的密钥关系维护在redis中
生成公钥私钥对时,进行遍历维护公钥和私钥的关系,并且将公钥单独维护、用于后期给客户端返回指定的公钥
List<String> keyList = new ArrayList<String>();
for (String key : rsaKey.keySet()) {
keyList.add(key);
// 存储公钥和私钥的对应关系
redisService.getRedisTemplate().opsForHash()
.put(RedisKeyEnums.XX.toString(), key, rsaKey.get(key));
}
3. 登录时给前端返回公钥 publicKey
登录时给前段返回公钥key,每次进入登录页面返回
String publicKey = (String) redisService.getRedisTemplate().opsForSet()
.randomMember(RedisKeyEnums.XX.toString());
4. 客户端使用公钥与密码、用户名等字符串进行RSA加密(使用JSEncrypt)
客户端拿到公钥之后将于用户输入的用户名和密码进行RSA加密
var encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey); // 设置公钥
var encodePassword = encrypt.encrypt(password); // 加密 password
5. 服务端拿到公钥、已加密字符串,通过公钥拿到私钥进行解密
服务端通过获得的公钥获取到私钥并且使用私钥对加密字符串进行解密
public static String decryptByPrivateKey(String data, String key) throws Exception {
// 对密钥解密
byte[] keyBytes = decryptBASE64(key);
// 通过PKCS#8编码的Key指令获得私钥对象
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
// 对数据解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, decryptBASE64(data),
privateKey.getModulus().bitLength()), CHARSET);
}