8

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();
}
Artjom B.
  • 61,146
  • 24
  • 125
  • 222
NSouth
  • 5,067
  • 7
  • 48
  • 83
  • @ArtjomB., Thanks. I'm having a hard time figuring out how to "include" the Crypto provider with my app. It looks like the providers are supplied by the system, so maybe my app would have to install the provider? I don't know how that would work. As for a dedicated library, could you give an example? I'm not familiar with what you mean. – NSouth Apr 23 '16 at 18:16
  • This is the second time they've messed with the Crypto library (I believe the first time was one of the JellyBean releases), outright breaking apps and causing a lot of headaches for developers and users. As such I would use an encryption library that doesn't rely on any of the Crypto libraries built into Android. It's unfortunate but necessary to avoid these ridiculous complications every few years. – Pete Aug 03 '16 at 17:39

3 Answers3

6

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:

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.

Community
  • 1
  • 1
Trevor Johns
  • 15,682
  • 3
  • 55
  • 54
  • Hi Trevor. First of all, glad you're sounding in on not using `SecureRandom`. I've been fighting the `getRawKey` for quite a while here. I'm trying to recreate the functionality for a similar question. Can you confirm that the Harmony based source hasn't changed between versions (I'm talking about the Harmony based *deterministic* version of course, with `setSeed` used). I did take a look at the source and I was pretty horrified, I must admit. Then again, I blame SUN for not specifying the algorithm in any way. – Maarten Bodewes May 21 '16 at 11:17
5

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.

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
  • You can switch in the source to different tags for other Android versions if this is not the compatibility level you're looking for. – Artjom B. Apr 24 '16 at 13:55
  • I am little bit confused here. Are they not going to remove "javax.crypto.*" ? Crypto means java / org / apache / harmony / security / provider / crypto / ? – N Kaushik Jun 24 '16 at 10:45
  • 1
    @NandanKaushikDutta I'm not sure what you mean. I would be very surprised if the "javax.crypto" package would be removed, because then there would be no crypto compatibility between Java by Google and Java by Oracle (and others). It seems that only the "Crypto" provider will be removed along with the specific "SHA1PRNG" algorithm. – Artjom B. Jun 24 '16 at 11:16
  • Yes. I have tried on Android N Emulator, only "Crypto" provider is removed. – N Kaushik Jun 24 '16 at 11:19
4

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

Community
  • 1
  • 1
varotariya vajsi
  • 3,965
  • 37
  • 39