title: Android: How do I encrypt with fingerprint protected secret key right after generating it?
body: <p>I'm trying to encrypt a PIN using my fingerprint. My plan is to have a setting in my Settings activity to activate the fingerprint protection.</p>
  
  <p>Once the user turns the setting on, he/she will be asked to enter their PIN which will then be encrypted with a secret key linked to their fingerprint.</p>
  
  <p>As per <a href="https://github.com/googlesamples/android-FingerprintDialog" rel="nofollow noreferrer">Google's android-FingerprintDialog sample</a>, I created a secret key linked to my fingerprint and am trying to use it right away after generating it to encrypt my PIN but I'm getting <code>android.security.KeyStoreException: Key user not authenticated</code> when calling <code>cipher.doFinal()</code>.</p>
  
  <p>It looks like I'll have to not only ask the user to enter the PIN but also ask them to authenticate once with their fingerprint to encrypt the PIN which impacts the user experience a bit.</p>
  
  <p>Is there any way I can encrypt the PIN with the secret key right after generating it without requiring users to authenticate the first time around?</p>
  
  <p>Please see my code below. Thanks.</p>
  
  <pre><code>    public void createKey(String keyName, boolean invalidatedByBiometricEnrollment) {
          // The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint
          // for your flow. Use of keys is necessary if you need to know if the set of
          // enrolled fingerprints has changed.
          try {
              mKeyStore.load(null);
              // Set the alias of the entry in Android KeyStore where the key will appear
              // and the constrains (purposes) in the constructor of the Builder
  
              KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyName,
                      KeyProperties.PURPOSE_ENCRYPT |
                              KeyProperties.PURPOSE_DECRYPT)
                      .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                      // Require the user to authenticate with a fingerprint to authorize every use
                      // of the key
                      .setUserAuthenticationRequired(true)
                      .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
  
              // This is a workaround to avoid crashes on devices whose API level is &lt; 24
              // because KeyGenParameterSpec.Builder#setInvalidatedByBiometricEnrollment is only
              // visible on API level +24.
              // Ideally there should be a compat library for KeyGenParameterSpec.Builder but
              // which isn't available yet.
              if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.N) {
                  builder.setInvalidatedByBiometricEnrollment(invalidatedByBiometricEnrollment);
              }
              mKeyGenerator.init(builder.build());
              SecretKey secretKey = mKeyGenerator.generateKey();
  
              if (initEncryptionCipher(mDefaultEncryptionCipher, secretKey))
                  tryEncrypt(mDefaultEncryptionCipher, SECRET_MESSAGE);
          } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
                  | CertificateException | IOException e) {
              throw new RuntimeException(e);
          }
      }
  
      /**
       * Initialize the {@link Cipher} instance with the created key in the
       * {@link #createKey(String, boolean)} method.
       *
       * @param secretKey the key name to init the cipher
       * @return {@code true} if initialization is successful, {@code false} if the lock screen has
       * been disabled or reset after the key was generated, or if a fingerprint got enrolled after
       * the key was generated.
       */
      private boolean initEncryptionCipher(Cipher cipher, SecretKey secretKey) {
          try {
              mKeyStore.load(null);
              cipher.init(Cipher.ENCRYPT_MODE, secretKey);
              return true;
          } catch (KeyPermanentlyInvalidatedException e) {
              Log.e("Encryption Cipher", Log.getStackTraceString(e));
              return false;
          } catch (CertificateException | IOException
                  | NoSuchAlgorithmException | InvalidKeyException e) {
              throw new RuntimeException("Failed to init Cipher", e);
          }
      }
  
  
      /**
       * Tries to encrypt some data with the generated key in {@link #createKey} which is
       * only works if the user has just authenticated via fingerprint.
       */
      private void tryEncrypt(Cipher cipher, String secret) {
          try {
              byte[] encrypted = cipher.doFinal(secret.getBytes());
              SECRET_MESSAGE = new String(Base64.encode(encrypted, Base64.DEFAULT));
          } catch (BadPaddingException | IllegalBlockSizeException e) {
              Toast.makeText(this, "Failed to encrypt the data with the generated key. "
                      + "Retry the purchase", Toast.LENGTH_LONG).show();
              Log.e(TAG, Log.getStackTraceString(e));
          }
      }
  </code></pre>