Linux系统Java语言AES加解密失败

最近工作中遇到了AES加解密数据的需求,在单元测试用Java实现的AES加解密工具类的时发现一个有趣的问题,加解密代码在Windows系统上可以正常运行,但是在Linux系统上,加解密时突然报了javax.crypto.BadPaddingException: Given final block not properly padded。下面来尝试探讨和解决下这个问题。

##问题代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128, new SecureRandom(encryptKey.getBytes()));
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
// 创建密码器
Cipher cipher = Cipher.getInstance("AES");
byte[] byteContent = content.getBytes(CHARACTER_UTF8);
// 初始化
cipher.init(Cipher.ENCRYPT_MODE, key);
// 加密
byte[] result = cipher.doFinal(byteContent);
return result;

异常的实际原因是SecureRandom初始化方式问题。

##原因分析
SecureRandom实现完全随操作系统本身的內部状态,除非调用方在调用getInstance方法之后又调用了setSeed方法;该实现在windows上每次生成的key都相同,但是在 solaris或部分linux系统上则不同。

##解决方案
根据上述原因分析,我们需要对SecureRandom的初始化做一些调整,首先调用getInstance方法初始化之后,再调用setSeed方法设置随机种子,这样就可以忽略操作系统的区别。所以还是说,Java所谓的操作系统无关性还是有点坑啊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(encryptKey.getBytes());
kgen.init(128, random);
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
// 创建密码器
Cipher cipher = Cipher.getInstance("AES");
byte[] byteContent = content.getBytes(CHARACTER_UTF8);
// 初始化
cipher.init(Cipher.ENCRYPT_MODE, key);
// 加密
byte[] result = cipher.doFinal(byteContent);
return result;

##结论
虽然改进后的代码完美解决了Linux系统AES加解密的坑,但是我Google了很久也没搞清楚原因是什么,希望懂的同学可以帮忙解惑。然后也查到一些资料说,如果将随机种子设置的话,随机种子就固定了,可能会有一些安全问题,所以这种方式可能还不是最好的方案,如果对安全性要求较高,建议使用AES CBC的方案。

参考资料