Android keystore keys wiped regularly - android

I am using Android's keystore to implement fingerprint unlock of my Android app. I therefore use KeyGenerator to create a key using
var _keyGen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, "AndroidKeyStore")
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(GetAlias(_keyId),
KeyStorePurpose.Encrypt | KeyStorePurpose.Decrypt)
.SetBlockModes(KeyProperties.BlockModeCbc)
// Require the user to authenticate with biometry to authorize every use
// of the key
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingPkcs7)
.SetUserAuthenticationRequired(true);
_keyGen.Init(
builder
.Build());
_keyGen.GenerateKey();
When I later enumerate the aliases in the store I find the key I have created:
_keystore.Load(null);
var aliases = _keystore.Aliases();
if (aliases == null)
{
og("KS: no aliases");
}
else
{
while (aliases.HasMoreElements)
{
var o = aliases.NextElement();
Log("alias: " + o?.ToString());
}
}
While this is working reliably on most devices, some devices (e.g. Google Pixel 4a) seem to "lose" the keys in the Keystore quite regularly. When enumerating the aliases as above, no key is listed anymore. I can reproduce this behavior by updating my app using a debugger (settings are such that SharedPreferences and app data are kept and I do not have this behavior on another device).
Is there anything I can do to prevent losing the keys?

Related

Check if client certificate is already installed in Android Programatically

I have an android app where I am installing the client certificate using the following code.
val inputStream: InputStream = resources.openRawResource(R.raw.client)
val intent = KeyChain.createInstallIntent()
val p12: ByteArray = inputStream.readBytes()
intent.putExtra(KeyChain.EXTRA_PKCS12, p12)
intent.putExtra(KeyChain.EXTRA_NAME, "Sample cert")
startActivityForResult(intent,3)
Now once user installs the certificate, I dont want to repeat this again so I want to check if the certificate is already installed.
I used the following code to check it, but doest get the certificate with both "AndroidCAStore" and "PKCS12".
"AndroidCAStore" - returns all trusted CA certs but my certificate is in user credentials.
"PKCS12" - IS empty
//val ks = KeyStore.getInstance("AndroidCAStore")
val ks: KeyStore = KeyStore.getInstance("PKCS12")
if (ks != null) {
ks.load(null, null)
val aliases = ks.aliases()
while (aliases.hasMoreElements()) {
val alias = aliases.nextElement() as String
val cert = ks.getCertificate(alias) as X509Certificate
Log.d("Cert ---->",cert.issuerDN.name)
if (cert.issuerDN.name.contains(issuerDn)) {
return true
}
}
}
Can some one help me fix this.
When you call
val intent = KeyChain.createInstallIntent()
you are storing the Certificate in the Android Keychain and I don't think there's a way of accessing the Certificates stored there programatecally, see this unanswered post.
Since you want to check if the certificate was installed in the KeyChain, you can call KeyChain.getPrivateKey() or KeyChain.getCertificateChain() and if they return null, then it means that the Certificate has not been installed yet.
Note: You have the limitation that you have to call KeyChain.choosePrivateKeyAlias first to establish trust between the app and the KeyChain, otherwise you'll get a KeyChain exception.
If you don't need to use the KeyChain, then you can simply create your own KeyStore and add your certificates to it. Then you will be able to call aliases() to get all of the aliases of the certificates in the KeyStore.

How to use BiometricPrompt with CryptoObject to get KeyStore access for both Decryption and Encryption at the same time?

