diff options
author | Eric Biggers <ebiggers@google.com> | 2024-01-17 03:54:11 +0000 |
---|---|---|
committer | Eric Biggers <ebiggers@google.com> | 2024-01-17 18:36:57 +0000 |
commit | 6e5ccd7f4a73a54d4c3f4a7441780fab0d5238dc (patch) | |
tree | d3cc06d93bd5bde7431ed053fe7e8e71f2aa260f | |
parent | d68e691d0a2d65385f08e7e98b1865e72457fdc1 (diff) | |
download | security-6e5ccd7f4a73a54d4c3f4a7441780fab0d5238dc.tar.gz |
keystore: eliminate redundant key stretching
Since the Keystore password is a high-entropy synthetic password, key
stretching is not required. Therefore, improve the performance of
encrypting and decrypting Keystore user super keys by using HKDF instead
of 8192-iteration PBKDF2. PBKDF2 continues to be used for decrypting
old keys, when AES-GCM decryption using the HKDF-derived key fails.
Bug: 296464083
Bug: 314391626
Test: atest -p --include-subdirs system/security/keystore2
Test: Upgraded a device and verified the old super keys can still be
decrypted.
Test: Verified via logcat that super key creation got faster.
Change-Id: Ib7976671ecf886e6308b66e6b1fdfb4b21346afb
-rw-r--r-- | keystore2/src/crypto/lib.rs | 24 | ||||
-rw-r--r-- | keystore2/src/super_key.rs | 28 |
2 files changed, 37 insertions, 15 deletions
diff --git a/keystore2/src/crypto/lib.rs b/keystore2/src/crypto/lib.rs index 234dede1..09b84ec8 100644 --- a/keystore2/src/crypto/lib.rs +++ b/keystore2/src/crypto/lib.rs @@ -172,7 +172,7 @@ pub fn aes_gcm_encrypt(plaintext: &[u8], key: &[u8]) -> Result<(Vec<u8>, Vec<u8> } } -/// Represents a "password" that can be used to key the PBKDF2 algorithm. +/// A high-entropy synthetic password from which an AES key may be derived. pub enum Password<'a> { /// Borrow an existing byte array Ref(&'a [u8]), @@ -194,20 +194,23 @@ impl<'a> Password<'a> { } } - /// Generate a key from the given password and salt. - /// The salt must be exactly 16 bytes long. - /// Two key sizes are accepted: 16 and 32 bytes. - pub fn derive_key_pbkdf2(&self, salt: &[u8], key_length: usize) -> Result<ZVec, Error> { + /// Derives a key from the given password and salt, using PBKDF2 with 8192 iterations. + /// + /// The salt length must be 16 bytes, and the output key length must be 16 or 32 bytes. + /// + /// This function exists only for backwards compatibility reasons. Keystore now receives only + /// high-entropy synthetic passwords, which do not require key stretching. + pub fn derive_key_pbkdf2(&self, salt: &[u8], out_len: usize) -> Result<ZVec, Error> { if salt.len() != SALT_LENGTH { return Err(Error::InvalidSaltLength); } - match key_length { + match out_len { AES_128_KEY_LENGTH | AES_256_KEY_LENGTH => {} _ => return Err(Error::InvalidKeyLength), } let pw = self.get_key(); - let mut result = ZVec::new(key_length)?; + let mut result = ZVec::new(out_len)?; // Safety: We checked that the salt is exactly 16 bytes long. The other pointers are valid, // and have matching lengths. @@ -224,6 +227,13 @@ impl<'a> Password<'a> { Ok(result) } + /// Derives a key from the given high-entropy synthetic password and salt, using HKDF. + pub fn derive_key_hkdf(&self, salt: &[u8], out_len: usize) -> Result<ZVec, Error> { + let prk = hkdf_extract(self.get_key(), salt)?; + let info = []; + hkdf_expand(out_len, &prk, &info) + } + /// Try to make another Password object with the same data. pub fn try_clone(&self) -> Result<Password<'static>, Error> { Ok(Password::Owned(ZVec::try_from(self.get_key())?)) diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs index b9f43ed1..9c17ccbf 100644 --- a/keystore2/src/super_key.rs +++ b/keystore2/src/super_key.rs @@ -532,11 +532,17 @@ impl SuperKeyManager { (Some(&EncryptedBy::Password), Some(salt), Some(iv), Some(tag)) => { // Note that password encryption is AES no matter the value of algorithm. let key = pw - .derive_key_pbkdf2(salt, AES_256_KEY_LENGTH) - .context(ks_err!("Failed to generate key from password."))?; - - aes_gcm_decrypt(blob, iv, tag, &key) - .context(ks_err!("Failed to decrypt key blob."))? + .derive_key_hkdf(salt, AES_256_KEY_LENGTH) + .context(ks_err!("Failed to derive key from password."))?; + + aes_gcm_decrypt(blob, iv, tag, &key).or_else(|_e| { + // Handle old key stored before the switch to HKDF. + let key = pw + .derive_key_pbkdf2(salt, AES_256_KEY_LENGTH) + .context(ks_err!("Failed to derive key from password (PBKDF2)."))?; + aes_gcm_decrypt(blob, iv, tag, &key) + .context(ks_err!("Failed to decrypt key blob.")) + })? } (enc_by, salt, iv, tag) => { return Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(ks_err!( @@ -563,14 +569,20 @@ impl SuperKeyManager { } /// Encrypts the super key from a key derived from the password, before storing in the database. + /// This does not stretch the password; i.e., it assumes that the password is a high-entropy + /// synthetic password, not a low-entropy user provided password. pub fn encrypt_with_password( super_key: &[u8], pw: &Password, ) -> Result<(Vec<u8>, BlobMetaData)> { let salt = generate_salt().context("In encrypt_with_password: Failed to generate salt.")?; - let derived_key = pw - .derive_key_pbkdf2(&salt, AES_256_KEY_LENGTH) - .context(ks_err!("Failed to derive password."))?; + let derived_key = if android_security_flags::fix_unlocked_device_required_keys_v2() { + pw.derive_key_hkdf(&salt, AES_256_KEY_LENGTH) + .context(ks_err!("Failed to derive key from password."))? + } else { + pw.derive_key_pbkdf2(&salt, AES_256_KEY_LENGTH) + .context(ks_err!("Failed to derive password."))? + }; let mut metadata = BlobMetaData::new(); metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password)); metadata.add(BlobMetaEntry::Salt(salt)); |