Decrypting message with RSA-OAEP and Android Keystore: IllegalBlockSizeException - android

I have the following code inside a try block that should generate a RSA public/private keypair use the public key to encrypt a message and decrypt again with the private key:
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
keyPairGenerator.initialize(new KeyGenParameterSpec.Builder(
"key1",
KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.build());
KeyPair keyPair = keyPairGenerator.generateKeyPair();
byte[] src = "hello world".getBytes();
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
byte[] cipherData = cipher.doFinal(src);
Cipher cipher2 = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher2.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
byte[] msg = cipher2.doFinal(cipherData);
Taken mostly from here and here.
The final line throws an exception of type javax.crypto.IllegalBlockSizeException with no message/further details. The three lines in logcat before the exception are
E keymaster1_device: Finish send cmd failed
E keymaster1_device: ret: 0
E keymaster1_device: resp->status: -1000
in case that matters at all.
Does anyone have an idea what could be going wrong?
Using minSdkVersion 23
Edit:
I just realised, if I use PKCS#1 v1.5 padding it works. That helps me for now, but I'd still like to try get it work with OAEP.

You need to put into the cipher the algorithm parameter spec when you encrypt
if (algorithmParameterSpec != null) {
encrypter.init(Cipher.ENCRYPT_MODE, getKey(), algorithmParameterSpec)
}
algorithmParameterSpec is
OAEPParameterSpec("SHA-256",
"MGF1",
MGF1ParameterSpec.SHA256,
PSource.PSpecified.DEFAULT)

Related

computing digital signature by using AndroidKeyStore

I want to calculate RSA digital signature in Android using AndroidKeyStore. I have two solutions, the first is using java.security.Signature and the second using javax.crypto.Cipher. At the begin, I try to use Signature object and compute a signature successfully, but I have a problem. Signature object make a digest itself from my data so my first question is:
1- Is there any way to use Signature object by disabling calculation hash?
Then I chose the second solution (using Cipher object) by the code bellow:
// *** Creating Key
KeyPairGenerator spec = KeyPairGenerator.getInstance(
// *** Specified algorithm here
// *** Specified: Purpose of key here
KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
spec.initialize(new KeyGenParameterSpec.Builder(
alias, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) // RSA/ECB/PKCS1Padding
.setKeySize(2048)
// *** Replaced: setStartDate
.setKeyValidityStart(notBefore.getTime())
// *** Replaced: setEndDate
.setKeyValidityEnd(notAfter.getTime())
// *** Replaced: setSubject
.setCertificateSubject(new X500Principal("CN=test"))
// *** Replaced: setSerialNumber
.setCertificateSerialNumber(BigInteger.ONE)
.build());
KeyPair keyPair = spec.generateKeyPair();
and using the key:
Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
inCipher.init(Cipher.ENCRYPT_MODE, privateKey);
but in "inCipher.init" function I get this error:
java.security.InvalidKeyException: Keystore operation failed
caused by: android.security.KeyStoreException: Incompatible purpose`
My second question is: 2- what is the problem? (I have to say, I can do encryption by public key but I can't do it by private key to calculate signature)
I encrypted a message with with private key without androidKeyStore and I succeeded. The code is in the below:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
PublicKey pub = kp.getPublic();
PrivateKey pvt = kp.getPrivate();
Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
inCipher.init(Cipher.ENCRYPT_MODE, pvt);
byte[] x = new byte[]{0x01, 0x01, 0x01, 0x01};
byte[] result = inCipher.doFinal(x, 0, x.length);
Regarding your first question:
If, as described in your comment, the message is already hashed (e.g. with SHA256) and only needs to be signed using PKCS#1 v1.5 padding, then this is possible with NONEwithRSA. However, it must be specified in the key properties that no digest is used.
If the signature should also comply with the standard, i.e. a verification with SHA256withRSA should be possible, the digest ID (more precisely, the DER encoding of the DigestInfo value) must be placed in front of the hashed message. The following code (based on the posted code) shows this for SHA256 (tested for Android P / API 28):
// Load keystore
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
// Create key (if not already in keystore)
String alias = "Some Alias";
if (!keyStore.containsAlias(alias)) {
Calendar notBefore = Calendar.getInstance();
Calendar notAfter = Calendar.getInstance();
notAfter.add(Calendar.YEAR, 1);
KeyPairGenerator spec = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
spec.initialize(new KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) // for signing / verifying
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) // use RSASSA-PKCS1-v1_5
.setDigests(KeyProperties.DIGEST_NONE) // apply no digest
.setKeySize(2048)
.setKeyValidityStart(notBefore.getTime())
.setKeyValidityEnd(notAfter.getTime())
.setCertificateSubject(new X500Principal("CN=test"))
.setCertificateSerialNumber(BigInteger.ONE)
.build());
spec.generateKeyPair();
}
// Retrieve key
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null);
PrivateKey privateKey = privateKeyEntry.getPrivateKey();
PublicKey publicKey = privateKeyEntry.getCertificate().getPublicKey();
// Hash message (hashedMessage corresponds to your message)
MessageDigest digest = MessageDigest.getInstance("SHA-256"); // SHA256 as digest assumed
byte[] message = "The quick brown fox jumps over the lazy dog".getBytes(StandardCharsets.UTF_8);
byte[] hashedMessage = digest.digest(message);
// Concatenate ID (in this example for SHA256) and message in this order
byte[] id = new byte[]{0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, (byte) 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
byte[] idHashedMessage = new byte[id.length + hashedMessage.length];
System.arraycopy(id, 0, idHashedMessage, 0, id.length);
System.arraycopy(hashedMessage, 0, idHashedMessage, id.length, hashedMessage.length);
// Sign with NONEwithRSA
Signature signing = Signature.getInstance("NONEwithRSA");
signing.initSign(privateKey);
signing.update(idHashedMessage);
byte[] signature = signing.sign();
// Verify with SHA256withRSA
Signature verifying = Signature.getInstance("SHA256withRSA"); // Apply algorithm that corresponds to the digest used, here SHA256withRSA
verifying.initVerify(publicKey);
verifying.update(message);
boolean verified = verifying.verify(signature);
System.out.println("Verification: " + verified);
Adding the digest ID is necessary because according to RFC 8017, signing with PKCS#1 v1.5 uses RSASSA-PKCS1-v1_5 padding, which includes the digest ID.
Concerning your second question:
The simple formula "signing equals encryption with the private key" is only valid if no padding is used (textbook RSA), s. also here. In practice, however, padding must always be applied for security reasons. For encryption and signing different paddings are involved: For encryption with PKCS#1 v1.5 padding the variant RSAES-PKCS1-v1_5 is applied and for signing with PKCS#1 v1.5 padding the variant RSASSA-PKCS1-v1_5.
When using private key encryption, the padding variant applied can vary depending on the library (if private key encryption is supported at all), which generally leads to incompatibilities. Probably to avoid such problems, the Android keystore may not support private key encryption (at least I haven't found a configuration that makes this possible).
The Java API as well as the Android API without keystore both support private key encryption. Therefore the last posted code works. Furthermore, for PKCS#1 v1.5 padding the variant RSASSA-PKCS1-v1_5 is used. If here the (e.g. with SHA256) hashed message would be passed and the ID of the digest would be placed in front of it, the generated signature could be verified with the algorithm SHA256withRSA (as in the posted code above).

IllegalBlockSizeException "null" in RSA decryption on Android

I'm currently working on an Android client of my encryption software, but I kept getting IllegalBlockSizeException, and e.getMessage() always returns null
Here's the code I used to find the problem
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA,"AndroidKeyStore");
generator.initialize(new KeyGenParameterSpec.Builder(
"1",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setDigests(KeyProperties.DIGEST_SHA256)
.setKeySize(2048)
.build());
KeyPair kp = generator.generateKeyPair();
KeyStore store = KeyStore.getInstance("AndroidKeyStore");
store.load(null);
PublicKey pubKey = store.getCertificate("1").getPublicKey();
PrivateKey privkey = ((KeyStore.PrivateKeyEntry) store.getEntry("1",null)).getPrivateKey();
byte[] content = "123456789".getBytes();
Cipher encrypt = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
encrypt.init(Cipher.ENCRYPT_MODE,pubKey);
Cipher decrypt = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
decrypt.init(Cipher.DECRYPT_MODE,privkey);
byte[] A = encrypt.doFinal(content);
byte[] B = decrypt.doFinal(A);
String resultA = new String(A);
String resultB = new String(B);
Toast.makeText(getApplicationContext(), resultA + "\n" + resultB, Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), e.toString()+"\n"+e.getMessage(), Toast.LENGTH_LONG).show();
}
As I said there was an IllegalBlockSizeException, which I found was thrown by decrypt.doFinal() , and e.getMessage() returns null
Here's what I get from the debug console
W/System.err: javax.crypto.IllegalBlockSizeException
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:519)
at javax.crypto.Cipher.doFinal(Cipher.java:1741)
W/System.err: at storm.cyanine.decryptor.MainActivity$override.onJob(MainActivity.java:187)
at storm.cyanine.decryptor.MainActivity$override.onOpen(MainActivity.java:115)
at storm.cyanine.decryptor.MainActivity$override.access$dispatch(Unknown Source:50)
at storm.cyanine.decryptor.MainActivity.onOpen(Unknown Source:15)
at java.lang.reflect.Method.invoke(Native Method)
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:385)
W/System.err: at android.view.View.performClick(View.java:6329)
at android.view.View$PerformClick.run(View.java:24996)
at android.os.Handler.handleCallback(Handler.java:809)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:166)
at android.app.ActivityThread.main(ActivityThread.java:7377)
W/System.err: at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:469)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:963)
W/System.err: Caused by: android.security.KeyStoreException: Unknown error
at android.security.KeyStore.getKeyStoreException(KeyStore.java:709)
at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:224)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:506)
... 16 more
At first, I thought it was some restriction of AndroidKeyStore that prevents me from using the private key, so I tried using directly the one generated by KeyPairGenerator but nothing changed
What could cause this problem?
I've been searching online for days and I found nothing wrong with the code above (apart from not being the final product)
My device is Android 8.1.0
Edit: Thanks Steve Miskovetz a lot for the solution, and I just found the Android official guide for this topic:
Cryptography
(don't know why I wasn't able to find this earlier)
It looks like your issue was introduced with Android Oreo, but has a workaround.
This stackoverflow post discusses it:
Android 8.0: IllegalBlocksizeException when using RSA/ECB/OAEPWithSHA-512AndMGF1Padding
This Google issue tracker has good discussion on it:
https://issuetracker.google.com/issues/36708951
You need to add this line:
OAEPParameterSpec sp = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-1"), PSource.PSpecified.DEFAULT);
And modify these lines, adding the sp parameter:
encrypt.init(Cipher.ENCRYPT_MODE,pubKey,sp);
decrypt.init(Cipher.DECRYPT_MODE,privkey,sp);
Your full code with modifications here:
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA,"AndroidKeyStore");
generator.initialize(new KeyGenParameterSpec.Builder(
"1",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setDigests(KeyProperties.DIGEST_SHA256)
.setKeySize(2048)
.build());
KeyPair kp = generator.generateKeyPair();
KeyStore store = KeyStore.getInstance("AndroidKeyStore");
store.load(null);
PublicKey pubKey = store.getCertificate("1").getPublicKey();
PrivateKey privkey = ((KeyStore.PrivateKeyEntry) store.getEntry("1",null)).getPrivateKey();
byte[] content = "123456789".getBytes();
OAEPParameterSpec sp = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-1"), PSource.PSpecified.DEFAULT);
Cipher encrypt = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
encrypt.init(Cipher.ENCRYPT_MODE,pubKey,sp);
Cipher decrypt = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
decrypt.init(Cipher.DECRYPT_MODE,privkey,sp);
byte[] A = encrypt.doFinal(content);
byte[] B = decrypt.doFinal(A);
String resultA = new String(A);
String resultB = new String(B);
Toast.makeText(getApplicationContext(), resultA + "\n" + resultB, Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), e.toString()+"\n"+e.getMessage(), Toast.LENGTH_LONG).show();
}
asymmetric key encryption available from android 6+( api 23+). This is a good guide for android's encryption. Additionsly after getBytes called you need to encode bytes with Base64.

App throwing "Unsupported MGF1 digest: SHA-256. Only SHA-1 supported" although I use recommended approach

In my android app I want to store secret keys in android key store. But My App throws java.security.InvalidAlgorithmParameterException: Unsupported MGF1 digest: SHA-256. Only SHA-1 supported when I use AndroidKeyStoreRSAPrivateKey in Cipher initialization:
KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA, "AndroidKeyStore");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 1);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
.setAlias(ALIAS)
.setKeyType(KeyProperties.KEY_ALGORITHM_RSA)
.setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, F4))
.setSubject(new X500Principal("CN=" + ALIAS))
.setSerialNumber(BigInteger.valueOf(Math.abs(ALIAS.hashCode())))
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.build();
try {
kpg.initialize(spec);
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
}
KeyPair kp = kpg.generateKeyPair();
publicKey = kp.getPublic();
privateKey = kp.getPrivate();
//......................
// creating and initalizing Cipher
final Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
// in this place exception is thrown
cipher.init(Cipher.DECRYPT_MODE,
key,
new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256,
PSource.PSpecified.DEFAULT));
Exception is thrown in cipher.init(). But when my private key is instance of OpenSSLRSAPrivateKey generated by KeyFactory.getInstance(RSA) issue is not reproduced and all works fine, excepting that I need exactly AndroidKeyStoreRSAPrivateKey. I read developer doc https://developer.android.com/guide/topics/security/cryptography , but it doesn't contain any info regarding to my problem.
How should I change the code to solve the issue?

Android KeyStore: Unsupported secret key algorithm: AES/CBC/PKCS5Padding

I am trying to store an AES key in the Android KeyStore using following code:
SecretKey AESkey = new SecretKeySpec(
byteKey, 0, byteKey.length, "AES/CBC/PKCS5Padding");
if (ks == null)
{
ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
}
ks.deleteEntry("aes_key");
ks.setEntry("aes_key",
new KeyStore.SecretKeyEntry(AESkey),
new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
The line with 'setEntry(...)' fails throwig:
java.security.KeyStoreException: java.lang.IllegalArgumentException: Unsupported secret key algorithm: AES/CBC/PKCS5Padding
How can I store my key in the Android.KeyStore?
CBC and PKCS5Padding are not part of a key but key size is.
Somewhat guessing given the error message just use "AES".
SecretKey AESkey = new SecretKeySpec(byteKey, 0, byteKey.length, "AES");
The documentation is thin at best and the closest I can find is SecretKeyFactory Algorithms: "AES" Constructs secret keys for use with the AES algorithm. See: SecretKeyFactory Algorithms.

encoded text of secretkey from keystore is null in android M

I need to implement 256 bit AES encryption, I refer this- http://nelenkov.blogspot.jp/2015/06/keystore-redesign-in-android-m.html
The KeyGenerator looks like this .
KeyGenerator keyGenerator = KeyGenerator
.getInstance(KeyProperties.KEY_ALGORITHM_AES,"AndroidKeyStore");
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(keyName,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setUserAuthenticationValidityDurationSeconds(5 *11160)
.build();
keyGenerator.init(spec);
SecretKey key1 = keyGenerator.generateKey();
When I import encoded value of the key from keystore, it returns null to me.
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
SecretKey key3 = (SecretKey) keyStore.getKey(keyName, null);
Log.d("Test MM",key3.getEncoded()+",");
I found null value of key3.getEncoded() in logcat.Please give me some suggestions.
Symmetric keys generated in the keystore are unexportable in Android M. So it works exactly as it is supposed to. You can still use the key with a Cipher to encrypt/decrypt, but you cannot get the raw key bytes. Generally you should need to either.

Categories

Resources