How to create accessible PrivateKeys? - android

I've stumpled upon following Problem:
I need to create a KeyPair where I need to access the PrivateKey's getEncoded() once, before inserting it into the AndroidKeyStore.
When generating a Key via AndroidKeyStore, the resulting Keys getEncoded() method returns null (as intended as an extra key protection mechanism).
This is how I generate a Key using a KeyGenParameterSpec (targeting only devices above Android.M):
public AlgorithmParameterSpec createSpec(){
Calendar start = new GregorianCalendar();
Calendar end = new GregorianCalendar();
end.add(Calendar.YEAR, 1);
String alias = "myKeyAlias";
AlgorithmParameterSpec spec = new KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY |
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setCertificateSubject(new X500Principal("CN=" + alias))
.setDigests(KeyProperties.DIGEST_SHA256)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.setCertificateSerialNumber(BigInteger.valueOf(1337))
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.build();
return spec;
}
public KeyPair generateKeyPairInKeystore(AlgorithmParameterSpec spec){
KeyPairGenerator kpGenerator = KeyPairGenerator
.getInstance("RSA", "AndroidKeyStore");
kpGenerator.initialize(spec);
KeyPair kp = kpGenerator.generateKeyPair();
//here kp.getPrivate().getEncrypted() doesn't give me the key
return kp;
}
public KeyPair generateKeyPair(AlgorithmParameterSpec spec){
KeyPairGenerator kpGenerator = KeyPairGenerator
.getInstance(SecurityConstants.TYPE_RSA);
kpGenerator.initialize(spec);
KeyPair kp = kpGenerator.generateKeyPair();
//I cannot receive the encrypted Key here either
//kp.getPrivate().getEncrypted();
return kp;
}
These Keys are generated inside AndroidKeyStore so they are automatically stored there. But I cannot access getEncrypted() to send it to a trusted Server.
So what I'm missing is:
How to create a PrivateKey where getEncoded() is accessible?
How do I store it inside Androids secure KeyStore (as I need to provide Certificates along)?

Related

Android KeyStore with StrongBox: How to test for User Presence?

Android API 28 running on a Pixel 3 gives the option to require User Presence for keys generated inside the Android KeyStore. But how do I actually test for user presence when creating a signature with that key? Am I missing something in the docs?
KeyGenParameterSpec.Builder keyGenSpec = new KeyGenParameterSpec.Builder("alias", KeyProperties.PURPOSE_SIGN)
.setDigests(KeyProperties.DIGEST_SHA256)
.setKeySize(256)
.setIsStrongBoxBacked(true)
.setUserPresenceRequired(true);
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
keyPairGenerator.initialize(keyGenSpec.build());
keyPairGenerator.generateKeyPair();
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null, null);
PrivateKey privateKey = (PrivateKey) keyStore.getKey("alias", null);
Signature signature = Signature.getInstance("SHA256withECDSA");
signature.initSign(privateKey);
signature.update("Hello".getBytes(Charset.defaultCharset()));
byte[] sign = signature.sign();
That code throws android.security.KeyStoreException: -69 which translates to PROOF_OF_PRESENCE_REQUIRED. I've also tried to wrap the signature process in an BiometricPrompt, but to no avail.

Generate symmetric key and store in android keystore

I am attempting to do the following,
Generate a secret key for sqlcipher.
Store the secret in android keystore.
Retrieve secret from keystore.
I've found nearly everything I need, but I'm having trouble getting the below pieces of code working together.
Pre: Setup keystore
private void InitialiseKeystore() throws ... {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
boolean containsAlias = keyStore.containsAlias("com.example.myapp");
KeyPairGenerator kpg = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
if (!containsAlias) {
kpg.initialize(new KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setDigests(KeyProperties.DIGEST_SHA256,
KeyProperties.DIGEST_SHA512)
.build());
KeyPair kp = kpg.generateKeyPair();
}
Generating symmetric key
I found these good examples but they're creating public/private keys, as the same with many others, so a bit confused there, but I have the following.
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128); //<------------------------------------- [ERRORS HERE]
SecretKey secretKey = keyGen.generateKey();
Storing in keystore
Reviewing the documentation, it seems I should use KeyStore.SecretKeyEntry to store secrets, so this is what I currently have,
KeyStore.SecretKeyEntry secretKeyEntry = new KeyStore.SecretKeyEntry(secretKey);
keyStore.setKeyEntry("key1", secretKeyEntry.getSecretKey().getEncoded(), null);
Retrieving from keystore
private static byte[] getRawSecret() throws Exception {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
byte[] raw = keyStore.getKey("key1", null).getEncoded();
return raw;
}
Here's where it goes wrong, in another function I'm trying to call getRawSecret().
SQLiteDatabase.openOrCreateDatabase(databaseFile, getRawSecret().toString(), null);
ERROR: Cannot initialize without a android.security.keystore.KeyGenParameterSpec parameter
So question, how do I create a symmetric key and store in keystore? As the error is telling me to use KeyGenParameterSpec but as in the examples linked above, this wants to create public/private keys. What am I doing wrong?
I used this to solve my issue,
Guide: https://medium.com/#josiassena/using-the-android-keystore-system-to-store-sensitive-information-3a56175a454b
Code: https://gist.github.com/JosiasSena/3bf4ca59777f7dedcaf41a495d96d984

