KeyPairGeneratorSpec replacement with KeyGenParameterSpec.Builder equivalents - Keystore operation failed
Asked Answered
K

2

5

The following method is deprecated

KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");

KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(this)
                            .setAlias(alias)
                            .setSubject(new X500Principal("CN=Sample Name, O=Android Authority"))
                            .setSerialNumber(BigInteger.ONE)
                            .setStartDate(start.getTime())
                            .setEndDate(end.getTime())
                            .build();

generator.initialize(spec);

The replacement I came upon looks like this

KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");

generator.initialize(new KeyGenParameterSpec.Builder
                            (alias, KeyProperties.PURPOSE_SIGN)
                            .setDigests(KeyProperties.DIGEST_SHA256)
                            .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
                            .build());

Although I am able to use this to generate a keypair entry and encrypt the value, I am unable to decrypt it

 public void encryptString(String alias) {
        try {
            KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);
            RSAPublicKey publicKey = (RSAPublicKey) privateKeyEntry.getCertificate().getPublicKey();

            String initialText = startText.getText().toString();
            if(initialText.isEmpty()) {
                Toast.makeText(this, "Enter text in the 'Initial Text' widget", Toast.LENGTH_LONG).show();
                return;
            }

            //Security.getProviders();

            Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
            inCipher.init(Cipher.ENCRYPT_MODE, publicKey);

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            CipherOutputStream cipherOutputStream = new CipherOutputStream(
                    outputStream, inCipher);
            cipherOutputStream.write(initialText.getBytes("UTF-8"));
            cipherOutputStream.close();

            byte [] vals = outputStream.toByteArray();
            encryptedText.setText(Base64.encodeToString(vals, Base64.DEFAULT));
        } catch (Exception e) {
            Toast.makeText(this, "Exception " + e.getMessage() + " occured", Toast.LENGTH_LONG).show();
            Log.e(TAG, Log.getStackTraceString(e));
        }
    }

public void decryptString(String alias) {
        try {
            KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);

            Cipher output = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
            output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());

            String cipherText = encryptedText.getText().toString();
            CipherInputStream cipherInputStream = new CipherInputStream(
                    new ByteArrayInputStream(Base64.decode(cipherText, Base64.DEFAULT)), output);
            ArrayList<Byte> values = new ArrayList<>();
            int nextByte;
            while ((nextByte = cipherInputStream.read()) != -1) {
                values.add((byte)nextByte);
            }

            byte[] bytes = new byte[values.size()];
            for(int i = 0; i < bytes.length; i++) {
                bytes[i] = values.get(i).byteValue();
            }

            String finalText = new String(bytes, 0, bytes.length, "UTF-8");
            decryptedText.setText(finalText);

        } catch (Exception e) {
            Toast.makeText(this, "Exception " + e.getMessage() + " occured", Toast.LENGTH_LONG).show();
            Log.e(TAG, Log.getStackTraceString(e));
        }

in the decrypt method, the following command fails:

 Cipher output = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
                output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());

with

java.security.InvalidKeyException: Keystore operation failed

I think it has to do with the KeyGenParamaterSpec.Builder has incorrect conditions, similarly that the encrypt Cipher types are incorrect strings, same thing in the decrypt function.

But this can all be traced back to the use of the new KeygenParameterSpec.Builder, as using the older deprecated method allows me to encrypt and decrypt.

How to fix?

Kenner answered 22/4, 2016 at 20:10 Comment(1)
trying to target android 18 -23 by calling keypairgeneratorspec.builder() and iam getting the line through it indivcating deprecated code? i have an if statement targeting 18-23? if i try to use keygenparameterspec.builder() iam getting a compile time error red line? looks like ive no option but to use the deprecated calls? any suggestions anyone?Semifluid
S
6

As Alex mentioned one missing piece is KeyProperties.PURPOSE_DECRYPT other one is setSignaturePaddings instead for that you have to use setEncryptionPaddings method. Here is the sample snippet.

    new KeyGenParameterSpec.Builder(ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
            // other options
           .build()

Refer documentation for more information.

Skyla answered 24/5, 2016 at 13:7 Comment(1)
Works for me :)Hedonism
F
2

It's hard to be 100% sure given that you didn't provide a full stack trace of the exception.

Your code generates the private key such that it is only authorized to be used for signing, not decrypting. Encryption works fine because it does not use the private key -- it uses the public key and Android Keystore public keys can be used without any restrictions. Decryption fails because it needs to use the private key, but your code did not authorize the use of the private key for decryption.

It looks like the immediate fix is to authorize the private key to be used for decryption. Thia is achieved by listing KeyProperties.PURPOSE_DECRYPT when invoking the KeyGenParameterSpec.Builder constructor. If the key shouldn't be used for signing, remove KeyProperties.PURPOSE_SIGN from there as well as remove setSignaturePaddings.

You'll also need to authorize the private key use with PKCS1Padding: invoke setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)

Fulmination answered 23/4, 2016 at 19:44 Comment(2)
the exception is handled, there is no crash that generates a stack trace, but I could start logging it manuallyKenner
You save me with the encryptPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1). I also set NONE-PADDING but it crash on Android 6. Your solution perfect. Thanks a lot.Sadie

© 2022 - 2024 — McMap. All rights reserved.