SecureNandom提供程序"Crypto"在Android N中不可用,用于确定性地生成密钥

NSo*_*uth 8 random android cryptography secure-random android-7.0-nougat

用户可以购买我的应用程序的"专业版".当他们这样做时,我按如下方式存储和验证他们的购买.

  • 合并用户的UUID和另一个唯一字符串.
  • 然后使用静态种子加密生成的字符串.我这样做SecureRandom.getInstance("SHA1PRNG", "Crypto")- 这就是问题!
  • 然后,生成的加密字符串是"解锁代码".
  • 因此,我总是知道用户期望的唯一解锁代码值.
  • 当用户购买"Pro"时,我将"解锁代码"存储在数据库中.
  • 我通过查看数据库中存储的"解锁代码"是否与基于其唯一信息的预期代码匹配来检查用户是否具有"Pro" .

所以,不是最好的系统,但是对于我的简陋应用程序来说,所有内容都是模糊不清的.

问题是SecureRandom.getInstance("SHA1PRNG", "Crypto")在N 上失败,因为不支持"加密".我了解到依赖于特定的提供程序是不好的做法,并且N不支持加密.哎呀.

所以我遇到了一个问题:我依赖于值种子对的加密来始终具有相同的输出.Android N不支持我使用的加密提供程序,因此我不知道如何确保N上的加密输出与其他设备上的加密输出相同.

我的问题:

  1. 是否可以在我的APK中加入"加密"以便它始终可用?
  2. 加密Android N上的值种子对时,我可以确保相同的输出吗?

我的代码:

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();
}
Run Code Online (Sandbox Code Playgroud)

Tre*_*hns 6

我最近与Android安全团队就此进行了讨论.

在Android N中,SHA1PRNG已被删除,因为我们没有安全的实现.具体来说,.setSeed(long)在请求PRNG输出之前调用会替换SecureRandom实例中的所有熵.

长期以来,这种行为一直被指向安全性失败(读取:经常导致应用程序中的细微错误),因此我们选择在更换SecureRandom提供程序时不复制它.

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

那就是说... SecureRandom()并不是设计用作密钥派生函数,就像你在你的例子中所做的那样.请不要这样做!相反,使用可通过的算法,如PBKDF2 SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").

我们一直在警告开发人员.请看这些帖子:

如果您真的需要SHA1PRNG,即使在所有这些之后...然后解决方法是将实现复制到Android源代码中,例如在他的答案中提到的@ artjom-b.

但请注意,如果您在迁移到PBKDF2或类似设备时需要兼容性,请执行此操作.


Art*_* B. 5

使用PRNG(例如SecureRandom)来确定性地导出数据通常是一个坏主意,因为存在更改中断的历史记录。使用特定的实现并将其包含在您的应用中总是一个好主意。您可以只复制实施代码。

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

然后,您可以像这样使用它:

sr = new SecureRandom(new your.pkg.SHA1PRNG_SecureRandomImpl(), null);
Run Code Online (Sandbox Code Playgroud)

根据代码,不使用第二个provider参数,但是您可以创建一个虚拟provider。


从某些种子生成密钥的正确方法是使用密钥派生函数(KDF)。如果seed是类似密码的,那么当指定许多迭代时,PBKDF2是一个很好的KDF。如果seed是类似密钥的,则建议使用类似HKDF的KBKDF。