I use the following code to generate an AES key:
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder("db_enc_key", KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT);
KeyGenParameterSpec keySpec = builder
.setKeySize(256)
.setBlockModes("CBC")
.setEncryptionPaddings("PKCS7Padding")
.setRandomizedEncryptionRequired(true)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(5 * 60)
.build();
KeyGenerator keyGen = KeyGenerator.getInstance("AES", "AndroidKeyStore");
keyGen.init(keySpec);
SecretKey sk = keyGen.generateKey();
but everytime I try to get the byte[] version of the key via sk.getEncoded(), the method returns null. The documentation says that it should return the encoded key, or null if the key does not support encoding, but I don't think that the key doesn't support encoding.
I need the byte[] because I want to encrypt a realm database (for which I need to combine 2 AES-256 keys as byte-arrays) [https://realm.io/docs/java/latest/#encryption]
The official documentation uses SecureRandom, but also states that this is a silly way of doing this and that the key is never stored. Therefore, I wanted to use the KeyStore to securely store the two separate AES-256 keys.
P.S.: The code is only a test code and not the final product, so any comment on coding style is useless. I'm currently just trying to get a working version going.
edit: So I tried the following code, which successfully generates an AES key (though only 16 bytes of length):
SecretKey sk1 = KeyGenerator.getInstance("AES").generateKey();
When I use the getEncoded() method on it, I'll even get the byte array, so naturally I went on and saved it to the KeyStore with the following code:
KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(sk1);
KeyStore.ProtectionParameter pp = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT).build();
keyStore.setEntry("db_enc_key_test", entry, pp);
Which also works. So I tried to read the key from the keystore via KeyStore.Entry entry2 = keyStore.getEntry("db_enc_key_test", null); which worked as well. But when I call entry2.getEncoded() the method returns null again. Is this a keystore problem?
edit2: So I just found out, that symmetric keys generated in (and apparently saved to) the keystore are unexportable in Android M, which seems to be intended, which puts me in a bit of a problem, as I need the key itself to encrypt the realm database.
Some realm-developer here to recommend a best-practice?
The fact that you cannot retrieve the encoded key is by design as the Keystore should be the only one knowing it. However you can use a double layered key:
Generate a random key and store it in the Keystore.
Generate the "real" key used by Realm and encrypt it using the key from the Keystore.
Now you have some completely random text that can be stored in e.g SharedPreferences or in a file on disk.
Whenever people wants to open the Realm, read the encrypted key on disk, decrypt it using the Keystore and now you can use it to open the Realm.
This repo here uses the same technique to save User data in a secure way: https://github.com/realm/realm-android-user-store
This is probably the class you are after: https://github.com/realm/realm-android-user-store/blob/master/app/src/main/java/io/realm/android/CipherClient.java It also handle fallback through the various Android versions (the Keystore has quite a few quirks).
Related
I want to know how to securely store encryption key in Android? What is the best scenario to protect encryption and secrete keys?
From your comments, you need to encrypt data using a local key for current Android versions and the old ones
Android Keystore is designed to generate and protect your keys. But it is not available for API level below 18 and it has some limitations until API level 23.
You will need a random symmetric encryption key, for example AES. The AES key is used to encrypt and decrypt you data. I'm going to summarize your options to generate and store it safely depending on Android API level.
API Level < 18: Android Keystore not present. Request a password to the user, derive an encryption key from the password, The drawback is that you need to prompt for the password when application starts. The encryption key it is not stored in the device. It is calculated each time when the application is started using the password
API Level >=18 <23: Android Keystore available without AES support. Generate a random AES key using the default cryptographic provider (not using AndroidKeystore). Generate a RSA key pair into Android Keystore, and encrypt the AES key using RSA public key. Store encrypted AES key into Android SharedPreferences. When application starts, decrypt the AES key using RSA private key
API Level >=23: Android Keystore available with AES support. Generate a random AES key using into Android Keystore. You can use it directly.
To encrypt to can use AES/CBC/PKCS7Padding algorithm. It requires also a random initialization vector (IV) to encrypt your data, but it can be public.
Alternatives:
API level >14: Android Key Chain: KeyChain is a system-wide credential storage. You can install certificates with private keys that can be used by applications. Use a preinstalled key to encrypt/decrypt your AES key as shown in the second case above.
External token: The protected keys are not stored in the device. You can use an external token containing a private/public key pair that allows you to encrypt the AES key. The token can be accesed using bluetooth or NFC
You cannot place the encryption key inside your apk file. You may want to keep it in a remote server and decrypt using server. Or you may make it difficult for others by encoding the key and keeping it in non-obvious places. But there's no bullet proof solution for this.
It sounds like you want EncryptedSharedPreferences or EncryptedFile. Both of these use the AndroidKeyStore. The code snippets below actually answer the question "How do I use the AndroidKeystore to encrypt a file or store a cryptographic key?"
Make sure to include implementation "androidx.security:security-crypto:1.0.0-rc02" in your app build.gradle file.
from EncryptedSharedPreferences:
An implementation of SharedPreferences that encrypts keys and values.
String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
"secret_shared_prefs",
masterKeyAlias,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
// use the shared preferences and editor as you normally would
SharedPreferences.Editor editor = sharedPreferences.edit();
You could store an encryption key like this:
// generate random symmetric key
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey symkey = keyGen.generateKey();
String alias = "your encryption key";
// store symmetric key
byte[] encodedSymmetricKey = symkey.getEncoded();
SharedPreferences.Editor edit = sharedPreferences.edit();
String base64EncodedSymmetricKey = new String(Base64.getEncoder().encode(encodedSymmetricKey));
edit.putString(alias, base64EncodedSymmetricKey);
edit.commit();
// retrieve symmetric key
String raw = sharedPreferences.getString(alias, null);
byte[] symKey = Base64.getDecoder().decode(raw);
SecretKeySpec spec = new SecretKeySpec(symKey, "AES");
assert(spec.equals(symkey));
// use your encryption key
Although it would be much better to use EncryptedFile.
from EncryptedFile:
Class used to create and read encrypted files.
String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
File file = new File(context.getFilesDir(), "secret_data");
EncryptedFile encryptedFile = EncryptedFile.Builder(
file,
context,
masterKeyAlias,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();
// write to the encrypted file
FileOutputStream encryptedOutputStream = encryptedFile.openFileOutput();
// read the encrypted file
FileInputStream encryptedInputStream = encryptedFile.openFileInput();
note:
The master key, once created, is constant. So even after a phone reboot your app will still be able to encrypt/decrypt the file.
There is no way to securely save your private api keys into code. But you can use
NDK to securely save private keys. It is not trivial to get key from NDK. Secue Key With NDK Example
You can use Android Keystore system to store and retrieve sensitive information.
Read this 5 minute article to understand how it works.
Using the Android Keystore system to store and retrieve sensitive information
I'm using java-aes-crypto for encrypting strings in android. After generating key using this code
AesCbcWithIntegrity.SecretKeys keys = AesCbcWithIntegrity.generateKey();
and I'm saving it as String(.toString()) in a file and after that I'm trying to retrieve it from that file and convert it to the same object. How can I do that?
Use the following static method in the AesCbcWithIntegrity class (link):
/**
* An aes key derived from a base64 encoded key. This does not generate the
* key. It's not random or a PBE key.
*
* #param keysStr a base64 encoded AES key / hmac key as base64(aesKey) : base64(hmacKey).
* #return an AES and HMAC key set suitable for other functions.
*/
public static SecretKeys keys(String keysStr) throws InvalidKeyException {...}
It properly parses the previously serialized keys into the encryption key and the integrity key.
On going through the link,one can see that you need to use the following code to convert the encrypted String into the desired one.
//Use the constructor to re-create the CipherTextIvMac class from the string:
CipherTextIvMac cipherTextIvMac = new CipherTextIvMac (cipherTextString);
String plainText = AesCbcWithIntegrity.decryptString(cipherTextIvMac, **keys**);
The keys is the same key that was used while encryption.
As you can see, you need to store the key to decrypt it. The link also provides the precaution to store the key
Once you've generated a random key, you naturally might want to store
it. This may work for some use cases, but please be aware that if you
store the key in the same place that you store the encrypted data,
your solution is not cryptographically sound since the attacker can
just get both the key and the encrypted text. Instead, you should use
either the Keystore infrastructure or consider generating the key from
a passphrase and using that to encrypt the user data.
If despite the above you still want to store the key, you can convert
the keys to a string using the included functions and store them in
preferences or SQLite.
I am just familiarizing myself with the Android Keystore API.
I found out that the following features are available:
At least on some devices the Android Keystore is hardware backed, meaning that crypto operations run in a secure environment (TEE).
When the keystore is hardware backed, private RSA keys as well as secret symmetric keys that have been created within the Keystore can
be configured to never leave the Keystore and the raw keys cannot be
read out even with root access.
I am wondering now if the following is possible:
Generate a Public/Private key pair where the private key never leaves the Keystore
Upload the public key of this pair to a server
On the server: create a random symmetric AES key and encrypt it with the public RSA key uploaded by the user
On the device: Download this encrypted AES key
Import it into the hardware backed Keystore such that it is decrypted in there with the private key of the pair and stored under a
new alias
Use this new key alias to perform symmetric encryption and decryption
1-4 should be possible, the missing link for me now is point 5. in this list. Can some one help me out and tell me if this is possible at all and/or point me to the correct API reference?
I found this:
https://android.googlesource.com/platform/development/+/master/samples/Vault/src/com/example/android/vault/SecretKeyWrapper.java
But it looks to me as if the unwrapping of the secret key happens in the normal environment and the decrypted AES key would be available in the App, which would not satisfy my security requirements.
Update:
I created a small test project using the linked SecretKeyWrapper and here are two code snippets:
The first one does the following:
Create a random AES key (not within in the keystore, this is what would happen on a server later). Obviously the raw key can be retrieved from the generated SecretKey object what isn't a problem since the server can know the key.
Encrypt/wrap the key with a RSA public key that was created in the client's Android Keystore (this would also happen on a server).
Decrypt the key again with the RSA private key (this would happen on the client and actually happens within the TEE in the example).
Snippet 1:
SecretKeyWrapper secretKeyWrapper = new SecretKeyWrapper(this,"testKeyRsa");
// Generate a random AES key (not in the keystore) [1]
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
SecretKey secretKeyGenerated = keyGen.generateKey();
byte[] secretKeyGeneratedRaw = secretKeyGenerated.getEncoded();
// wrap this key with the RSA key from the keystore [2]
byte[] wrappedKey = secretKeyWrapper.wrap(secretKeyGenerated);
// unwrap it again with the RSA key from the keystore [3]
SecretKey unwrappedKey = secretKeyWrapper.unwrap(wrappedKey);
// the raw key can be read again [4]
byte[] unwrappedKeyRaw = secretKeyGenerated.getEncoded();
What I want to achieve is that the unwrapped key from [3] is stored in the Keystore with a new alias without returning the raw key. Of course I could easily import the SecretKey object into the Keystore here, but the problem is, that at this point the raw key can be retrieved from the object with the statement [4] what induces a security flaw. It is clear that the unwrapping/decryption already happens in the Keystore/TEE, since the private RSA key that is used for the decryption lives in the Keystore and cannot be retrieved.
If I compare this to the situation where a random secret AES key is created in the keystore, I notice that different types (implementing the SecretKey Interface) are returned. In the above example, the type is SecretKeySpec, whereas for keys which are returned from the Android Keystore (see snippet 2 below), "opaque" types are used where the getEncoded() method always returns null. In the following example, the type of keyAesKeystore is AndroidKeyStoreSecretKey.
Snippet 2:
// create a new AES key in the keystore
KeyGenerator keyGenAndroid = KeyGenerator.getInstance("AES","AndroidKeyStore");
keyGenAndroid.init(
new KeyGenParameterSpec.Builder("testKeyAes",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
SecretKey keyAesKeystore = keyGenAndroid.generateKey();
// this returns null
byte[] keyAesKeystoreRaw = keyAesKeystore.getEncoded();
So to rephrase the question: Is it somehow possible to securely import a RSA wrapped AES key into the Android Keystore without revealing the secret key to the application?
Update 2:
#Robert makes the absolutely valid in the answer below that it actually does not matter if the unwrapping happens in the TEE or in the Rich OS (App) since the App (or a tampered version) could always later (after intercepting the wrapped key) just "use" the private RSA key from the Keystore to unwrap the AES key (without the need to access the raw private key at all).
Here is another thought though:
I found that it is possible to set Key Protection Parameters for keys in the Android Keystore (see here).
The linked implementation for the SecretKeyWrapper does not use such protection parameters. After changing the generateKeyPair method as follows and adding the PURPOSE_DECRYPT and PURPOSE_ENCRYPT properties everything still works.
private static void generateKeyPair(Context context, String alias)
throws GeneralSecurityException {
final Calendar start = new GregorianCalendar();
final Calendar end = new GregorianCalendar();
end.add(Calendar.YEAR, 100);
final KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.build();
final KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
gen.initialize(keyGenParameterSpec);
gen.generateKeyPair();
}
I can now protect the RSA key such that it cannot be used for decryption by removing the PURPOSE_DECRYPT property. As expected the Cipher.unwrap method stops working and throws an Incompatible purpose exception then.
So what I would need then is a protection property where the plain decrypt functionality is blocked but which allows such a "secure import functionality" I am looking for. Something like a "PURPOSE_IMPORT" which apparently does not exist.
What you're looking for now exists, as of API level 28 (Android Pie). To use you you need to:
Create a wrapping key pair, an RSA key pair with purpose PURPOSE_WRAP_KEY. You should also generate an attestation for the public key to verify that the private key is a keystore key, in secure hardware.
Send the public key (and attestation) from your app to the server that will provide the wrapped symmetric key.
On the server, you need to wrap the symmetric key. This involves more than just encrypting it, because the wrapper needs to contain not just the key material but also the authorization list that defines how the key may be used. This is done by packaging the key and authorization info in an ASN.1 DER-encoded structure, per the schema documented here. There's some sample wrapping code in the CTS test. Note that if this format seems excessively complicated (e.g. the optional "masking key"), it's because in a future Android release there will be a corresponding secure export function and the use cases for that require the additional complexity. The secure export function didn't make it into Q, but will probably make it into R.
Send the wrapped key to the app, which must create a WrappedKeyEntry and use Keystore.setEntry() to store it.
This should work on any device with API level 28. However, if the device has a Keymaster version < 4 (see the attestation certificate to find out what version of Keymaster is present), then the unwrapping operation will return the wrapped key material to Android userspace. Keymaster version 4 (or above) will keep the unwrapped material in secure hardware, but because lower versions don't have support for the wrapped key feature, it has to be sort of emulated.
What happens if you have a lower Keymaster version is that when you create a PURPOSE_WRAP_KEY key pair, what is actually requested of the secure hardware is a PURPOSE_DECRYPT key pair. Then when you do the import, the keystore daemon uses this PURPOSE_DECRYPT private key to decrypt the secret from the wrapper, then it imports the secret into the secure hardware and wipes the userspace memory that held it. So, the key material exists in the keystore daemon's memory for a fraction of a millisecond. Again, if the device has Keymaster version 4+ it is only unwrapped inside the secure hardware and never leaves.
What you want to achieve is not possible by simply using the AndroidKeystore. What you need is custom code that runs within the TEE.
The reason for this is simple: When you have set-up your app with an asymmetric key-pair stored in the AndroidKeystore and you receive the wrapped AES key it does not matter if the unwrapping takes place inside or outside the AndroidKeystore:
All keys of an app stored in the AndroidKeystore are usable by the app in the normal environment. Is is by design as you wouldn't be able to use them otherwise.
Hence if the asymmetric key-pair is usable by the app the app is always able to unwrap the received wrapped AES key (using code in the normal environment). Therefore it does not make any difference where the unwrapping takes place. You can not guarantee that someone had copied the wrapped AES key when the app received it and then unwrapped it using the asymmetric key from the AndroidKeystore.
Assume I have the following Java code to generate a Public-private keypair:
KeyPairGenerator generator = KeyPairGenerator.getInstance ("RSA");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
generator.initialize (1024, random);
KeyPair pair = generator.generateKeyPair();
RSAPrivateKey priv = (RSAPrivateKey)pair.getPrivate();
RSAPublicKey pub = (RSAPublicKey)pair.getPublic();
// Sign a message
Signature dsa = Signature.getInstance("SHA1withRSA");
dsa.initSign (priv);
dsa.update ("Hello, World".getBytes(), 0, "Hello, World".length());
byte[] out = dsa.sign();
/* save the signature in a file */
FileOutputStream sigfos = new FileOutputStream("sig");
sigfos.write(out);
sigfos.close();
How would one go about and decrypt the file "sig" in PHP? I've read the post: https://stackoverflow.com/a/1662887/414414 which supplies a function to convert a DER file to PEM (Assume I also save the public key from Java).
I have tried something like:
$key = openssl_pkey_get_public ("file://pub_key.pem");
$data = null;
openssl_public_decrypt ( file_get_contents ("sig"), $data, $key);
echo $data, "\n";
It successfully decrypts the message, but it is many weird characters.
Our scenario is a Java client that is sending messages to a PHP server, but encrypts the data with a private key. PHP knows about the public key, which it should use to decrypt and validate the message.
I've read a lot of posts regarding this issue here on SO, but I've come to realize that this is a bit specific issue, especially if there's different algorithms in use, etc. So sorry if this may be a duplicate.
Any feedbacks are greatly appreciated!
an "RSA signature" is usually more than just "encrypt with private key, decrypt with public key", since Public key protocols like PKCS#1 also specify padding schemes, and all signature schemes will encrypt a digest of the message, instead of the full message. I cannot find any documentation if java's signature scheme uses the signature padding scheme specified in PKCS#1, but my suspicion is that it is.
If it is, you will instead want to use the openssl_verify method in PHP, documented here. This will return a 0 or 1 if the signature is invalid or valid, respectively.
In the event that Java does not use a padding scheme, your issue is that the data encrypted in the signature is a hash of the message, instead of the message itself (you can see in the Java code that it uses the SHA-1 hash algorithm). So on the PHP side, you will need to take the sha1 hash of your message using the sha1 method with $raw_output set to true, and compare those strings to ensure your message is valid.
From the snippet
$key = openssl_pkey_get_public ("file://pub_key.pem");
It looks like you're referencing the public key, which would be the wrong one to decrypt. Double check ?
I am trying to implement a PKI verification scheme, where a message string is signed with a private key on server, the signature is stored on the client along with the message string. The client then verifies the signature using a public key.
The restrictions of my environment are, the server is Google App Engine and the client is a Java program. I have played with Java-only and Python-only solutions of PKI verification and got them to work, however when doing one operation in Python and another in Java is posing problem, mainly due to Key file format restrictions and my limited understanding of cryptography terminology.
One of the biggest limitations is crypto support in GAE. The only library supported is PyCrypto and this library can't read public/private keys stored in PEM, DER or X509 formats. As far as I could find, only M2Crypto supports reading from these files, but it can't be used inside GAE because it's a wrapper around openssl, so not a pure python solution. Even if I could find a way to translate the public/private keys from PEM/DER/X509 to the format that PyCrypto understands, that will work for me. But I couldn't find any way to do it. Any ideas there?
I found one possible solution in the form of tlslite. tlslite could read a private key from PEM file and create a signature. Here is the code.
from tlslite.utils.cryptomath import bytesToBase64
from tlslite.utils.keyfactory import parsePEMKey
s = open('private.pem').read()
key = parsePEMKey(s)
doc = 'Sample text'
bytes = array('B')
bytes.fromstring(doc)
print bytesToBase64(key.sign(bytes))
The corresponding Java code I used to verify the signature is.
String signAlgo = "SHA1WithRSAEncryption";
// read public key from public.der
byte[] encodedKey = new byte[294]; // shortcut hardcoding
getAssets().open("public.der").read(encodedKey);
// create public key object
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedKey);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pk = kf.generatePublic(publicKeySpec);
// read signature (created by python code above)
byte[] encodedSig = new byte[345];
getAssets().open("signature.txt").read(encodedSig);
byte[] decodedSig = Base64.decodeBase64(encodedSig);
// Do verification
Signature verifyalg = Signature.getInstance(signAlgo);
verifyalg.initVerify(pk);
verifyalg.update(message.getBytes());
Log.d(TAG, "Verif : "+verifyalg.verify(decodedSig));
The verification fails.
I suspected if the tlslite is using different algorithm for signature creation than what the java code expects.
So I tried to find that out.
On python side
print key.getSigningAlgorithm()
gave me
pkcs1-sha1
on Java side, I tried to find all supported algorithms with this code:
Set<String> algos = java.security.Security.getAlgorithms("Signature");
for(String algo : algos) {
Log.d(TAG, algo);
}
That gave me
MD4WithRSAEncryption
RSASSA-PSS
SHA1withDSA
SHA1withRSA/ISO9796-2
1.2.840.113549.1.1.10
SHA512withRSA/PSS
MD5withRSA/ISO9796-2
DSA
SHA512WithRSAEncryption
SHA224withRSA/PSS
NONEWITHDSA
SHA256withRSA/PSS
SHA224WithRSAEncryption
SHA256WithRSAEncryption
SHA1withRSA/PSS
SHA1WithRSAEncryption
SHA384withRSA/PSS
SHA384WithRSAEncryption
MD5WithRSAEncryption
I tried all the SHA1 values on the Java side. But none helped to verify the signature generated by tlslite with pkcs1-sha1 algo. Any idea about this mapping?
These are different operations. In Python, you need to use hashAndSign. The default happens to be SHA1 hash.
Keyczar should work fine on App Engine, and is available in both Java and Python flavours.