从往年Google I/O大年夜会上会商了“Android利用的安然和隐私”以来,良多人对如安在Android上利用暗码学手艺有些疑问。大年夜大都标题问题都是环绕在为了某一特定目标该利用哪个API上。是以,我们在这里就是来切磋一下如安在本地存储中呵护用户的敏感信息,好比说暗码和auth token(【译者】授权标识表记标帜,这里直接用auth token更天然)。
一种反模式(弊端的做法——译者注)
凡是我们都能意想到应当利用SecureRandom类来产生密钥,用来对本地的敏感信息进行加密,如许的例子很等闲找到,但实际上这类做法是欠妥的。
在这类编制中,不将密钥作为一个字符串直接存储在APK文件中,而是经由过程别的一个字符串来生成密钥–有点近似于经由过程用户口令生成加密密钥。这类需要的混合手段可使报复打击者不等闲破解加密信息,可是对一个有经验的报复打击者而言,这类策略很等闲被绕过,是以我们不保举这类编制。
事实上,Android现有的安然机制中已为这类数据供给了很好的呵护,敏感数据应当标识表记标帜上MODE_PRIVATE,然后存储在内部存储中,请寄望,千万不克不及存储在SD卡中,因为拜候节制没法强迫在外部存储上起感化。
连络设备加密办法,这类编制可以杜尽尽大年夜部门报复打击。
除此以外,像我们上面描述的那样利用SecureRandom类还存在别的一个标题问题。从Android4.2开端,SecureRandom的默许实现是OpenSSL,开辟者没法笼盖SecureRandom的内部状况信息,例以下面这段代码:
SecureRandom secureRandom = new SecureRandom();
byte[] b = new byte[]{(byte)1};
secureRandom.setSeed(b);
//在Android4.2上,下面这行代码老是返回统一个数字。
System.out.println(secureRandom.nextInt());
(【译者注】这段代码可以看到经由过程法度笼盖了random对象中的种子,造成每次生成的随机数序列都是一样的)
在之前的Android版本中,SecureRandom是基于Bouncy Castle实现的,它承诺像上面代码如许的把持,每个SecureRandom类的实例产生伪随机数时利用的种子是从/dev/urandom获得的。(【译者注】/dev/urandom是类Unix系统中按照当前计较机混乱状况,如内存利用,CPU占用率等信息计较出来的随机数,读者可以在Linux下尝尝cat /dev/urandom,它会不断地输出乱码,一般用“熵”这个专业术语形容计较机的混乱状况)。那些试牟利用产生随机数的开辟者凡是是经由过程替代现有的种子来产生随机数序列的(参考相干实现文档),假定种子固定,那么产生的随机数列就是可猜想的,这一点是不服安的。此刻经由过程OpenSSL实现,使得这类弊端的行动不再可能呈现。
不幸的是,那些依托老的SecureRandom类的利用法度会发现每次法度启动时产生的随机数都不一样了(事实上,这就应当是随机数产生器的期看行动)。想要经由过程这类编制对加密密钥混合已不成行了。
(【译者注】原作者的意思是有些利用对敏感信息做加密,加密的话需要密钥,可是密钥假定直接存起来感觉很不安心,因而经由过程产生随机数的编制来对密钥混合一下,那么每次法度启动时都要用不异的密钥往解密数据,因而经由过程一个固定的信息,好比一个暗码,或记住某个随机数字,凡是良多人用当前时候,然后经由过程这个固定的信息作为种子产生随机数,用这个随机数做密钥,相当于对这个固定信息做了一次混合把持。实际上这类编制仍是不服安,安然性就变成了若何包管这个种子的安然性,治标不治本。下面的部门作者的意思是应当直接将密钥打上MODE_PRIVATE的标识表记标帜存起来,经由过程系统的拜候节制机制包管密钥的安然性。)
准确的编制
一种加倍合理的解决方案很简单,就是当利用法度第一次启动时产生一个随机的AES算法的密钥:
public static SecretKey generateKey() throws NoSuchAlgorithmException
{
// 生成一个256位密钥
final int outputKeyLength = 256;
SecureRandom secureRandom = new SecureRandom();
// Do *not* seed secureRandom! Automatically seeded from system entropy.
//不要给secureRandom一个固定的种子!经由过程系统熵值产生随机的种子
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(outputKeyLength, secureRandom);
SecretKey key = keyGenerator.generateKey();
return key;
}
寄望这类编制的安然性依托于若何包管密钥的安然性,这可以依托于Android系统的内部存储的安然性。将密钥直接存放在文件中,标识表记标帜为MODE_PRIVATE存在内部存储器。(【译者注】我们良多人的Android手机都被Root过的,良多多少利用也会获得Root权限,Root权限用户是可以做任何工作的。。。这如何办呢?)
加倍安然的编制
假定你的利用还需要额外的加密把持,那么一个保举的编制是每次进进你的利用时需要输进一个口令或PIN码。然后将这个口令传给 PBKDF2(PBKDF2,基于口令的密钥导出函数版本2,是RSA安然公司提出的密钥导出算法,凡是常利用来按照口令获得密钥,经由过程一种叫做密钥拉伸的专业手艺),Android在SecretKeyFactory类中供给了一个叫做PBKDF2WithHmacSHA1的实现:
public static SecretKey generateKey(char[] passphraseOrPin, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException
{
//PBKDF2算法履行轮数,这个数字越大年夜,计较时候越长,你应当让这个数字
//足够大年夜,乃至于这个算法履行时候超越100毫秒以包管安然性
final int iterations = 1000;
// 产生一个256位的密钥
final int outputKeyLength = 256;
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength);
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
return secretKey;
}
加密盐应当是一个经由过程SecureRandom产生的随机字符串,和加密密文一路存放在内部存储器中。利用加密盐很首要,它可以有效避免字典报复打击。
(【译者注】看PBKDF2WithHmacSHA1这个名字也能够知道该算法是基于SHA1算法的,常常报复打击这类单向函数编制就是字典报复打击,预先计较好大年夜量的明文对应的密文,就像是明文对应密文的字典,然后再进行一一对比,假定明文字符串在加密前和一个随机字符串做个连接把持,那么那些预先计较的字典救没用了。)
查抄你的利用是不是是准确的利用SecureRandom
如本文和Jelly Bean的新安然特点所述,Android4.2的SecureRandom默许实现产生了改变,用它产生固定密钥已行不通了。
假定你也用了这类弊端编制的话,我们建议此刻就更新你的利用,避免当用户进级到Android4.2或以上版本后产生一些莫名其妙的弊端。