PROBLEM DESCRIPTION: When accessing the Android KeyStore with BiometricPrompt authentication, the BiometricPrompt appears every time when I have to perform read or write operation to the KeyStore. I am looking for a solution to authenticate only once, and then manipulate data in the keystore as I wish, in a similar way as it is done on iOS KeyChain.
I have implemented biometric authentication for Android so that I can store app API AuthRefreshToken in the application KeyStore secured by the biometric authentication, by calling setUserAuthenticationRequired(true) on the KeyStore params builder (see below). I have followed examples from Google (https://github.com/android/security-samples/tree/main/BiometricLoginKotlin) and other developers and have made solution working successfully. I am now trying to resolve the problem described above for second working day without success, and now considering using BiometricPrompt without CryptoObject, which would be a big disappointment. I suspect there is a way to authenticate once for a period of time, perhaps by setting paramsBuilder.setUserAuthenticationValidityDurationSeconds(30), but I am unable to achieve the intended result.
To get access to the KeyStore and read the API AuthRefreshToken, I use this code:
biometricPrompt = BiometricPromptUtils.createBiometricPrompt(this, ::decryptServerTokenFromStorage)
val promptInfo = BiometricPromptUtils.createPromptInfo(this)
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
fun decryptServerTokenFromStorage(authResult: BiometricPrompt.AuthenticationResult) {
ciphertextWrapper?.let { textWrapper ->
authResult.cryptoObject?.cipher?.let {
val authRefreshToken = cryptographyManager.decryptData(textWrapper.ciphertext, it)
// Use authRefreshToken to get authToken from the API
// The API returns new authRefreshToken which has to be saved back to the KeyStore
}
}
}
Everything works fine, and I get decrypted data. However, after each authentication on the app API using AuthRefreshToken, the token changes and I have to immediately save it back to the KeyStore. When this happens, I use below code, which displays the BiometricPrompt again. This causes the UI flow to show the BiometricPrompt twice:
biometricPrompt = BiometricPromptUtils.createBiometricPrompt(this, ::encryptServerTokenToStorage)
val promptInfo = BiometricPromptUtils.createPromptInfo(this)
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
fun encryptServerTokenToStorage(authResult: BiometricPrompt.AuthenticationResult) {
authResult.cryptoObject?.cipher?.apply {
SampleAppUser.refreshAuthToken?.let { refreshAuthToken ->
Log.d(TAG, "The token from server is $refreshAuthToken")
val encryptedServerTokenWrapper = cryptographyManager.encryptData(refreshAuthToken, this)
// Now save encrypted authRefreshToken together with initializationVector in the app prefs for future authentications
)
}
}
}
How can I authenticate at once with the BiometricPrompt so that I have full read/write access to the KeyStore for, let say, 1 minute or a longer interval without calling the BiometricPrompt multiple times?
I have tried different approaches and tried recreating the Cipher or reinitializing it for a different purpose, however in all these and similar attempts I am getting Javax.Crypto.IllegalBlockSizeException with message 'Key user not authenticated'
The keystore initialization is as follows:
// If Secretkey was previously created for that keyName, then grab and return it.
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
keyStore.load(null) // Keystore must be loaded before it can be accessed
keyStore.getKey(keyName, null)?.let { return it as SecretKey }
// if you reach here, then a new SecretKey must be generated for that keyName
val paramsBuilder = KeyGenParameterSpec.Builder(
keyName,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
paramsBuilder.apply {
setBlockModes(ENCRYPTION_BLOCK_MODE) // KeyProperties.BLOCK_MODE_GCM
setEncryptionPaddings(ENCRYPTION_PADDING) // KeyProperties.ENCRYPTION_PADDING_NONE
setKeySize(KEY_SIZE) // 256
setUserAuthenticationRequired(true) // This is required for BiometricPrompt to work properly
}
val keyGenParams = paramsBuilder.build()
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
keyGenerator.init(keyGenParams)
return keyGenerator.generateKey()
}

How do I require user authentication only for decryption but not encryption

