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.
Related
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).
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)
I want to store my AES-256 key to AndroidKeyStore, this AES-256 key is raw key (a random 32 byte). I try some code like this.
public foo () {
SecureRandom sr = new SecureRandom();
byte[] key = new byte[32];
sr.nextBytes(key);
try {
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
SecretKeySpec sks = new SecretKeySpec(key, "AES");
SecretKeyFactory skf = SecretKeyFactory.getInstance("AES");
SecretKey sk = skf.generateSecret(sks);
ks.setEntry("key", new KeyStore.SecretKeyEntry(sk), new
KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT).build());
KeyStore.SecretKeyEntry entry = (KeyStore.SecretKeyEntry) ks.getEntry("key", null);
SecretKey skLoad = (SecretKey) ks.getKey("key", null);
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, skLoad);
Log.i(TAG, Arrays.toString(cipher.doFinal(plainBytes)));
} catch (Exception e) {
e.printStackTrace();
}
}
I get the exception at line SecretKey sk = skf.generateSecret(sks);
java.security.spec.InvalidKeySpecException: To generate secret key in Android Keystore, use KeyGenerator initialized with android.security.keystore.KeyGenParameterSpec
I know we can save key with using KeyGenerator with KeyGenParameterSpec, but I have some reason to use owner key and KeyGenParameter seem can't import my owner key. So have any idea for this problem, thank all!
Generally you should not import keys from outside the key store, as they are insecure before they enter. So adding them later has limited benefits.
However, you can do a little trick: create a wrapping key in the key store and use it to wrap your symmetric key, and store the result. Then you can simply reverse the process when the key is needed again.
Unfortunately the best methods for storing keys such as (GCM-)SIV mode is generally not implemented, but hey, now you've at least heard about it.
From API level 28, Google has restricted Security provider feature(bouncy castle issue).
So alternatively we have added Security provider using spongy castle
Now we can able to generate a keypair. But the key pair is not matching with the previous one. We can't get Private keyThis is we used previously, Old codeapi 27:
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", "BC");
SecureRandom random =SecureRandom.getInstance("SHA1PRNG");
keyGen.initialize(256, random);KeyFactory kaif = KeyFactory.getInstance("EC", "BC");
KeyPair pair = keyGen.generateKeyPair();
PrivateKey privateKey = pair.getPrivate();
PublicKey publicKey = pair.getPublic();
After the API level issue, we have removed "BC" and added Bouncy Castle manually by adding the below lineSecurity.insertProviderAt(BouncyCastleProvider(), 1);
by implementing Bouncy castle in dependencies,
implementation "com.madgag.spongycastle:core:1.58.0.0"
implementation "com.madgag.spongycastle:prov:1.58.0.0"
But the key pair is not matching with the previous one.
New Code:api 28
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
keyGen.initialize(256, random);
KeyFactory kaif = KeyFactory.getInstance("EC");
KeyPair pair = keyGen.generateKeyPair();
PrivateKey privateKey = pair.getPrivate();
PublicKey publicKey = pair.getPublic();
But the key pair is not matching with the previous one.
Image:
try insert new BouncyCastleProvider() on the first row of your security provider and remove all setprovider("BC") from your code.
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.