Solution 1 :
How could an attacker retrieve the refresh token from shared preference and decrypt it?
How it is retrieved is unclear to me, I’m not sure how you can retrieve values from a shared preference in Android. Decryption is simple, the attacker brute forces the 4 digit PIN. No amount of delay or salt is going to help you with that.
Is the symmetric key inside secure element?
No, it’s in memory after the PBKDF2 operation.
How safe is this implementation against malware or root?
Not that secure, basically you hope that they won’t be interested enough to crack the PIN.
How easy can the key be brute forced? (except that user tries 10k times manually to insert the correct pin)
What do you mean except that? The only ways to retrieve the key are:
- to bruteforce PBKDF2 and try to decrypt (this is by far the most obvious);
- to somehow read out the memory of
SecretKeySpec
(harder); - to perform a side channel attack on the AES implementation that can leak the key (pretty tricky if you use a good Android implementation nowadays.
Basically you can only use a PIN if you can limit the amount of guessing operations. That doesn’t seem the case for an encrypted value.
By the way, a larger salt than 16 to 32 bytes is meaningless. Concatenating the salt, IV and ciphertext makes more sense before base 64 encoding (and returning an object with these values already encoded makes no sense at all). 12K operations seems like a lot, but computers are fast nowadays, you may want to make that value configurable & higher.
Problem :
Our Product Manager wants a 4 digit pin for login in our app, obviously for UX reasons, so user don’t have to remember their password each time when they login.
A refresh token can be retrieved from backend to obtain a session token, which have access to the API. On our app, we encrypt the refresh token with AES and PBKDF2. A random salt and IV are generated plus the 4 digit used as password for PBKDF2.
After the encryption, I store the salt, IV and the cipher text base64 encoded in private shared preference.
The encryption code looks like this:
const val CPR_TRANSFORMATION = "AES/CBC/PKCS7Padding"
const val ALGORITHM_TYPE = "PBKDF2WithHmacSHA1"
const val ITERATION_AMOUNT = 12000
const val KEY_SIZE = 256
private fun encrypt(passCode: String, data: ByteArray): Encrypted { //e.g.: passCode = "0000"
val salt = ByteArray(256)
SecureRandom().nextBytes(salt)
val iv = ByteArray(16)
SecureRandom().nextBytes(iv)
val cipher = Cipher.getInstance(CPR_TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(passCode, salt), IvParameterSpec(iv))
val raw = cipher.doFinal(data)
return Encrypted(salt.encodeBase64(), iv.encodeBase64(), raw.encodeBase64())
}
private fun getSecretKey(passCode: String, salt: ByteArray): Key {
val pbKeySpec = PBEKeySpec(passCode.toCharArray(), salt, ITERATION_AMOUNT, KEY_SIZE)
val keyBytes = SecretKeyFactory.getInstance(ALGORITHM_TYPE).generateSecret(pbKeySpec).encoded
return SecretKeySpec(keyBytes, KeyProperties.KEY_ALGORITHM_AES)
}
Now my question is: How secure is this implementation?
- How could an attacker retrieve the refresh token from shared
preference and decrypt it? - Is the symmetric key inside secure element?
- How safe is this implementation against malware or root?
- How easy can the key be brute forced? (except that user tries 10k
times manually to insert the correct pin)
Comments
Comment posted by a_local_nobody
i don’t think you’re going to find a good answer here, might be better to ask this over on the security community
Comment posted by Minki
Thanks for the answer, how do you know that the symmetric key is not in the secure element? Where did you read that?