I have a public/private keypair in AndroidKeyStore which I generated as follows:
val spec = KeyGenParameterSpec.Builder(alias(username), KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT)
.setKeySize(keySize)
.setUserAuthenticationRequired(true)
.setBlockModes(ablockMode)
.setEncryptionPaddings(apaddingMode)
.setCertificateSubject(X500Principal("CN=Itsami Mario, OU=Adventure Unit, O=Plumber Bros, C=US"))
.setKeyValidityStart(Date())
.setKeyValidityEnd(Date(Date().time + 1000 * 60 * 60 * 24 * 7))
.setCertificateSerialNumber(BigInteger(64, SecureRandom()))
.setDigests(digest)
.build()
keyPairGen.initialize(spec)
return keyPairGen.genKeyPair()
I want to require biometric authentication every time the private key is used, but I don't want to require a biometric prompt when encrypting with the public key. However, when use I use setUserAuthenticationRequired(true) in the KeyGeneratior and then I try to encrypt without first showing the BiometricPrompt, I get an android.security.KeyStoreException with the message: Key user not authenticated
How can I require authentication for decryption but not encryption?
You must be testing on a device running Android 6, Marshmallow. This is a known issue in that release, which was fixed in Android 7.
To work around the problem, you can extract the encoding of the public key and create a new PublicKey object from it, like so:
PublicKey publicKey = keyPair.getPublicKey();
PublicKey unrestrictedPublicKey =
KeyFactory.getInstance(publicKey.getAlgorithm()).generatePublic(
new X509EncodedKeySpec(publicKey.getEncoded()));
This will work on all versions.
Note that it's also possible to create AES keys that require authentication when decrypting but not when encrypting, which is rather cool (AES is much, much faster than RSA). The trick is to generate the key outside of AndroidKeyStore and then import it twice, once with PURPOSE_ENCRYPT and once with PURPOSE_DECRYPT, under two different aliases, and specifying user authentication requirements on the DECRYPT version. Something like:
// Note that we do *not* specify "AndroidKeyStore" when we call getInstance()
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
SecretKey secretKey = keyGen.generateKey();
// This time we do specify "AndroidKeyStore".
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
// Now we import the encryption key, with no authentication requirements.
keyStore.setEntry(
"encrypt_key",
new KeyStore.SecretKeyEntry(secretKey),
new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
.setBlockMode(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
// And the decryption key, this time requiring user authentication.
keyStore.setEntry(
"decrypt_key",
new KeyStore.SecretKeyEntry(secretKey),
new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
.setBlockMode(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthentication(true)
.build());
Now, you can encrypt with the key alias "encrypt_key" at any time, no user authentication required, and you can decrypt with the key alias "decrypt_key", but only when you do the BiometricPrompt thing.
The downside of this is that the secret exists briefly in non-secure memory. In practice, this matters only if an attacker has already compromised the device when the key is created, and in that case you have most likely already lost.

How to employ keyed-hash message authentication code (HMAC) with Android Keystore

I am investigating the use of the Android KeyStore for Marshmallow and above.
I would like to simultaneously verify both the data integrity and the authentication of my data by employing HMAC's.
How do I go about achieving this?
I am current generating an Encrypt/Decrypt key as follows:-
mKeyStore = KeyStore.getInstance(keyStoreName);
mKeyStore.load(mKeyStoreLoadStoreParameter);
if (mKeyStore.containsAlias(keyStoreAlias)) {
mSecretKey = (SecretKey) mKeyStore.getKey(keyStoreAlias, KEY_STORE_PASSWORD);
} else {
final KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, keyStoreName);
final int keyPurpose = KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT;
keyGenerator.init(
new KeyGenParameterSpec.Builder(keyStoreAlias, keyPurpose)
.setKeySize(KEY_STORE_KEY_SIZE)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setRandomizedEncryptionRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
mSecretKey = keyGenerator.generateKey();
I have found this sample for generating HMAC's
SecretKey key = ...; // HMAC key of algorithm "HmacSHA512".
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
keyStore.setEntry(
"key1",
new KeyStore.SecretKeyEntry(key),
new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN).build());
// Key imported, obtain a reference to it.
SecretKey keyStoreKey = (SecretKey) keyStore.getKey("key1", null);
// The original key can now be discarded.
Mac mac = Mac.getInstance("HmacSHA512");
mac.init(keyStoreKey);
However, how do I use this when encrypting/decrypting my data?
EXPLANATION
I have a number of choices/decisions to make when implementing security/cryptography within any Android application.
1). Do I implement cryptography of any sort Yes or No?
2). If Yes then... I should attempt to achieve the "most" secure solution possible.
If I am going to employ cryptography then I need to ensure the following.
a). I store passwords/secret keys in a "Safe Place" e.g. Android Key Store.
b). I use the "strongest" cryptography available.
c). I would like to simultaneously verify both the data integrity and the authentication of my data, e.g. I would like to detect if my encrypted data has been tampered with.
As I understand what I have read about HMAC's, they provide this functionality. I would like to know how I code the use of HMAC's into my Android application to ensure both the data integrity and the authentication of my data.
You can apply HMAC to the plain text HMAC(plain text) before encrypting and recompute the HMAC after decrypting to check that the original message is the same.
It may be redundant because if the cipher text is altered you will not be able to decrypt it.
First generate a HMAC key inside AndroidKeyStore. I found an example here
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_HMAC_SHA256, "AndroidKeyStore");
keyGenerator.initialize(
new KeyGenParameterSpec.Builder(hmacKeyAlias, KeyProperties.PURPOSE_SIGN).build());
SecretKey key = keyGenerator.generateKey();
Then Apply HMAC to the original data and store the result somewhere
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key);
byte hmacOriginalData[] = mac.doFinal(dataToEncrypt);
//Store hmacOriginalData
After decrypting, get HMAC key from AndroidKeyStore, recompute HMAC and check both macs are equal
Key key = keyStore.getKey(hmacKeyAlias, null);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key);
byte hmacDecryptedData[] = mac.doFinal(decryptedData);
//Check equals(hmacDecryptedData, hmacOriginalData);

