用户可以购买我的应用的“专业版”。当他们这样做时,我按如下方式存储和验证他们的购买。
SecureRandom.getInstance("SHA1PRNG", "Crypto")
执行此操作 - 这是问题所在! 所以,不是最好的系统,但是对于我的简陋应用程序来说,一切都被混淆了。
问题是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上的加密输出与其他设备上的加密输出相同。
我的问题:
我的代码:
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();
}
答案 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());
您可以参考以下链接获取解决方案,它适合我;