2013年比特币开发商在一篇博客中透露,由于Android系统存在一处关键漏洞,该平台上的比特币电子钱包很容易失窃。比特币开发商称,该漏洞影响到Android平台上的每一个比特币电子钱包应用程序,包括流行的比特币钱包(Bitcoin Wallet)、blockchain.info钱包(blockchain.info wallet)、BitcoinSpinner钱包(BitcoinSpinner Wallet)和Mycelium钱包(Mycelium Wallet)等。
该漏洞存在于Android系统随机生成数字串安全密钥的环节中。该漏洞的生成原因是对SecureRandom类的不正确使用方式导致的。 翻看Android的官方文档会发现。对于SecureRandom类的构造函数SecureRandom(byte[] seed)和SecureRandom#setSeed方法有一段安全性提醒:
“Seeds this SecureRandom instance with the specified Seeding SecureRandom may be insecure”
遗憾的是Android官网并未对此做过多的解释。setSeed方法为什么会引起安全风险?应该怎样使用SecureRandom类?
SecureRandom secureRandom = new SecureRandom(); byte[] b = new byte[] { (byte) 1 }; secureRandom.setSeed(b); // Prior to Android 4.2, the next line would always return the same number! System.out.println(secureRandom.nextInt());
在Android4.2 之前使用以上代码, Android会用种子byte b[]代替了系统默认的随机数生成源。导致nextInt()方法总是范围同一个随机数。
4.2以上的SecureRadom类为什么没有这个问题呢?因为经过比特币钱包漏洞之后Google修改了SecureRandom的内部实现,用基于OpenSSL的算法替代了老版的Bouncy Castle。用户调用setSeed时会将用户设置的seed添加到随机源(/dev/urandom)中而不是简单的替换。2. 在调用setSeed方法前先调用任意nextXXX方法。 原理如下:SecureRandom#SecureRandom(byte[] seed) SecureRandom#setSeed(long seed) SecureRandom#setSeed(byte[] seed)
我们可以通过SecureRandom#nextBytes(byte[] bytes)避免这个问题。具体做法是调用setSeed方法前先调用一次SecureRandom#nextBytes(byte[] bytes)方法。为什么这样就可以避免默认随机源被替代呢? 我们可以从源码中找到答案。(本文所引用代码全部基于Android API 16)
在SecureRandom初始化时, 会生成一个SecureRandomSpi对象。SecureRandom的核心方法都由SecureRandomSpi对象代理。 下面是SecureRandomSpi类的子类SHA1PRNG_SecureRandomImpl的初始化源码:
在初始化时会将内部状态机设置为state = UNDEFINED。如果使用者接着调用 SecureRandom#nextBytes方法,
SecureRandom会调用SecureRandomSpi#engineNextBytes方法
如果state的状态为UNDEFINED,那么nextBytes会使用默认的随机源。并将state设置为NEXT_BYTES之后如果调用setSeed方法,最终会调用到SecureRandomSpi#engineSetSeed方法.源码如下:
当state == NEXT_BYTES时会恢复原有的hash再更新seed,因为nextBytes时已经设置了系统的seed所以setSeed中传入的seed将添加到系统seed的尾部。 这样生成随机数时也会在系统的随机源中找seed,从而保证其随机性。
1. 对资深的攻击者而言这种方式的加密太过简单。他们可以轻易的看懂这里在干什么,并构造有效的攻击代码。
2. 整个加密的过程依赖SecureRandom的实现细节,这种依赖使得程序健壮性和可扩展性都非常脆弱。例如在Android API 17以后SecureRandom的默认实现方式从Cipher.RSA 换到了 OpenSSL。SecureRandom新的实现方式不能将自己的seed替换掉系统的seed。造成这段代码在API 17以上不能工作。APP必须强制升级才能继续运作。对某个类内部细节的依赖是软件设计中的大忌。
3. 从seed到生成key的过程非常的廉价,时间成本和资源要求的很低。如果攻击者采用暴力破解这种加密方式将显的很脆弱。
1. 利用随机盐加强秘钥的强度。随机盐可以有效的防止暴力破解。同一个password可以生成多个秘钥。攻击者不得不针对每个salt构造不同的秘钥字典。Android的JCE provider 现在能支持PBKDF2WithHmacSHA1。下面的代码将展示如何通过PBK算法将password生成为一个秘钥。
2. 通过迭代方式增加秘钥生成的时间成本。使得攻击者破解秘钥的时间大大增加。
参考文章:
http://android-developers.blogspot.co.uk/2013/02/using-cryptography-to-store-credentials.html