本帖最后由 truthrudy 于 2016-7-15 18:21 编辑
如果您的 Android 应用使用来自 Crypto 提供程序的 SHA1PRNG 算法来派生密钥,您必须开始使用实际的密钥派生函数,还可能需要重新加密您的数据。
Java 加密体系结构允许开发者通过使用如下调用创建类的实例,例如密码或者伪随机数生成器: SomeClass.getInstance("SomeAlgorithm", "SomeProvider");
或者简单点: SomeClass.getInstance("SomeAlgorithm");
例如: Cipher.getInstance(“AES/CBC/PKCS5PADDING”);
SecureRandom.getInstance(“SHA1PRNG”);
在 Android 上,我们不建议指定提供程序。一般而言,只有在以下两种情形下才应调用指定提供程序的 Java Cryptography Extension (JCE) API:应用包含该提供程序;或者应用能够处理可能出现的 ProviderNotFoundException 异常。
不幸的是,许多应用依赖现已删除的“Crypto”提供程序来实现反模式的密钥派生。
此提供程序仅为 SecureRandom 实例提供了算法“SHA1PRNG”的一种实现方式。问题在于:SHA1PRNG 算法并非强加密。对于有兴趣深入了解有关详情的读者,可参阅 Yongge Want 和 Tony Nicol 合著的《根据统计距离测试伪随机序列并使用 PHP 和 Debian OpenSSL 进行试验》(On statistical distance based testing of pseudo random sequences and experiments with PHP and Debian OpenSSL),该文第 8.1 节中指出(二进制形式的)“随机”序列趋向于返回 0,而这种趋向会因随机种子而加剧。
因此,在 Android N 中,我们将 SHA1PRNG 算法和 Crypto 提供程序的实现一起淘汰了。几年前,我们在《使用加密技术安全地存储凭据》(Using Cryptography to Store Credentials Safely) 中讨论过与使用 SecureRandom 进行密钥派生有关的问题。然而,考虑到该技术仍在使用中,我们在此厘清误区。
关于此提供程序,一个常见但却错误的用法是,使用密码作为种子来派生加密密钥。SHA1PRNG 的实现有一个错误,其结果是,如果在获取输出之前调用 setSeed(),其返回结果是确定的。此错误的做法是:通过提供密码作为种子来派生密钥,然后使用“随机”输出密钥字节(此句中的“随机”是指“可预测的弱加密”)。随后即会使用这样的密钥来加密和解密数据。
下面,我们来解释如何正确地派生密钥以及如何解密使用不安全密钥加密的数据。本文还提供一个完整示例,包括一个帮助程序类,该类使用已弃用的 SHA1PRNG 功能,其唯一目的是解密必须解密、否则无法使用的数据。
可通过以下方式派生密钥: - 如果您要从磁盘读取 AES 密钥,只需存储实际的密钥,而不必如此大费周折。您可以从密钥字节获取供 AES 使用的 SecretKey,方法如下:SecretKey key = new SecretKeySpec(keyBytes, "AES");
- 如果您使用密码派生密钥,可遵循 Nikolay Elenkov 的优秀教程中建议的法则:盐值的大小应当与密钥输出大小相同。如下所示:
就这么简单。其他什么都不需要。
为了简化数据转换,我们考虑到开发者使用不安全的密钥对数据进行加密的情形,这种密钥每次都是通过密码派生而成。您可以使用示例应用中的帮助程序类 InsecureSHA1PRNGKeyDerivator 来派生密钥。
随后,您可以按照上面介绍的方法使用安全派生的密钥重新加密您的数据,然后即可高枕无忧了。
注 1:作为让应用正常运行的权宜之计,我们决定仍然针对版本 23、Marshmallow 或更低版本的 SDK 创建应用的实例。请不要使用 Android SDK 中现有的 Crypto 提供程序,我们计划在未来完全删除该提供程序。 注 2:由于系统的许多部分都假设存在 SHA1PRNG 算法,当在未指定提供程序的情况下请求 SHA1PRNG 的实例时,我们会返回 OpenSSLRandom 的实例,后者是从 OpenSSL 派生的强随机数源。
|