比使用SecureRandom生成随机数更好的创建AES密钥的方法是什么?

8
我需要从一个Java客户端发送加密数据到一个C#服务器。现在我正在学习如何使用AES对数据进行加密(这是要求)。按照这个已接受的答案 android encryption/decryption with 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”,我不确定是否可以在没有“SecureRandom”的情况下在C#甚至另一个Java程序中解码。
只知道“keyStart”的值,这种加密/解密是否有效?或者因为我使用了“SecureRandom”,仍然需要传递其他内容才能解密?
此外,是否有更好的方法来完成这项任务,还是这个方法已经足够好了?
1个回答

20
不,使用SecureRandom从静态数据派生密钥的整个想法都相当糟糕:
1. SecureRandom的主要功能是生成随机值,不应将其用作密钥流的生成器; 2. 当使用“SHA1PRNG”实例化SecureRandom时,它不实现一个明确定义的算法,实际上这个算法甚至可能在一个Sun JDK到另一个Sun JDK之间发生变化; 3. Oracle提供的“SHA1PRNG”实现仅使用初始种子作为种子,而其他实现可能只是将种子添加到随机池中。
在几个Android版本上已知使用“SHA1PRNG”作为密钥派生函数会产生问题,并且可能在任何其他Java RE上失败。
那么你应该做什么呢?
  1. 使用 new SecureRandom() 或者更好的 KeyGenerator 来生成一个真正随机的密钥,如果你需要全新的随机密钥,不要给随机数生成器种子。
  2. 直接提供已知密钥的 byte[]SecretKeySpec,或者使用十六进制解码器从十六进制中解码(请注意,String 实例很难从内存中删除,因此只有在没有其他方法时才这样做)。
  3. 如果您想从密码创建密钥,请使用 PBKDF2(但请使用比链接中提供的更高的迭代次数)。
  4. 如果您想从一个密钥种子中创建多个密钥,则使用真正的基于密钥的密钥派生机制,例如使用 HKDF(见下文)。

如果种子是由例如 Diffie-Hellman 或 ECDH 等密钥协议算法生成的,则首选选项 4。


请注意,对于选项3 PBKDF2,您最好仅使用ASCII密码。这是因为Oracle的PBKDF2实现不使用UTF-8编码。
关于选项4,我已经帮助添加了所有好的KBKDF到Bouncy Castle libraries中,所以如果您可以将Bouncy Castle添加到类路径和/或安装的安全提供程序列表中,则无需实现KBKDF。目前最好的KBKDF可能是HKDF。如果您无法将Bouncy Castle添加到类路径中,则可以使用SHA-256输出的最左边的字节作为导出数据的“穷人版”KDF。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接