fix(chrome): DPAPI HMAC key must be SHA1(masterkey), not raw masterkey

DPAPI's protocol derives the session key as HMAC-SHA512(SHA1(masterkey), salt).
LSASS exposes both the raw 64-byte master key AND its SHA-1 specifically for
this reason. The previous implementation passed the raw 64-byte key as the HMAC
input, which only round-tripped because the synthetic test built the blob with
the same wrong key. Validated against real Edge Local State blobs on a Windows
10 VM with cleartext masterkey sourced from LSASS — derived v10 AES-GCM key
now matches the value Chrome encrypts cookies with.

Test updated to use the correct algorithm so it actually exercises the spec.
This commit is contained in:
NK
2026-06-07 14:49:20 +02:00
parent 6b57235afd
commit 68fbf42793
2 changed files with 11 additions and 4 deletions
+6 -3
View File
@@ -7,6 +7,7 @@ use aes::Aes256;
use cbc::cipher::block_padding::NoPadding;
use cbc::cipher::{BlockDecryptMut, KeyIvInit};
use hmac::{Hmac, Mac};
use sha1::{Digest, Sha1};
use sha2::Sha512;
type Aes256CbcDec = cbc::Decryptor<Aes256>;
@@ -161,11 +162,13 @@ pub fn decrypt_blob(blob: &DpapiBlob<'_>, masterkey: &[u8]) -> Result<Vec<u8>> {
blob.crypt_alg, blob.hmac_alg
)));
}
// Derive session key: HMAC-SHA512(key=masterkey, data=salt).
// First 32 bytes = AES key, next 16 = IV.
// Derive session key: HMAC-SHA512(key=SHA1(masterkey), data=salt).
// DPAPI hashes the 64-byte masterkey down to 20 bytes via SHA-1 before HMAC.
// First 32 bytes of the 64-byte HMAC output = AES key, next 16 = IV.
type HmacSha512 = Hmac<Sha512>;
let mk_sha1 = Sha1::digest(masterkey);
let mut h =
HmacSha512::new_from_slice(masterkey).map_err(|_| Error::Parse("hmac key".into()))?;
HmacSha512::new_from_slice(&mk_sha1).map_err(|_| Error::Parse("hmac key".into()))?;
h.update(blob.salt);
let session = h.finalize().into_bytes();
+5 -1
View File
@@ -7,13 +7,17 @@ use cbc::cipher::block_padding::NoPadding;
use cbc::cipher::{BlockEncryptMut, KeyIvInit};
use hmac::{Hmac, Mac};
use sha2::Sha512;
use sha1::Digest as Sha1Digest;
use sha1::Sha1;
use vmkatz::chrome::dpapi_decrypt::{decrypt_blob, parse_blob};
type Aes256CbcEnc = cbc::Encryptor<Aes256>;
type HmacSha512 = Hmac<Sha512>;
fn build_blob(plaintext: &[u8], masterkey: &[u8], salt: &[u8]) -> Vec<u8> {
let mut h = HmacSha512::new_from_slice(masterkey).unwrap();
// DPAPI: HMAC-SHA512 key is SHA1(masterkey), not the raw masterkey.
let mk_sha1 = Sha1::digest(masterkey);
let mut h = HmacSha512::new_from_slice(&mk_sha1).unwrap();
h.update(salt);
let session = h.finalize().into_bytes();
let key = &session[..32];