title: Android Fingerprint API Encryption and Decryption
body: <p>I am using the Android M Fingerprint API to allow users to login to the application. To do this I would need to store the username and password on the device. Currently I have the login working, as well as the Fingerprint API, but the username and password are both stored as plaintext. I would like to encrypt the password before I store it, and be able to retrieve it after the user authenticates with their fingerprint.</p>
  
  <p>I am having a great amount of difficulty getting this to work. I have been trying to apply what I can from the <a href="http://developer.android.com/samples/security.html" rel="noreferrer">Android Security samples</a>, but each example seems to only handle encryption or signing, and never decryption.</p>
  
  <p>What I have so far is that I have to obtain an instance of the <code>AndroidKeyStore</code>, a <code>KeyPairGenerator</code> and a <code>Cipher</code>, using asymmetric cryptography to allow the use of the Android <a href="http://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder.html#setUserAuthenticationRequired(boolean)" rel="noreferrer"><code>KeyGenParameterSpec.Builder().setUserAuthenticationRequired(true)</code></a>. The reason for asymmetric cryptography is because the <code>setUserAuthenticationRequired</code> method will block <em>any</em> use of the key if the user is not authenticated, but:</p>
  
  <blockquote>
    <p>This authorization applies only to secret key and private key operations. Public key operations are not restricted.</p>
  </blockquote>
  
  <p>This should allow me to encrypt the password using the public key before the user authenticates with their fingerprint, then decrypt using the private key only after the user is authenticated.</p>
  
  <pre><code>public KeyStore getKeyStore() {
      try {
          return KeyStore.getInstance("AndroidKeyStore");
      } catch (KeyStoreException exception) {
          throw new RuntimeException("Failed to get an instance of KeyStore", exception);
      }
  }
  
  public KeyPairGenerator getKeyPairGenerator() {
      try {
          return KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
      } catch(NoSuchAlgorithmException | NoSuchProviderException exception) {
          throw new RuntimeException("Failed to get an instance of KeyPairGenerator", exception);
      }
  }
  
  public Cipher getCipher() {
      try {
          return Cipher.getInstance("EC");
      } catch(NoSuchAlgorithmException | NoSuchPaddingException exception) {
          throw new RuntimeException("Failed to get an instance of Cipher", exception);
      }
  }
  
  private void createKey() {
      try {
          mKeyPairGenerator.initialize(
                  new KeyGenParameterSpec.Builder(KEY_ALIAS,
                          KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                          .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")
                          .setUserAuthenticationRequired(true)
                          .build());
          mKeyPairGenerator.generateKeyPair();
      } catch(InvalidAlgorithmParameterException exception) {
          throw new RuntimeException(exception);
      }
  }
  
  private boolean initCipher(int opmode) {
      try {
          mKeyStore.load(null);
  
          if(opmode == Cipher.ENCRYPT_MODE) {
              PublicKey key = mKeyStore.getCertificate(KEY_ALIAS).getPublicKey();
              mCipher.init(opmode, key);
          } else {
              PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_ALIAS, null);
              mCipher.init(opmode, key);
          }
  
          return true;
      } catch (KeyPermanentlyInvalidatedException exception) {
          return false;
      } catch(KeyStoreException | CertificateException | UnrecoverableKeyException
              | IOException | NoSuchAlgorithmException | InvalidKeyException
              | InvalidAlgorithmParameterException exception) {
          throw new RuntimeException("Failed to initialize Cipher", exception);
      }
  }
  
  private void encrypt(String password) {
      try {
          initCipher(Cipher.ENCRYPT_MODE);
          byte[] bytes = mCipher.doFinal(password.getBytes());
          String encryptedPassword = Base64.encodeToString(bytes, Base64.NO_WRAP);
          mPreferences.getString("password").set(encryptedPassword);
      } catch(IllegalBlockSizeException | BadPaddingException exception) {
          throw new RuntimeException("Failed to encrypt password", exception);
      }
  }
  
  private String decryptPassword(Cipher cipher) {
      try {
          String encryptedPassword = mPreferences.getString("password").get();
          byte[] bytes = Base64.decode(encryptedPassword, Base64.NO_WRAP);
          return new String(cipher.doFinal(bytes));
      } catch (IllegalBlockSizeException | BadPaddingException exception) {
          throw new RuntimeException("Failed to decrypt password", exception);
      }
  }
  </code></pre>
  
  <p>To be honest, I am not sure if any of this is right, it is bits and pieces from anything I could find on the subject. Everything I change throws a different exception, and this particular build does not run because I cannot instantiate the <code>Cipher</code>, it throws a <code>NoSuchAlgorithmException: No provider found for EC</code>. I have tried switch to <code>RSA</code> as well, but I get similar errors.</p>
  
  <p>So my question is basically this; how can I encrypt plaintext on Android, and make it available for decryption after the user is authenticated by the Fingerprint API? </p>
  
  <hr>
  
  <p>I have made some progress, mostly due to the discovery of the information on the <a href="http://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html" rel="noreferrer"><code>KeyGenParameterSpec</code></a> documentation page.</p>
  
  <p>I have kept <code>getKeyStore</code>, <code>encryptePassword</code>, <code>decryptPassword</code>, <code>getKeyPairGenerator</code> and <code>getCipher</code> mostly the same, but I changed the <code>KeyPairGenerator.getInstance</code> and <code>Cipher.getInstance</code> to <code>"RSA"</code> and <code>"RSA/ECB/OAEPWithSHA-256AndMGF1Padding"</code> respectively. </p>
  
  <p>I also changed the rest of the code to RSA instead of Elliptic Curve, because from what I understand, Java 1.7 (and therefore Android) does not support encryption and decryption with EC. I changed my <code>createKeyPair</code> method based on the "RSA key pair for encryption/decryption using RSA OAEP" example on the documentation page:</p>
  
  <pre><code>private void createKeyPair() {
      try {
          mKeyPairGenerator.initialize(
                  new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT)
                          .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                          .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                          .setUserAuthenticationRequired(true)
                          .build());
          mKeyPairGenerator.generateKeyPair();
      } catch(InvalidAlgorithmParameterException exception) {
          throw new RuntimeException(exception);
      }
  }
  </code></pre>
  
  <p>I also altered my <code>initCipher</code> method based on the <strong>known issue</strong> in the <code>KeyGenParameterSpec</code> documentation:</p>
  
  <blockquote>
    <p>A known bug in Android 6.0 (API Level 23) causes user authentication-related authorizations to be enforced even for public keys. To work around this issue extract the public key material to use outside of Android Keystore.</p>
  </blockquote>
  
  <pre><code>private boolean initCipher(int opmode) {
      try {
          mKeyStore.load(null);
  
          if(opmode == Cipher.ENCRYPT_MODE) {
              PublicKey key = mKeyStore.getCertificate(KEY_ALIAS).getPublicKey();
  
              PublicKey unrestricted = KeyFactory.getInstance(key.getAlgorithm())
                      .generatePublic(new X509EncodedKeySpec(key.getEncoded()));
  
              mCipher.init(opmode, unrestricted);
          } else {
              PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_ALIAS, null);
              mCipher.init(opmode, key);
          }
  
          return true;
      } catch (KeyPermanentlyInvalidatedException exception) {
          return false;
      } catch(KeyStoreException | CertificateException | UnrecoverableKeyException
              | IOException | NoSuchAlgorithmException | InvalidKeyException
              | InvalidAlgorithmParameterException exception) {
          throw new RuntimeException("Failed to initialize Cipher", exception);
      }
  }
  </code></pre>
  
  <p>Now I can encrypt the password, and save the encrypted password. But when I obtain the encrypted password and attempt to decrypt, I get a <code>KeyStoreException</code> <strong>Unknown error</strong>...</p>
  
  <pre><code>03-15 10:06:58.074 14702-14702/com.example.app E/LoginFragment: Failed to decrypt password
          javax.crypto.IllegalBlockSizeException
              at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:486)
              at javax.crypto.Cipher.doFinal(Cipher.java:1502)
              at com.example.app.ui.fragment.util.LoginFragment.onAuthenticationSucceeded(LoginFragment.java:251)
              at com.example.app.ui.controller.FingerprintCallback.onAuthenticationSucceeded(FingerprintCallback.java:21)
              at android.support.v4.hardware.fingerprint.FingerprintManagerCompat$Api23FingerprintManagerCompatImpl$1.onAuthenticationSucceeded(FingerprintManagerCompat.java:301)
              at android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23$1.onAuthenticationSucceeded(FingerprintManagerCompatApi23.java:96)
              at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:805)
              at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:757)
              at android.os.Handler.dispatchMessage(Handler.java:102)
              at android.os.Looper.loop(Looper.java:148)
              at android.app.ActivityThread.main(ActivityThread.java:5417)
              at java.lang.reflect.Method.invoke(Native Method)
              at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
              at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
          Caused by: android.security.KeyStoreException: Unknown error
              at android.security.KeyStore.getKeyStoreException(KeyStore.java:632)
              at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:224)
              at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:473)
              at javax.crypto.Cipher.doFinal(Cipher.java:1502) 
              at com.example.app.ui.fragment.util.LoginFragment.onAuthenticationSucceeded(LoginFragment.java:251) 
              at com.example.app.ui.controller.FingerprintCallback.onAuthenticationSucceeded(FingerprintCallback.java:21) 
              at android.support.v4.hardware.fingerprint.FingerprintManagerCompat$Api23FingerprintManagerCompatImpl$1.onAuthenticationSucceeded(FingerprintManagerCompat.java:301) 
              at android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23$1.onAuthenticationSucceeded(FingerprintManagerCompatApi23.java:96) 
              at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:805) 
              at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:757) 
              at android.os.Handler.dispatchMessage(Handler.java:102) 
              at android.os.Looper.loop(Looper.java:148) 
              at android.app.ActivityThread.main(ActivityThread.java:5417) 
              at java.lang.reflect.Method.invoke(Native Method) 
              at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
              at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
  </code></pre>