Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SecureRandom provider "Crypto" unavailable in Android N for deterministially generating a key

Users can purchase a "Pro" version of my app. When they do, I store and verify their purchase as follows.

  • Combine the user's UUID and another unique string.
  • The resulting string is then encrypted using a static seed. I do this using SecureRandom.getInstance("SHA1PRNG", "Crypto")- This is the problem!
  • The resulting encrypted string is then the "unlock code".
  • Therefore, I always know the expected unique unlock code value for the user.
  • When the user purchases "Pro", I store the "unlock code" in the database.
  • I check to see whether the user has "Pro" by seeing if the stored "unlock code" in the database matches the expected code based on their unique info.

So, not the best system, but everything is obfuscated enough for my humble app.

The problem is that SecureRandom.getInstance("SHA1PRNG", "Crypto") fails on N because "Crypto" is not supported. I have learned that relying on specific providers is bad practice and Crypto is not supported on N. Oops.

So I have a problem: I rely on the encryption of a value-seed pair to always have the same output. Android N does not support the encryption provider I use, so I don't know how to ensure that the encryption output will be the same on N as it is on other devices.

My questions:

  1. Is it possible to include "Crypto" in my APK so that it is always available?
  2. Can I otherwise ensure the same output when encrypting a value-seed pair on Android N?

My code:

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();
}
like image 964
NSouth Avatar asked Apr 23 '16 16:04

NSouth


3 Answers

I had a discussion with the Android Security team about this recently.

In Android N, SHA1PRNG was removed because we don't have a secure implementation of it. Specifically, calling .setSeed(long) before requesting output from the PRNG replaces all of the entropy in the SecureRandom instance.

This behavior has long been pointed to as a security failure (read: frequently causes subtle bugs in apps), so we chose not to replicate it when the SecureRandom provider was replaced.

If you need a PRNG, then just use new SecureRandom().

That said... SecureRandom() is not designed to be used as a key derivation function, as you've done in your example. Please don't do this! Instead, use an algorithm such as PBKDF2, available via SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").

We've been warning developers about this for a while. Please see these posts:

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

IF YOU REALLY NEED SHA1PRNG, EVEN AFTER ALL OF THAT... then the workaround is to copy the implementation out of the Android source, like @artjom-b mentioned in his answer.

But please, only do this if you need compatibility while migrating to PBKDF2 or similar.

like image 124
Trevor Johns Avatar answered Oct 18 '22 22:10

Trevor Johns


Using a PRNG such as SecureRandom for deriving data deterministically is generally a bad idea, because there is a history of breaking changes. It is always a good idea to use a specific implementation and include that with your app. It is possible to just copy the implementation code in your case.

SecureRandom.getInstance("SHA1PRNG", "Crypto"); looks up the "Crypto" provider which is org.apache.harmony.security.provider.crypto.CryptoProvider in Android 5.1.1. It redirects to org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl as the actual implementation. You can easily copy the code into your project under a different package and be sure to comply with the code license.

Then you can use it like this:

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

The second provider argument is not used according to the code, but you can create a dummy provider.


The proper way to generate a key from some seed is to use a key derivation function (KDF). If seed is password-like, then PBKDF2 is a good KDF when a lot of iterations are specified. If seed is key-like, then a KBKDF like HKDF is recommended.

like image 42
Artjom B. Avatar answered Oct 18 '22 21:10

Artjom B.


I added one class for CryptoProvider you can replace SecureRandom.getInstance("SHA1PRNG", "Crypto"); to SecureRandom.getInstance("SHA1PRNG", new CryptoProvider());

you can refer following link for solution, it working for me;

Security "Crypto" provider deprecated in Android N

like image 4
varotariya vajsi Avatar answered Oct 18 '22 23:10

varotariya vajsi