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?
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.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With