一、描述
之前有一篇文章中介绍过 AES 的双向加密的实现,此为链接,然后某一天就出异常了。
问题很蛋疼,Windows 系统上开发的项目,利用 AES 双向加密技术,在 Java 代码内对手机号进行加密解密操作。
一切正常,但是项目移到 Linux 系统上,解密就出如下异常:
javax.crypto.BadPaddingException: Given final block not properly padded at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..) at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..) at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..) at javax.crypto.Cipher.doFinal(DashoA13*..) at chb.test.crypto.AESUtil.crypt(AESUtil.java:386) at chb.test.crypto.AESUtil.AesDecrypt(AESUtil.java:254) at chb.test.crypto.AESUtil.main(AESUtil.java:40)
定位很精准,就是我的 AESUtil 这个工具类里面的如下代码有问题:
//1.构造密钥生成器,指定为AES算法,不区分大小写 KeyGenerator keygen= KeyGenerator.getInstance("AES"); //2.根据ecnodeRules规则初始化密钥生成器 //生成一个128位的随机源,根据传入的字节数组 keygen.init(128, new SecureRandom(encodeRules.getBytes()));
上面生成 key 的时候出问题了。
二、解决
那就将随机源手动指定就好,即手动生成一个key,
如下为代码改造,完美解决。
//1.构造密钥生成器,指定为AES算法,不区分大小写 KeyGenerator keygen= KeyGenerator.getInstance("AES"); //2.根据ecnodeRules规则初始化密钥生成器 //防止linux下 随机生成key SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); secureRandom.setSeed(encodeRules.getBytes()); keygen.init(128, secureRandom);
三、原理
其实原理也很蛋疼,网上解决办法大同小异,理由也都是千篇一律:
SecureRandom 实现完全随操作系统本身的內部状态,除非调用方在调用 getInstance 方法之后又调用了 setSeed 方法; 该实现在 windows 上每次生成的 key 都相同,但是在 solaris 或部分 linux 系统上则不同。
简单理解就是在 new SecureRandom() 的时候,两种操作系统上生成的随机数不相同罢了,why,追了下源码。
其实默认情况下,我们需要的 prng(random number algorithm),
在 Windows 环境下,默认是:SHA1PRNG,而 Linux 环境下,则默认是:NativePRNG。
再换句话说,Linux 默认使用 NativePRNG, 则它会去本地寻找熵(/ dev /urandom),产生的随机数(直接影响我们加密解密时需要的key)每次都是不一样的,故解密必然会失败了。
所以最直接的方式就是直接指定 prng,使用SHA1PRNG,即用下述方法来建立 SecureRandom,
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); secureRandom.setSeed(encodeRules.getBytes());
源码地址:MyGitHub
文章评论