How Do I Protect Decryption Password And Salt in APK

I am creating a product whose firmware is updated using an android phone. The android application automatically downloads an encrypted version of the firmware, decrypts it, and sends it to the devices boot-loader. In order to generate the same secret key I specificy the password and salt in the code. I'm worried the apk will be decompiled and someone will be able to decrypt our firmware.
Is there a better way to decrypt/encrypt files or protect the code?
Code:
private byte[] DecryptFile(byte[] encryptedFileBuffer) {
final int iterationCount = 10;
byte[] dataDecrypted = null;
SecretKey secKey = null;
try {
byte[] salt = "salt1234".getBytes();
String accessThingy = "Password";
KeySpec keySpec = new PBEKeySpec(accessThingy.toCharArray(), salt, iterationCount);
secKey = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec);
AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);
Cipher desCipher;
// Create the cipher
desCipher = Cipher.getInstance(secKey.getAlgorithm());
desCipher.init(Cipher.DECRYPT_MODE, secKey,paramSpec);
dataDecrypted = desCipher.doFinal(encrptedFileBuffer);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
return dataDecrypted;
}
Yes and no.
No, if the decryption routine can be executed by an attacker (and why wouldn't it be) then the firmware would be compromised. The only way to avoid this is to add protection to the key on the device. You can think of OS/hardware support for this, or about storing the key/password outside of the device for instance. But a single compromised device would leak the firmware. This is the DRM conundrum.
And yes as you seem to use PBKDF1, MD5 and DES, none of which is particularly safe. MD5 is the most broken algorithm in that list, but it is the one that is least likely to actually become a problem. You should be using PBKDF2, SHA-2 and AES instead. Try this answer, Java 8 also has added support for PBKDF2 with SHA-2. Or you could actually use a fully random key instead of using password based encryption (PBE).
You may also want to consider asymmetric primitives (ECDSA/RSA) for encryption and code signing.
Would it be possible to move the decryption to the device itself? This way the code would be less accessible to the end user assuming that there wasn't any way to read back the program from the device (which would also cause an issue here).

Categories

Resources