Android N对安全加密提供者已弃用

原文来自: Android Developers Blog —— Security “Crypto” provider deprecated in Android N

如果你的安卓应用程序使用的是SHA1PRNG算法从加密提供者那里取得的密钥,你必须开始使用一个真正的密钥导出函数,然后可能需要重新加密你的数据。

Java加密体系结构允许开发者创建像密码一样的一个类的实例,或者是生成伪随机数,像这样去调用它:

1
SomeClass.getInstance("SomeAlgorithm", "SomeProvider");

或者更简单通熟些:

1
SomeClass.getInstance("SomeAlgorithm");

对于这个实例,

1
2
Cipher.getInstance(“AES/CBC/PKCS5PADDING”); 
SecureRandom.getInstance(“SHA1PRNG”);

在Android上,我们不建议指定提供者。一般来说,任意调用Java加密扩展(JCE)API指定一个提供者,如果这个提供者是包括在应用程序里,或者说这个应用程序能够处理可能出现的ProviderNotException异常,也应该仅仅能完成这些。

不幸的是,很多应用程序都依赖于现在已经被移除了的”加密”提供者,对于一个密钥派生出来的反模式

这个提供者只能被提供一些为SecureRandom实例去实现的“SHA1PRNG”算法接口,问题是SHA1PRNG算法不是强加密,在使用基于PHP和Debian OpenSSL的伪随机序列和实验的统计距离的测试,8.1那部分,Yongge Want 和 Tony Nicol,指出该”随机”数列,以二进制形式考虑,朝0偏移返回,而且该偏移恶化的程度取决于种子。

结果是,在Android N中我们干脆弃用依赖于以“SHA1PRNG”算法去实现的接口和该加密提供者。我们在几年前使用加密安全存储凭据预先去覆盖使用SecureRandom派生出来的密钥产生的那些问题。然而,鉴于要继续使用它,我们将在这里重温一下。

该提供者有一个常见但是不正确的使用方法,就是通过使用密码作为种子来推导出加密密钥。SHA1PRNG接口有一个漏洞缺陷,如果setSeed()在获得输出之前被调用,那么就使他正确了。这个漏洞缺陷已经被用于密码作为种子派生加密密钥的关键,然后使用“随机”输出关键的密钥(其中,”随机”在这句话里的意思是“可以预测和若加密”),然后这种密钥可以用于加密和解密数据。

接下来,我们解释如何正确的导出密钥,以及如何解密已经进行不安全密钥加密的数据。这儿也有一个完整的例子,包括了一个帮助类去使用已经弃用SHA1PRNG功能,解密数据的唯一目的将不可用。

密钥可以通过以下方式得到:

如果你是通过从磁盘上读取一个AES的密钥,只是存储实际的密钥,你可以从通过使用执行字节AES生成SecretKey密钥:

1
SecretKey key = new SecretKeySpec(keyBytes, "AES");

如果您使用密码来获得一个密钥,参照Nikolay Elenkovd的这篇优秀教程,一个好的经验法制是腌制的大小应该和密钥输出的大小一致,它看上去就像是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* User types in their password: */ 
String password = "password";

/* Store these things on disk used to derive key later: */
int iterationCount = 1000;
int saltLength = 32; // bytes; should be the same size
as the output (256 / 8 = 32)
int keyLength = 256; // 256-bits for AES-256, 128-bits for AES-128, etc
byte[] salt; // Should be of saltLength

/* When first creating the key, obtain a salt with this: */
SecureRandom random = new SecureRandom();
byte[] salt = new byte[saltLength];
random.nextBytes(salt);

/* Use this to derive the key from the password: */
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
iterationCount, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
SecretKey key = new SecretKeySpec(keyBytes, "AES");

就是这样,你也不需要做其他的任何事情。

为了使过渡数据更容易,我们覆盖开发者的情况,有一个不安全的密钥加密的数据,它是每次从一个密码那里派生出来的。你可以在示例应用程序中使用帮助类InsecureSHA1PRNGKeyDerivator去派生出密钥:

1
2
3
4
5
6
7
8
private static SecretKey deriveKeyInsecurely(String password, int
keySizeInBytes) {
byte[] passwordBytes = password.getBytes(StandardCharsets.US_ASCII);
return new SecretKeySpec(
InsecureSHA1PRNGKeyDerivator.deriveInsecureKey(
passwordBytes, keySizeInBytes),
"AES");
}

然后,你可以使用安全的派生出的密钥按照上面的所述的重新加密数据,并从此过上幸福的生活。

注意1:作为一个临时的措施去维持应用重新工作,我们决定了在目标SDK版本为Mashmallow(API23)及其低版本还是为应用程序创建示例,请不要在Android SDK中存在依赖加密提供者,我们的计划是在未来会将其完全删除。
注意2:因为该系统很多部分都承担了SHA1PRNG算法的存在,当一个SHA1PRNG实例请求和提供者没有指定,我们将返回一个OpenSSLRandom的实例,这是从OpenSSL的衍生出随机数的一个强有力的来源。

以上是对Anroid 官网博客文章的翻译,如果不足,欢迎指正,谢谢。

0%