I have access token from the server after authentication lets say "uyhjjfjfgg567f8fhjkkf" now I want to save it in the device securely. I looked in Keystore and Keychain in android developer sites. I dont clearly understand how it works and how we should retrieve the token from the keystore.
KeyPairGenerator kpg = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
kpg.initialize(new KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setDigests(KeyProperties.DIGEST_SHA256,
KeyProperties.DIGEST_SHA512)
.build());
KeyPair kp = kpg.generateKeyPair();
/*
* Load the Android KeyStore instance using the the
* "AndroidKeyStore" provider to list out what entries are
* currently stored.
*/
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
Enumeration<String> aliases = ks.aliases();
You don't need to save the access token, since it has short life anyway. Keeping it in memory is good enough.
You do need to keep the refresh token, and you have a few options for that:
In a file
Either directly in a file in the internal storage
or using SharedPreferences
or in a Database
Using the AccountManager
Consider using the StoredCredential. For the flow itself, I recommend you to use Google AppAuth library.
Of course, you can also encrypt the key using a cipher:
private static byte[] encrypt(byte[] key, byte[] text) throws GeneralSecurityException {
final SecretKeySpec skeySpec = new SecretKeySpec(key, KEY_ALGORITHM);
final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, sInitVectorSpec);
return cipher.doFinal(text);
}
And the key can be stored in the KeyStore.
We use a custom SharedPreference instance that encrypts the keys and values when adding, and decrypts when requesting.
SecurePreferences preferences = ...
preferences.edit().putString( "key", "value" ).apply(); // key and value are encrypted automatically
String value = preferences.getString( "key", null ); // key and value are decrypted automatically
I would only recommend using SharedPreferences if the values are encrypted, because even though the xml file is only available to the app, it can be accessed on rooted devices.
If you already using a SqlLiteDB, I would probably use that. If not, it's bit heavy for just saving a token.
EDIT:
An oauth token is completely unrelated to the key and keystore used to sign the app.
The oauth token is a token provided by the server after validating the user's credentials, within the app.
The keystore contains 1 or more certificates that is used to digitally sign the app. This is to prevent someone else from uploading an app that has the same package name as yours and replacing it.
Related
I want to encrypt the data I store in Firestore database. Let's say you encrypt data with a 256 bit AES key derived from a user password and you encrypt the AES key using a single Google KMS key.
Is this enough? Or do we need to use somesort of private-public cyptorgraphic library like RNCryptor locally.
In my case all my app data is stored in Firestore and have supporting cloud functions (node.js). A good chunk of user data needs to be encrypted.
Every user will have a unique AES key generated this way below on login. The key would be generated on their first login -- or registration, and the AES key post encryption using the KMS key, will be saved in Firestore itself. When decryption is needed, the KMS key will be used to decrypt the AES key, then use it.
String password = "password";
int iterationCount = 1000;
int keyLength = 256;
int saltLength = keyLength / 8; // same size as key output
SecureRandom random = new SecureRandom();
byte[] salt = new byte[saltLength];
randomb.nextBytes(salt);
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
iterationCount, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
SecretKey key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[cipher.getBlockSize());
random.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes("UTF-8"));
(code reference: "Using Password-based Encryption on Android
" article)
Just one thing there is no concept of a end-user-side app, and service provider app in my use case. There is just one app, and firestore backend, and server-side cloud functions powered by node.js. I am trying to see if I can produce a strong system free from hacks, to store encrypted stuff (prevent admins from viewing the same also using console dashboard -- for private data). I will be ensure the Google KMS key can't be accessed directly but it will saved in another user account.
"The KMS key belongs to a different Google account to the Firebase database, so no one user (e.g. me) has permission to both read the data AND decrypt it. A hacker would need to compromise both accounts to access the unencrypted data."
Strategy Reference HOW TO ENCRYPT A GOOGLE FIREBASE REALTIME DATABASE
Official response I got from Google Firebase support team:
Currently, custom access to Firebase (e.g. access to Firestore, RTDB)
is not yet supported, but a highly requested feature. Also, as you may
know, Firestore client SDKs do not have encrypting capabilities. So
additional encryption done won’t hurt (I cannot suggest that much on
this part). To add, having a strict security rules is a must.
You don't have to do it generally because it's encrypted automatically.
https://cloud.google.com/firestore/docs/server-side-encryption
It's free from cracks already!
I am generating an Asymmetric key pair in the Android key store as below:
I have used the public key for symmetric key wrapping and storing the wrapped key to a file. When I try to unwrap symmetric key using the private key, I am able to do so within that instance. Once my application is re-installed, I am unable to get the key store entry with the alias.
KeyPairGenerator kpg = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
"AndroidKeyStore"
);
kpg.initialize(new KeyGenParameterSpec.Builder(
Constants.KEY_STORE_ALIAS_NAME,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
)
.setKeySize(Constants.ASYMMETRIC_KEY_LENGTH)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.build()
);
keyPair = kpg.generateKeyPair();
// Code for accessing the key store entry to un wrap the symmetric key
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(Constants.KEY_STORE_ALIAS_NAME, null);
PrivateKey privateKey = ((KeyStore.PrivateKeyEntry) entry).getPrivateKey();
Keys stored in Android Keystore are non-extractable. It is a security measure
Security Features
Android Keystore system protects key material from unauthorized use. Firstly, Android Keystore mitigates unauthorized use of key material outside of the Android device by preventing extraction of the key material from application processes and from the Android device as a whole. Secondly, Android KeyStore mitigates unauthorized use of key material on the Android device by making apps specify authorized uses of their keys and then enforcing these restrictions outside of the apps' processes
This means that the keys can not be part of the Android backup service in any way. It allows to store application data on the cloud once the application is uninstalled. See HowBackupWorks.
It would be a serious security risk that private keys could be extracted and stored in cloud or even that they remain stored in the device when the application has been uninstalled
If you need to use an encryption key that does not depend on the reinstallation, you could generate a symmetric key from a user passphrase using a key derivation algorithm
I am trying to setup an encrypted default realm instance in my app.
The idea is to generate a key using a KeyPairGenerator with a given alias, store it in the AndroidKeyStore and use said key every time it is needed.
WHAT I DO
This is how i generate the key:
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
if (!ks.containsAlias(KEY_ALIAS)) {
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 99);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(this)
.setAlias(KEY_ALIAS)
.setSubject(new X500Principal("CN=Example, O=ExampleOrg"))
.setSerialNumber(BigInteger.ONE)
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.build();
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
generator.initialize(spec);
KeyPair keyPair = generator.generateKeyPair();
}
I am using the KeyPairGenerator as i need to support api versions 18 and up.
Here is how i setup my default realm instance in my Application:
RealmConfiguration config = null;
try {
config = new RealmConfiguration
.Builder(this)
.encryptionKey(ks.getKey(KEY_ALIAS, null).getEncoded())
.name("dealmatrix.realm")
.schemaVersion(1)
.build();
where ks is a Keystore instance acquired like so:
Keystore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
WHAT GOES WRONG
My problem is that this expression:
ks.getKey(KEY_ALIAS, null).getEncoded()
returns null, which understandably leads to an exception.
I have read online that this is the intended behaviour of the KeyStore system.
If indeed i am unable to get the stored encryption key's byte array, how am I supposed to encrypt my realm using said key?
Are there any other methods to securely store an encryption key so that i may use it in my realm configuration?
There is a WIP example project in feature/example/store_password branch in Realm repository which uses Android keystore.
https://github.com/realm/realm-java/tree/feature/example/store_password/examples/StoreEncryptionPassword
Core logic is written in Store.java
We need some more works(cleanup, adding comments, supporting old devices) before releasing this example project. But I think this project helps you.
Android Keystore keys returning null from getEncoded is working as intended. getEncoded is supposed to return the private key's key material (usually in PKCS#8 DER-encoded format) or null if key material export is not supported. Android Keystore by design does not reveal/export key material of private or secret keys and thus getEncoded returns null. See https://developer.android.com/training/articles/keystore.html#SecurityFeatures.
You can still use these keys just fine with Signature and Cipher abstractions.
The Android Keystore prohibits extraction of private keys from it. So the design would be to generate a Realm key outside of the Android Keystore, so that you can use it for the encryption/decryption of the Realm database.
But to safely store that Realm key, you would use the power of the Android Keystore, by encrypting the Realm key with the Android Keystore and then store it locally (e.g. Shared Preferences). Later you could read that encrypted Realm key, decrypt it with the Android Keystore and use it again to unlock your Realm database.
In my app, I am saving some data in shared preference which has to be encrypted before saving and has to be decrypted when retrieving.
I am using AES-256 encryption. For that, I am generating the secret key using a passphrase/pin. Below is my code snippet.
public static SecretKey generateKey(char[] passphraseOrPin, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
// Number of PBKDF2 hardening rounds to use. Larger values increase
// computation time. You should select a value that causes computation
// to take >100ms.
final int iterations = 1000;
// Generate a 256-bit key
final int outputKeyLength = 256;
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength);
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
return secretKey;
}
As per my app, I can ask the user to provide a unique pin. But I am not able to save the pin in keystore, because the app has to support from 4.0. How can I save the pin?
For this, I would recommend you look into using Facebook Conceal library. It is compatible with android up to API 9 as per their build.gradle: https://github.com/facebook/conceal/blob/master/build.gradle
You can follow their guidance on the website here on how to integrate:
https://github.com/facebook/conceal
Use the KeyChain instead. It is compatible down to API 14 (ICS). The main difference being that the KeyChain is available systemwide.
However, I wonder, why are you storing the pin? Keys protected by passwords exist to prevent unauthorized use, so usage should be authorized by the user by entering his/her password.
I am currently working on android application which is based on Client-server architecture. For data security, I am using Public-Private key pair for data encryption and signing. I am using AndroidKeyStore for storing key pair. Below is the code to generate key pair:
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(
mContext)
.setAlias(mPrivateKeyAlias)
.setSubject(new X500Principal("CN=" + mPrivateKeyAlias))
.setSerialNumber(
BigInteger.valueOf(System.currentTimeMillis()))
.setStartDate(start.getTime())
.setEndDate(end.getTime()).setKeySize(2048).build();
KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance(
"RSA",
"AndroidKeyStore");
kpGenerator.initialize(spec);
// Key Pair will be saved in AndroidKeyStore
KeyPair pair = kpGenerator.generateKeyPair();
After executing this code, Keystore releated files (CERT and PKEY files) will be generated at '/data/misc/keystore/user_0/' directory.
I am encrypting application sensitive data like auth-token and saving it to Shared Pref for security reasons.
But now when user changes device password or pin, keystore files are getting deleted as Masterkey used for keystore encryption is generated using device credentials.
Now to fix this issue, I tried to keep Public-Private key pair in RAM and when password gets changed. From onPasswordChanged(Context context, Intent intent) method of DeviceAdminReceiver, I am executing below code :
KeyStore keyStore = KeyStore
.getInstance("AndroidKeyStore");
keyStore.load(null);
keyStore.setKeyEntry(mPrivateKeyAlias, mPrivateKey.getPrivateKey(),
null, new Certificate[] { mPrivateKey.getCertificate() });
But, after this code only CERT file gets created at '/data/misc/keystore/user_0/' directory and while decryption using private key, giving some invalid signature error.
Also, I have shared my public key with server, encrypted data with private key, so creating new key pair would not be better solution.
So, how I can retain my public private key pair after device password change ? If there is no work around, what is the exact use of AndroidKeyStore? Where can I use it ?
This issue has been fixed by Google in Android 5.0 (Lollipop) release. But, for previous versions of Android, you will have to live with this issue. :(