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

时间:2016-04-23 16:01:48

标签: android random cryptography android-7.0-nougat secure-random

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

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

所以,不是最好的系统,但是对于我的简陋应用程序来说,一切都被混淆了。

问题是SecureRandom.getInstance("SHA1PRNG", "Crypto")在N上失败,因为不支持“加密”。 I have learned that relying on specific providers is bad practice and Crypto is not supported on N。哎呀。

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

我的问题:

  1. 是否可以在我的APK中加入“加密”,以便始终可用?
  2. 在加密Android N上的值 - 种子对时,我是否可以确保相同的输出?
  3. 我的代码:

    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();
    }
    

3 个答案:

答案 0 :(得分:5)

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

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

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

如果您需要PRNG,请使用new SecureRandom()

那说...... SecureRandom()并不是用作密钥派生函数,正如您在示例中所做的那样。请不要这样做!而是使用诸如PBKDF2之类的算法,可通过SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")获得。

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

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

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

答案 1 :(得分:4)

使用诸如SecureRandom之类的PRNG来确定性地获取数据通常是个坏主意,因为存在破坏变化的历史。使用特定的实现并将其包含在您的应用中总是一个好主意。您可以在您的案例中复制实施代码。

SecureRandom.getInstance("SHA1PRNG", "Crypto");查找org.apache.harmony.security.provider.crypto.CryptoProvider in Android 5.1.1的“加密”提供程序。它将重定向到org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl作为实际实现。您可以轻松地将代码复制到不同包装下的项目中,并确保符合代码许可证。

然后你可以像这样使用它:

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

根据code不使用第二个提供者参数,但您可以创建虚拟提供者。

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

答案 2 :(得分:3)

我为CryptoProvider添加了一个类,您可以将 SecureRandom.getInstance(&#34; SHA1PRNG&#34;,&#34;加密&#34;); 替换为 SecureRandom.getInstance( &#34; SHA1PRNG&#34;,新的CryptoProvider());

您可以参考以下链接获取解决方案,它适合我;

Security "Crypto" provider deprecated in Android N