比播种SecureRandom更好的创建AES密钥的方法
我需要将加密数据从Java
客户端发送到C#
服务器。 现在我正在学习如何使用AES
(要求)加密数据。 继这个接受的答案用AES加密/解密我正在做以下事情:
byte[] keyStart = "qweroiwejrwoejlsifeoisrn".getBytes(); // Random character string byte[] toEncrypt = myMessageString.getBytes(); keyGen = KeyGenerator.getInstance("AES"); sr = SecureRandom.getInstance("SHA1PRNG"); sr.setSeed(keyStart); keyGen.init(128, sr); SecretKey secretKey = keyGen.generateKey(); byte[] secretKeyByte = secretKey.getEncoded(); SecretKeySpec skeySpec = new SecretKeySpec(secretKeyByte, "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); cipher.doFinal(toEncrypt);
由于该算法使用keyStart
使用SecureRandom
,我不确定这是否可以在C#
解码,甚至在没有SecureRandom
情况下在其他Java
程序中解码。
这个加密/解密是否只需要知道keyStart
的值,或者因为我使用的是SecureRandom
我还需要传递其他内容才能解密?
还有,有更好的方法可以做到这一点还是这个好吗?
不,你应该使用SecureRandom
从静态数据中获取密钥的整个想法是相当糟糕的:
-
SecureRandom
的主要function是生成随机值,它不应该用作密钥流的生成器; -
SecureRandom
,当用"SHA1PRNG"
实例化时,没有实现一个定义良好的算法,实际上已经知道算法会改变,甚至从一个Sun JDK到另一个; - Oracle提供的
"SHA1PRNG"
使用初始种子作为种子,其他人可能只是将种子添加到随机池中。
已知使用"SHA1PRNG"
作为密钥派生函数会在多个版本的Android上产生问题,并且可能在任何其他Java RE上失败。
那你该怎么做呢?
- 使用
new SecureRandom()
甚至更好的KeyGenerator
来生成真正的随机密钥,如果您需要一个全新的随机密钥,则不需要随机数生成器; - 直接向
SecretKeySpec
提供已知密钥的byte[]
,或者使用hex解码器从hex解码它(请注意,String
实例很难从内存中删除,所以只有在没有其他方法时才这样做); - 如果要从密码创建密钥,请使用PBKDF2 (尽管迭代次数高于链接中提供的次数);
- 如果要从一个密钥种子创建多个密钥,请使用真正的基于密钥的密钥派生机制,例如使用HKDF(见下文)。
如果种子是通过例如关键协议算法(例如Diffie-Hellman或ECDH)生成的,则选项4将是优选的。
请注意,对于选项3,PBKDF2,您最好只保留ASCII密码。 这是因为Oracle的PBKDF2实现不使用UTF-8编码。
至于选项4,我帮助将所有优秀的KBKDF添加到Bouncy Castle库中,因此如果您可以将Bouncy Castle添加到类路径和/或已安装的安全提供程序列表中,则无需自己实现KBKDF。 可能目前最好的KBKDF是HKDF。 如果您不能将Bouncy Castle添加到类路径中,那么您可能希望使用SHA-256输出的最左边字节而不是派生数据作为“穷人”KDF。