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:
- Is it possible to include "Crypto" in my APK so that it is always available?
- 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();
}