title: SecureRandom provider "Crypto" unavailable in Android N for deterministially generating a key
body: <p>Users can purchase a "Pro" version of my app. When they do, I store and verify their purchase as follows.</p>
  
  <ul>
  <li>Combine the user's UUID and another unique string.</li>
  <li>The resulting string is then encrypted using a static seed. <strong>I do this using <code>SecureRandom.getInstance("SHA1PRNG", "Crypto")</code>- This is the problem!</strong></li>
  <li>The resulting encrypted string is then the "unlock code".</li>
  <li>Therefore, I always know the <em>expected</em> unique unlock code value for the user. </li>
  <li>When the user purchases "Pro", I store the "unlock code" in the database.</li>
  <li>I check to see whether the user has "Pro" by seeing if the stored "unlock code" in the database matches the <em>expected</em> code based on their unique info.</li>
  </ul>
  
  <p>So, not the best system, but everything is obfuscated enough for my humble app. </p>
  
  <p>The problem is that <code>SecureRandom.getInstance("SHA1PRNG", "Crypto")</code> fails on N because "Crypto" is not supported. <a href="https://code.google.com/p/android/issues/detail?id=203584">I have learned that relying on specific providers is bad practice and Crypto is not supported on N</a>. Oops. </p>
  
  <p>So I have a problem: <strong>I rely on the encryption of a value-seed pair to always have the same output.</strong> 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.  </p>
  
  <p><strong>My questions:</strong></p>
  
  <ol>
  <li><strong>Is it possible to include "Crypto" in my APK so that it is always available?</strong></li>
  <li>Can I otherwise ensure the same output when encrypting a value-seed pair on Android N?</li>
  </ol>
  
  <p>My code:</p>
  
  <pre><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 &lt; buf.length; i++) {
          appendHex(result, buf[i]);
      }
      return result.toString();
  }
  </code></pre>