Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BadPaddingException when trying to decrypt Fingerprint API cypher

In my app, I save an encrypted version of the user's access code when they scan their finger in the initial setup [fingerprint enrollment]. When the user tries to unlock the app at a later time, I will attempt to decrypt this access code using the Fingerprint API [fingerprint verification].

However, Cipher.doFinal throws the following exception on decryption:

javax.crypto.BadPaddingException
 at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:482)
 at javax.crypto.Cipher.doFinal(Cipher.java:1502)
 (...)

Caused by: android.security.KeyStoreException: Invalid argument
         at android.security.KeyStore.getKeyStoreException(KeyStore.java:940)
         at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:224)
         at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:473)
        ... 12 more

The fingerprint scanner is displayed in a DialogFragment. The following functions are always called in order from the constructor, regardless of whether the fingerprint is being enrolled or verified.

Initialize the keystore:

private void initializeKeystore() {

    try {
        mKeyStore = KeyStore.getInstance(KEY_STORE_NAME); //AndroidKeyStore
    } catch (KeyStoreException e) {
        mKeyStore = null;
    }

    try {
        mKeyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEY_STORE_NAME);
    } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
        mKeyGenerator = null;
    }
}

Create a key:

private void createKey() {
    if (mKeyGenerator != null) {
        try {
            mKeyStore.load(null);

            KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(KEY_NAME,
                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                    .setUserAuthenticationRequired(true)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);

            mKeyGenerator.init(builder.build());
            mKeyGenerator.generateKey();
        } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException |
                CertificateException | IOException e) {
            mKeyGenerator = null;
        }
    }
}

Create cipher object:

private void createCipher() {
    try {
        mCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
                + KeyProperties.BLOCK_MODE_CBC + "/"
                + KeyProperties.ENCRYPTION_PADDING_PKCS7);
    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
        mCipher = null;
    }
}

When the the fingerprint scanner API successfully authenticates the user, the following code is called in sequence:

@Nullable Cipher getCipher(@NonNull final FingerprintStore ivStore) {
    if (mKeyStore != null && mKeyGenerator != null && mCipher != null) {
        try {
            mKeyStore.load(null);
            SecretKey key = (SecretKey)mKeyStore.getKey(KEY_NAME, null);

            switch (mEncryptionMode) {
                case MODE_ENCRYPT:
                    mCipher.init(Cipher.ENCRYPT_MODE, key);
                    ivStore.writeIv(mCipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV());
                    break;
                case MODE_DECRYPT:
                    byte[] iv = ivStore.readIv();
                    mCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
                    break;
            }
            return mCipher;
        } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
                | NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException
                | InvalidParameterSpecException | NullPointerException e) {
            return null;
        }
    }

    return null;
}

And, using the instance of Cipher just returned from the previous call:

@Nullable byte[] encryptOrDecrypt(@NonNull Cipher cipher, @NonNull byte[] subject) {
    try {
        return cipher.doFinal(subject);
    } catch (BadPaddingException | IllegalBlockSizeException e) {
        e.printStackTrace();
        return null;
    }
}

This call to doFinal works fine when encrypting the data, but throws the exception on decryption. I have inspected the byte[]'s of the both the initialization vector and the encrypted data and have found that they are being stored to disk (Base64) and read back into memory correctly.

like image 288
Thijs Avatar asked Sep 13 '16 22:09

Thijs


1 Answers

Never mind, apparently I overlooked the fact that the SecretKey should only be generated in the enrollment stage. Because createKey was called when trying to decrypt the data, it was overwritten with a newly generated key before doFinal was called. The code works perfectly now.

like image 125
Thijs Avatar answered Sep 28 '22 15:09

Thijs