SecureRandom 提供程序“Crypto”在 Android N 中无法确定性地生成密钥

Posted

技术标签:

【中文标题】SecureRandom 提供程序“Crypto”在 Android N 中无法确定性地生成密钥【英文标题】:SecureRandom provider "Crypto" unavailable in Android N for deterministially generating a key 【发布时间】:2016-08-17 05:20:08 【问题描述】:

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

结合用户的 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 上的加密输出与其他设备上的相同。

我的问题:

    是否可以在我的 APK 中包含“加密”以使其始终可用? 在 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();

【问题讨论】:

@ArtjomB.,谢谢。我很难弄清楚如何在我的应用程序中“包含”加密提供程序。看起来提供程序是由系统提供的,所以也许我的应用程序必须安装提供程序?我不知道那会怎样。至于专门的图书馆,你能举个例子吗?我不明白你的意思。 这是他们第二次弄乱 Crypto 库(我相信第一次是 JellyBean 版本之一),彻底破坏了应用程序,给开发人员和用户带来了很多麻烦。因此,我将使用一个不依赖于任何内置于 Android 中的 Crypto 库的加密库。每隔几年就避免这些可笑的并发症很不幸,但有必要。 【参考方案1】:

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

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

这种行为长期以来一直被认为是安全故障(阅读:经常导致应用程序中的细微错误),因此我们选择在替换 SecureRandom 提供程序时不复制它。

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

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

一段时间以来,我们一直在警告开发者。请参阅以下帖子:

Android Developers Blog: Using Cryptography to Store Credentials Safely Android 4.2 broke my encrypt/decrypt code and the provided solutions don't work

如果您真的需要 SHA1PRNG,即使在所有这些之后......那么解决方法是从 Android 源代码中复制实现,就像他的回答中提到的 @artjom-b 一样。 p>

但请仅当您在迁移到 PBKDF2 或类似软件时需要兼容性时才这样做。

【讨论】:

嗨特雷弗。首先,很高兴您不使用SecureRandom。我在这里与getRawKey 斗争了很长时间。我正在尝试为类似问题重新创建功能。您能否确认基于 Harmony 的源在不同版本之间没有改变(当然,我说的是基于 Harmony 的 deterministic 版本,使用了setSeed)。我确实看过源代码,我必须承认,我非常害怕。然后,我再次责怪 SUN 没有以任何方式指定算法。【参考方案2】:

使用诸如 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是key-like,那么推荐使用类似HKDF的KBKDF。

【讨论】:

如果这不是您正在寻找的兼容性级别,您可以在源代码中切换到其他 Android 版本的不同标签。 我在这里有点困惑。他们不会删除 "javax.crypto.*" 吗? Crypto表示java / org / apache / Harmony / security / provider / crypto / ? @NandanKaushikDutta 我不确定你的意思。如果删除“javax.crypto”包,我会感到非常惊讶,因为这样一来,Google 的 Java 和 Oracle(和其他)的 Java 之间就没有加密兼容性。似乎只有“加密”提供程序将与特定的“SHA1PRNG”算法一起被删除。 是的。我在 Android N Emulator 上试过,只删除了“加密”提供程序。【参考方案3】:

我为 CryptoProvider 添加了一个类,您可以将 SecureRandom.getInstance("SHA1PRNG", "Crypto"); 替换为 SecureRandom.getInstance("SHA1PRNG", new CryptoProvider());

您可以参考以下链接获取解决方案,它对我有用;

Security "Crypto" provider deprecated in Android N

【讨论】:

以上是关于SecureRandom 提供程序“Crypto”在 Android N 中无法确定性地生成密钥的主要内容,如果未能解决你的问题,请参考以下文章

AES加密解密

AES加解密过程

3DES双倍长加密

JAVA实现DES加密实现详解

JAVA实现DES加密实现详解

JAVA实现DES加密实现详解