Android N中无法使用SecureRandom提供程序“Crypto”来确定生成密钥

用户可以购买我的应用程序的“专业版”。 当他们这样做,我存储和validation他们的购买如下。

  • 结合用户的UUID和另一个唯一的string。
  • 结果string然后使用静态种子进行encryption我用SecureRandom.getInstance("SHA1PRNG", "Crypto")做这个 – 这是问题!
  • 得到的encryptionstring就是“解锁码”。
  • 因此,我总是知道用户期望的唯一解锁码值。
  • 当用户购买“Pro”时,我将“解锁码”存储在数据库中。
  • 通过查看数据库中存储的“解锁代码”是否与基于其唯一信息的预期代码相匹配来查看用户是否具有“专业”。

所以,不是最好的系统,但是对于我不起眼的应用程序来说,所有的东西都被混淆了。

问题是SecureRandom.getInstance("SHA1PRNG", "Crypto")在N上失败,因为不支持“Crypto”。 我了解到,依靠特定的提供商是不好的做法,Crypto不支持N。 哎呀。

所以我有一个问题: 我依靠价值种子对的encryption总是有相同的输出。 Android N不支持我使用的encryption提供程序,所以我不知道如何确保N上的encryption输出与其他设备上的encryption输出相同。

我的问题:

  1. 是否可以在我的APK中包含“encryption”,以便它始终可用?
  2. 我可以在Android N上encryption一个值 – 种子对时确保相同的输出吗?

我的代码:

 public static String encrypt(String seed, String cleartext) throws Exception { byte[] rawKey = getRawKey(seed.getBytes(), seed); byte[] result = encrypt(rawKey, cleartext.getBytes()); return toHex(result); // "unlock code" which must always be the same for the same seed and clearText accross android versions } private static byte[] getRawKey(byte[] seed, String seedStr) throws Exception { SecureRandom sr; sr = SecureRandom.getInstance("SHA1PRNG", "Crypto"); // what used to work KeyGenerator kgen = KeyGenerator.getInstance("AES"); sr.setSeed(seed); kgen.init(128, sr); SecretKey skey = kgen.generateKey(); byte[] raw = skey.getEncoded(); return raw; } private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception { SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); byte[] encrypted = cipher.doFinal(clear); return encrypted; } public static String toHex(byte[] buf) { if (buf == null) return ""; StringBuffer result = new StringBuffer(2 * buf.length); for (int i = 0; i < buf.length; i++) { appendHex(result, buf[i]); } return result.toString(); } 

我最近和Android安全团队讨论过这个问题。

在Android N中,SHA1PRNG被删除,因为我们没有一个安全的实现。 具体来说,在从PRNG请求输出之前调用.setSeed(long)将replaceSecureRandom实例中的所有熵。

这种行为一直被视为安全失败( 读取:经常会导致应用程序中的细微错误 ),所以我们select在SecureRandom提供程序被replace时不复制它。

如果您需要PRNG,那么只需使用new SecureRandom()

这就是说… SecureRandom()不是用来作为密钥派生函数的 ,就像你在例子中所做的那样。 请不要这样做! 而应使用可通过SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")获得的algorithm,例如PBKDF2。

一段时间以来,我们一直在警告开发者。 请看这些post:

  • Android开发人员博客:使用encryption技术安全地存储凭证
  • Android 4.2打破了我的encryption/解密代码,提供的解决scheme无法正常工作

如果你真的需要SHA1PRNG,甚至在所有这些之后……那么解决方法是将实现从Android源代码中复制出来,比如他的答案中提到的@ artjom-b。

但是请注意,只有在迁移到PBKDF2或类似的情况下需要兼容性时才这样做。

使用SecureRandom等PRNG来确定性地导出数据通常是一个糟糕的主意,因为有一个破坏性变化的历史。 使用特定的实现并将其包含在您的应用中总是一个好主意。 在你的情况下可以复制实现代码。

SecureRandom.getInstance("SHA1PRNG", "Crypto"); 查找Android 5.1.1中的org.apache.harmony.security.provider.crypto.CryptoProvider的“Crypto”提供程序。 它作为实际的实现redirect到org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl 。 您可以轻松地将代码复制到您的项目中,并确保遵守代码许可证。

那么你可以像这样使用它:

 sr = new SecureRandom(new your.pkg.SHA1PRNG_SecureRandomImpl(), null); 

第二个提供者参数不是根据代码使用的 ,但是您可以创build一个虚拟提供程序。


从种子生成密钥的正确方法是使用密钥导出函数(KDF)。 如果seed是类似密码的,那么当指定很多迭代时,PBKDF2是一个好的KDF。 如果seed是关键的,那么build议使用像HKDF这样的KBKDF。

我为CryptoProvider添加了一个类,您可以replaceSecureRandom.getInstance(“SHA1PRNG”,“Crypto”); SecureRandom.getInstance(“SHA1PRNG”,new CryptoProvider());

你可以参考下面的链接解决scheme,它为我工作;

Android N中弃用了安全性“encryption”提供程序