Password protected keys in AndroidKeystore

I have Generated a RSA key pair in AndroidKeystore. I need to protect my Alias with a password so that no other can access the same alias without the password.
I have used the following code to generate the keypair.
if (!keyStore.containsAlias(alias)) {
Calendar notBefore = Calendar.getInstance();
Calendar notAfter = Calendar.getInstance();
notAfter.add(Calendar.YEAR, 1);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(this)
.setAlias(alias)
.setKeyType("RSA")
.setKeySize(2048)
.setSubject(new X500Principal("CN=test"))
.setSerialNumber(BigInteger.ONE)
.setStartDate(notBefore.getTime())
.setEndDate(notAfter.getTime())
.build();
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
generator.initialize(spec);
KeyPair keyPair = generator.generateKeyPair();
}

android.security.KeyStoreException: Incompatible digest when signing with RSA

When I try to sign a hashed value in my android app that I get from outside, I get the above mentioned exception.
The code for generating the keypair is:
public static KeyPair generateKeyPair(Context context, String username) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEY_STORE);
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 1);
AlgorithmParameterSpec parameterSpec;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
parameterSpec = new KeyGenParameterSpec.Builder(MY_KEY_ALIAS, KeyProperties.PURPOSE_SIGN)
.setKeySize(KEY_SIZE)
.setCertificateSubject(usernameToSubject(username))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.build();
} else {
// Here I build the keys for older versions. This is not part of my problem
}
keyPairGenerator.initialize(parameterSpec);
return keyPairGenerator.generateKeyPair();
}
Later on I sign the hash I get from outside:
public static byte[] signHash(byte[] hashToSign) {
try {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
keyStore.load(null);
KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(MY_KEY_ALIAS, null);
PrivateKey privateKey = keyEntry.getPrivateKey();
DigestAlgorithmIdentifierFinder hashAlgorithmFinder = new DefaultDigestAlgorithmIdentifierFinder();
AlgorithmIdentifier hashingAlgorithmIdentifier = hashAlgorithmFinder.find(KeyProperties.DIGEST_SHA256);
DigestInfo digestInfo = new DigestInfo(hashingAlgorithmIdentifier, hashToSign);
byte[] hashToEncrypt = digestInfo.getEncoded();
Cipher cipher = Cipher.getInstance("RSA/ECB/Pkcs1Padding");
cipher.init(Cipher.ENCRYPT_MODE, privateKey); // <= the exception is thrown here
return cipher.doFinal(hashToEncrypt);
} catch (Throwable e) {
Log.e("KeyStoreWrapper", "Error while signing: ", e);
}
return "Could not sign the message.".getBytes(StandardCharsets.UTF_16LE);
}
The error I get is:
java.security.InvalidKeyException: Keystore operation failed
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1004)
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1024)
at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:53)
at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:263)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:108)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:612)
at javax.crypto.Cipher.tryCombinations(Cipher.java:532)
at javax.crypto.Cipher.getSpi(Cipher.java:437)
at javax.crypto.Cipher.init(Cipher.java:815)
at javax.crypto.Cipher.init(Cipher.java:774)
at de.new_frontiers.m2fa.security.KeyStoreWrapper.signHash(KeyStoreWrapper.java:186)
at de.new_frontiers.m2fa.bluetooth.BluetoothHandler.handleMessage(BluetoothHandler.java:93)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7229)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
Caused by: android.security.KeyStoreException: Incompatible digest
at android.security.KeyStore.getKeyStoreException(KeyStore.java:944)
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1024) 
at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:53) 
at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89) 
at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:263) 
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:108) 
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:612) 
at javax.crypto.Cipher.tryCombinations(Cipher.java:532) 
at javax.crypto.Cipher.getSpi(Cipher.java:437) 
at javax.crypto.Cipher.init(Cipher.java:815) 
at javax.crypto.Cipher.init(Cipher.java:774) 
at com.mycompany.security.KeyStoreWrapper.signHash(KeyStoreWrapper.java:186) 
In the android documentation I see:
For signing and verification operations a digest must be specified in
the additional_params argument of begin. If the specified digest is
not in the digests associated with the key, the operation must fail
with KM_ERROR_INCOMPATIBLE_DIGEST.
But as you can see I create the keypair with KeyProperties.DIGEST_SHA256 and set the DigestInfo to the same algorithm. So I don't understand why I get the error "Incompatible digest". Can anybody shed some light on this?
Oh, for anybody who is wondering why I don't use Signature.sign(): this would need the plaintext to sign then creates a hash, a DigestInfo and then encrypts it with the private key. I already get the hash from outside and this is something I cannot change.
 
