Search code examples
androidcryptographyandroid-keystore

Android keystore can't decrypt strings from the shared preferences


I am trying to use the Keystore to encrypt a string, and save this string to the shared preferences, then later on on a sub-subsequent app launch, decrypt the string. Yet, I believe I am missing the main point the keystore.

Mostly, I am based on this link below:

https://medium.com/@ericfu/securely-storing-secrets-in-an-android-application-501f030ae5a3#.80y72xi61

I wrote this wrapper using another thread I posted from a different issue

java.lang.IllegalArgumentException: bad base-64 when decrypting string

Yet, all the sample code I find, encrypt and decrypt during the same App run. This is never useful. I need to encrypt my string, save somewhere and decrypt it at later time. So this wrapper tries to initialize the KeyStore as such:

@TargetApi(Build.VERSION_CODES.M)
public KeyStoreHelper(boolean encrypt) {
    try {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
                KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
        if (encrypt) {
            keyPairGenerator.initialize(
                    new KeyGenParameterSpec.Builder(
                            MY_KEY_NAME_INSIDE_KEYSTORE,
                            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
                            .build());
        } else {
            keyPairGenerator.initialize(
                    new KeyGenParameterSpec.Builder(
                            MY_KEY_NAME_INSIDE_KEYSTORE,
                            KeyProperties.PURPOSE_DECRYPT)
                            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
                            .build());
        }
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        String provider = Build.VERSION.SDK_INT < Build.VERSION_CODES.M ? "AndroidOpenSSL" : "AndroidKeyStoreBCWorkaround";

        if (encrypt) {
            PublicKey publicKey = keyPair.getPublic();
            mInCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", provider);
            mInCipher.init(Cipher.ENCRYPT_MODE, publicKey);
        } else {
            PrivateKey privateKey = keyPair.getPrivate();
            mOutCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", provider);
            mOutCipher.init(Cipher.DECRYPT_MODE, privateKey);
        }
    } catch (Exception e) {
        Log.e(ERROR_TAG, Log.getStackTraceString(e));
    }
}
public static KeyStoreHelper getInstance(boolean encrypt) {
    if (mKeyStoreHelperInstance == null) {
        mKeyStoreHelperInstance = new KeyStoreHelper(encrypt);
    }
    return mKeyStoreHelperInstance;
}

Then I tried to encrypt some strings save to the preferences, as below

private SharedPreferences mSharedPreferences;    
private void testKeystoreHelper(boolean encrypt) {
    KeyStoreHelper keyStoreHelper;
    initSharedPreferences();
    final String sharedPreferencesAlias = "mysecret";
    String plainText;
    String secretString;
    if (encrypt) {
        plainText = "my secret string";
        keyStoreHelper = KeyStoreHelper.getInstance(true);
        secretString = keyStoreHelper.encrypt(plainText);
        Log.v(TAG, "Encrypted = " + secretString);
        mSharedPreferences.edit().putString(sharedPreferencesAlias, secretString).apply();
    } else {
        keyStoreHelper = KeyStoreHelper.getInstance(false);
        secretString = mSharedPreferences.getString(sharedPreferencesAlias, null);
        plainText = keyStoreHelper.decrypt(secretString);
        Log.v(TAG, "Decrypted" + plainText);
    }
}

finally I do one run with this:

testKeystoreHelper(true);

I quit the app and run again with this:

testKeystoreHelper(false);

But this never works giving me:

E/Error: java.io.IOException: Error while finalizing cipher at
javax.crypto.CipherInputStream.fillBuffer(CipherInputStream.java:104)

Because everytime the app launches although the keyname is the same these pairs are always different:

KeyPair keyPair = keyPairGenerator.generateKeyPair();

Since I am initializing the key every time. But how else can I get the KeyPair without initializing them?

So I missed the main point, long story short can someone lead me on the following basic algorithm?

  1. Init keystore
  2. get the pairs
  3. ecrypt the string and save to the preferences
  4. quit the app
  5. Init keystore getting the correct keypairs for decryption
  6. Get encrypted string from preferences
  7. decrypt it to memory

I have no idea how this can be done using two different app launches. I always find code the encrypt and decrypt within the same app run.

thank you!


Solution

  • As you indicated in your question, you need to recover the key from the keystore and not initialize it every time.

    Use this code to load AndroidKeyStore and get the private key

    KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    keyStore.load(null);
    KeyStore.Entry entry = keyStore.getEntry(MY_KEY_NAME_INSIDE_KEYSTORE, null);
    PrivateKey privateKey = ((KeyStore.PrivateKeyEntry) entry).getPrivateKey();
    

    Do not generate the key if it has been created. Check if exist with

    keyStore.containsAlias(MY_KEY_NAME_INSIDE_KEYSTORE);
    

    Recover public key with

    PublicKey publicKey = keyStore.getCertificate(MY_KEY_NAME_INSIDE_KEYSTORE).getPublicKey();