Android Keystore Issues


#1

We are using SQLCipher to encrypt the sqlite database in our Android app and we are using the Android keystore to store the private key we encrypt the database password with. We have run into an issues where the keystore becomes unusable with either an "java.lang.IllegalStateException: could not generate key in keystore” or an “java.security.InvalidKeyException: javax.crypto.BadPaddingException: error:0407106B:rsa routines:RSA_padding_check_PKCS1_type_2:block type is not 02” when we attempt to use it. We are wondering if anyone has a recommended work around for either of these issues that would allow us to continue using the keystore?


#2

Hello @ToddP

Can you provide a code sample showing exactly what you are doing such that we can replicate the behavior? Thanks!


#3

We are using the following code to generate/retrieve a key from the keystore and then encrypt/decrypt the database password stored in a file. If you change from a password lock screen to pin or swipe you sometimes corrupt the keystore in such a way as either the key becomes mangled/null (leading to the bad padding error) or the keystore becomes unusable (resulting in the could not generate key error). Google said they fixed this in Lollipop and then it turned out not completely. In M it appears to be fixed.

import android.content.Context;
import android.security.KeyPairGeneratorSpec;

import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.util.Calendar;
import java.util.GregorianCalendar;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.security.auth.x500.X500Principal;

/**

  • Wraps {@link javax.crypto.SecretKey} instances using a public/private key pair stored in

  • the platform {@link java.security.KeyStore}. This allows us to protect symmetric keys with

  • hardware-backed crypto, if provided by the device.

  • See key wrapping for more

  • details.

  • Not inherently thread safe.
    */
    public class SecretKeyWrapper {

    private final Cipher mCipher;
    private final KeyPair mPair;

    /**

    • Create a wrapper using the public/private key pair with the given alias.
    • If no pair with that alias exists, it will be generated.
      */
      public SecretKeyWrapper(Context context, String alias)
      throws GeneralSecurityException, IOException {
      mCipher = Cipher.getInstance(“RSA/ECB/PKCS1Padding”);
      final KeyStore keyStore = KeyStore.getInstance(“AndroidKeyStore”);
      keyStore.load(null);
      if (!keyStore.containsAlias(alias)) {
      generateKeyPair(context, alias);
      }
      // Even if we just generated the key, always read it back to ensure we
      // can read it successfully.
      final KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(
      alias, null);
      mPair = new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey());
      }

    private static void generateKeyPair(Context context, String alias)
    throws GeneralSecurityException {
    final Calendar start = new GregorianCalendar();
    final Calendar end = new GregorianCalendar();
    end.add(Calendar.YEAR, 100);
    final KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
    .setAlias(alias)
    .setSubject(new X500Principal(“CN=” + alias))
    .setSerialNumber(BigInteger.ONE)
    .setStartDate(start.getTime())
    .setEndDate(end.getTime())
    .build();
    final KeyPairGenerator gen = KeyPairGenerator.getInstance(“RSA”, “AndroidKeyStore”);
    gen.initialize(spec);
    gen.generateKeyPair();
    }

    /**

    • Wrap a {@link javax.crypto.SecretKey} using the public key assigned to this wrapper.
    • Use {@link #unwrap(byte[])} to later recover the original
    • {@link javax.crypto.SecretKey}.
    • @return a wrapped version of the given {@link javax.crypto.SecretKey} that can be
    •     safely stored on untrusted storage.
      

    */
    public byte[] wrap(SecretKey key) throws GeneralSecurityException {
    mCipher.init(Cipher.WRAP_MODE, mPair.getPublic());
    return mCipher.wrap(key);
    }

    /**

    • Unwrap a {@link javax.crypto.SecretKey} using the private key assigned to this
    • wrapper.
    • @param blob a wrapped {@link javax.crypto.SecretKey} as previously returned by
    •        {@link #wrap(javax.crypto.SecretKey)}.
      

    */
    public SecretKey unwrap(byte[] blob) throws GeneralSecurityException {
    mCipher.init(Cipher.UNWRAP_MODE, mPair.getPrivate());
    return (SecretKey) mCipher.unwrap(blob, “AES”, Cipher.SECRET_KEY);
    }

}


#4

Hello @ToddP

Have you considered using a SecretKeyEntry within the KeyStore instead?


#5

@ToddP - Hello, Did you happen to fix this issue please? I have been trying to fix this for a while but no luck.


#6

Turns out its a bug in the Android keystore that Google keeps claiming they have fixed in 5.x but then it keeps coming back. We stopped using the keystore to store the encryption key.


#7

Ok, thanks. Can I also please ask, if you found an alternative to store the key on the device other than Keystore?


#8

Not a secure one. You can store it in shared preferences (which is not secure) or you have the user type in the password/pin every time that the app is launched as the seed value for the encryption key.


#9

Yes, as you said it’s not the secure way. Anyways, thanks for your response.