Encryption with private key using RSA/ECB/PKCS1Padding is available on AndroidKeyStore from Android 18, so you should be able to perform a valid digital signature with the received hash.
I guess the problem is setting the key usage to sign and not for encryption (that is really what you want the key for). Try this:
parameterSpec = new KeyGenParameterSpec.Builder(MY_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | | KeyProperties.PURPOSE_DECRYPT)
.setKeySize(KEY_SIZE)
.setCertificateSubject(usernameToSubject(username))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.build();
Check also that the hashToSign is really SHA-256 (32 bytes)
pedrofbs answer helped me to get things right in the end. I had already changed the purpose for the key to the value mentioned in my comment to his answer: KeyProperties.PURPOSE_SIGN|KeyProperties.PURPOSE_ENCRYPT|Key‌​Properties.PURPOSE_D‌​ECRYPT, but forgot to call .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1). So a big thank you, pedrofs, for spotting this!
Unfortunately it still did not work. Fiddling around with different settings on the key I realized that I hadn't protected the device I use for testing with a password, pin or something else. So adding .setUserAuthenticationRequired(false) to the KeyGenParameterSpec did the trick. I know that this is not safe and has to be changed but at the moment I'm just doing a proof of concept, so that's fine. :-)
In case somebody else stumbles upon this problem: here is the whole working code for the key generation:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
parameterSpec = new KeyGenParameterSpec.Builder(LOGON_KEY_ALIAS, KeyProperties.PURPOSE_SIGN|KeyProperties.PURPOSE_ENCRYPT|KeyProperties.PURPOSE_DECRYPT)
.setKeySize(KEY_SIZE)
.setUserAuthenticationRequired(false)
.setCertificateSubject(usernameToSubject(username))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)//, KeyProperties.DIGEST_NONE, KeyProperties.DIGEST_SHA224, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512, KeyProperties.DIGEST_MD5)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.build();
}

How to store a created private key into Security KeyStore Android?

I have followed this below code that I need to generate a keypair stored in KeyStore
// generate a key pair
Context ctx = getContext();
Calendar notBefore = Calendar.getInstance()
Calendar notAfter = Calendar.getInstance();
notAfter.add(1, Calendar.YEAR);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(ctx)
.setAlias("key1")
.setSubject(
new X500Principal(String.format("CN=%s, OU=%s", alais,
ctx.getPackageName())))
.setSerialNumber(BigInteger.ONE).setStartDate(notBefore.getTime())
.setEndDate(notAfter.getTime()).build();
KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
kpGenerator.initialize(spec);
KeyPair kp = kpGenerator.generateKeyPair();
By this way, my keypair is generated and stored into Keystore but when calling this function: getPrivateKey.encoded() , it will return a null bytes array.
My will is I want generate normally, I can get both PrivateKey and PublicKey Encode (This is my own purpose). Then I want to store them into KeyStore to keep secret.
I have tried this: keyStore.setEntry(alias, privateKey, cert) but I got exception due to wrong params maybe. I don't understand what certificate I should get from?
I really appreciate your comments

Categories

Resources