summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Biggers <ebiggers@google.com>2024-01-17 03:54:11 +0000
committerEric Biggers <ebiggers@google.com>2024-01-17 18:36:57 +0000
commit6e5ccd7f4a73a54d4c3f4a7441780fab0d5238dc (patch)
treed3cc06d93bd5bde7431ed053fe7e8e71f2aa260f
parentd68e691d0a2d65385f08e7e98b1865e72457fdc1 (diff)
downloadsecurity-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.rs24
-rw-r--r--keystore2/src/super_key.rs28
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));