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!
Related
We're using Cipher and CipherInputStream in an Android app to decrypt files downloaded from a server.
For some reason, all the calls to Cipher.update return an empty block and the call to Cipher.doFinal returns the entire file in one block.
This causes OOM on large files.
This is the code we use to initialize the cipher:
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(keyData, "AES");
GCMParameterSpec nonce = new GCMParameterSpec(128, nonceData);
cipher.init(Cipher.DECRYPT_MODE, key, nonce);
Why is this happening?
Is this something that can be fixed on the client side?
Note: I don't currently have access to the server's code. When I have I will post it too.
Note 2: This happens on Android API 25.0.1
Good question! It's because you're using GCM mode. The authentication tag can't be checked until all of the data has been received from the server, so Java automatically buffers this data, checks it, and then gives you the final data once the tag has been checked.
GCM mode is excellent for relatively small message sizes or files, but large files shouldn't be encrypted with GCM mode.
You might prefer to use CBC mode with an HMAC, stream the file to disk, validate the HMAC and then decrypt. It's more roundabout, but avoids the issue you're currently having.
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.
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’m implementing a “use fingerprint instead of password” feature for devices with nexus imprint.
It’s fairly easy to prompt for a fingerprint and see if was correct or not but I’m scratching my head trying to protect a value with fingerprint, I’m following this example https://github.com/googlesamples/android-FingerprintDialog but there is no “recover value with fingerprint”, it only explains how to store it
any good example of something like that?
The API expects a password so the general idea is:
user activates "protect with fingerprint"
is asked for the password
if correct, is asked for fingerprint
if correct, password is securely stored
for any subsequent logins, when touching the fingerprint scanner, if correct, the value is decrypted and sent to server for login
Okay, found the way, just for the record, here are the steps:
Init Cypher for decryption:
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
Create the CryptoObject with Cypher:
CryptoObject cryptoObject = new FingerprintManagerCompat.CryptoObject(cipher);
fingerprintManager.authenticate(cryptoObject, 0, cancellationSignal, callback, null);
Check the onAuthenticationSucceeded(AuthenticationResult) and get the value:
Cipher cipher = authenticationResult.getCryptoObject().getCipher();
byte[] encryptedBytes = cipher.doFinal("1234".getBytes("UTF-8"));
I'm making an app that will require a password to be entered before the main app will load. I plan to get the user to create a password when they first run the app and then store the password on the local device and encrypt it using a local symmetric key (which will be generated when the app first runs). This is so someone can't simply read the file where the password is stored.
How can I store the key used securely? Or is there a better way of hiding stored passwords to be used in local verification?
The app is designed for offline usage so I can't add any networking capabilities.
You can use SharedPreferences in private mode to store the password. It is secure as far as the phone is not rooted but you can use Cryptography techniques to store the password. The approach which I follow to store the passwords locally is to add a SALT to the password while storing.
You can read more about it here
A secure way for passwords - hashing. A hash can never be decrypted as the password is lost during the hashing process. I'm using MD5 hashing process in the following code -
public String StringToMD5(String s) {
try {
// Create MD5 Hash
MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
digest.update(s.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
for (int i=0; i<messageDigest.length; i++)
M hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
Use this code to hash your password and then store it using Private SharedPreferences. When the user enters the password again, hash it again and check if it is the same as previous hash. If the hashes match, then access is granted.
Please start by reading Thomas Pornin's canonical answer to How to securely hash passwords?.
PBKDF2 options are listed in the question PBKDF2 function in Android, but include a native SecretKeyFactory method as well as Spongycastle, bouncycastle, rtner.de, etc.
Long, cryptographically random per-password salt is required (make room for more than one password for future growth!).
Never ask PBKDF2 for more key length than the native function supports - that 64 bytes for PBKDF2-HMAC-SHA-512, and 20 bytes for PBKDF2-HMAC-SHA-1.
Always use as high an iteration count as your users can stand. Even for android devices, for a single user on their own device, done only once at application start, that's in the hundreds of thousands or more for PBKDF2-HMAC-SHA-1 and the tens to hundreds of thousands for PBKDF2-HMAC-SHA-512.
Note that PBKDF2's primary use is in creating encryption keys - you can use the same password entered to generate the encryption key for files you encrypt using AES (NOT in ECB mode); just use a different salt and a different number of iterations. If you're only doing that, then you don't even need the password hash; simply try decrypting the file with the key generated and a stored salt and number of iterations - if it works, it was the right password. If it fails, it wasn't.