1use aes::Aes256;
32use cbc::Decryptor as CbcDec;
33use cipher::{block_padding::NoPadding, BlockDecryptMut, KeyIvInit};
34use des::TdesEde3;
35use hmac::{Hmac, Mac};
36use sha1::{Digest, Sha1};
37
38use forensicnomicon::dpapi::{
39 cipher_alg_info, hash_alg_info, CALG_AES_256, CALG_HMAC, CALG_SHA1, CALG_SHA_512,
40};
41
42use crate::blob::{decode_utf16le, hash_alg};
43use crate::decrypt::hmac_hash;
44use crate::error::DpapiError;
45
46const HEADER_LEN: usize = 128;
48pub const MASTER_KEY_LEN: usize = 64;
50
51#[derive(Debug, Clone)]
57pub struct MasterKeyFile {
58 pub version: u32,
59 pub guid: String,
61 pub policy: u32,
62 pub flags: u32,
63 pub master_key: Vec<u8>,
64 pub backup_key: Vec<u8>,
65 pub cred_hist: Vec<u8>,
66 pub domain_key: Vec<u8>,
67}
68
69#[derive(Debug, Clone)]
71pub struct MasterKey {
72 pub version: u32,
73 pub salt: [u8; 16],
74 pub rounds: u32,
75 pub alg_id_hash: u32,
76 pub alg_id_encrypt: u32,
77 pub data: Vec<u8>,
78}
79
80fn read_u32(data: &[u8], pos: &mut usize) -> Result<u32, DpapiError> {
82 let slice: [u8; 4] = data
83 .get(*pos..*pos + 4)
84 .and_then(|s| s.try_into().ok())
85 .ok_or(DpapiError::TooShort {
86 needed: *pos + 4,
87 got: data.len(),
88 })?;
89 *pos += 4;
90 Ok(u32::from_le_bytes(slice))
91}
92
93fn read_u64(data: &[u8], pos: &mut usize) -> Result<u64, DpapiError> {
95 let slice: [u8; 8] = data
96 .get(*pos..*pos + 8)
97 .and_then(|s| s.try_into().ok())
98 .ok_or(DpapiError::TooShort {
99 needed: *pos + 8,
100 got: data.len(),
101 })?;
102 *pos += 8;
103 Ok(u64::from_le_bytes(slice))
104}
105
106pub fn parse_masterkey_file(data: &[u8]) -> Result<MasterKeyFile, DpapiError> {
112 if data.len() < HEADER_LEN {
113 return Err(DpapiError::TooShort {
114 needed: HEADER_LEN,
115 got: data.len(),
116 });
117 }
118
119 let mut pos = 0usize;
120 let version = read_u32(data, &mut pos)?;
121 let _unk1 = read_u32(data, &mut pos)?;
122 let _unk2 = read_u32(data, &mut pos)?;
123 let guid = decode_utf16le(&data[pos..pos + 72]);
124 pos += 72;
125 let _unknown = read_u32(data, &mut pos)?;
126 let policy = read_u32(data, &mut pos)?;
127 let flags = read_u32(data, &mut pos)?;
128 let master_key_len = read_u64(data, &mut pos)? as usize;
129 let backup_key_len = read_u64(data, &mut pos)? as usize;
130 let cred_hist_len = read_u64(data, &mut pos)? as usize;
131 let domain_key_len = read_u64(data, &mut pos)? as usize;
132 debug_assert_eq!(pos, HEADER_LEN);
133
134 let mut take = |len: usize| -> Result<Vec<u8>, DpapiError> {
135 let slice = data.get(pos..pos + len).ok_or(DpapiError::TooShort {
136 needed: pos + len,
137 got: data.len(),
138 })?;
139 pos += len;
140 Ok(slice.to_vec())
141 };
142
143 let master_key = take(master_key_len)?;
144 let backup_key = take(backup_key_len)?;
145 let cred_hist = take(cred_hist_len)?;
146 let domain_key = take(domain_key_len)?;
147
148 Ok(MasterKeyFile {
149 version,
150 guid,
151 policy,
152 flags,
153 master_key,
154 backup_key,
155 cred_hist,
156 domain_key,
157 })
158}
159
160pub fn parse_master_key(data: &[u8]) -> Result<MasterKey, DpapiError> {
162 if data.len() < 32 {
164 return Err(DpapiError::TooShort {
165 needed: 32,
166 got: data.len(),
167 });
168 }
169 let mut pos = 0usize;
170 let version = read_u32(data, &mut pos)?;
171 let mut salt = [0u8; 16];
172 salt.copy_from_slice(&data[pos..pos + 16]);
173 pos += 16;
174 let rounds = read_u32(data, &mut pos)?;
175 let alg_id_hash = read_u32(data, &mut pos)?;
176 let alg_id_encrypt = read_u32(data, &mut pos)?;
177 let payload = data[pos..].to_vec();
178 Ok(MasterKey {
179 version,
180 salt,
181 rounds,
182 alg_id_hash,
183 alg_id_encrypt,
184 data: payload,
185 })
186}
187
188fn uses_sha512(alg_id_hash: u32) -> Result<bool, DpapiError> {
194 if alg_id_hash == CALG_HMAC {
195 return Ok(false);
196 }
197 hash_alg_info(alg_id_hash)
198 .map(|h| h.is_sha512)
199 .ok_or(DpapiError::UnsupportedAlgId(alg_id_hash))
200}
201
202fn prf(is_sha512: bool, key: &[u8], msg: &[u8]) -> Result<Vec<u8>, DpapiError> {
209 let alg = hash_alg(if is_sha512 { CALG_SHA_512 } else { CALG_SHA1 });
210 hmac_hash(alg, key, msg)
211}
212
213fn derive_key(
221 is_sha512: bool,
222 passphrase: &[u8],
223 salt: &[u8],
224 keylen: usize,
225 count: u32,
226) -> Result<Vec<u8>, DpapiError> {
227 let mut key_material: Vec<u8> = Vec::with_capacity(keylen + 64);
228 let mut i: u32 = 1;
229 while key_material.len() < keylen {
230 let mut u = salt.to_vec();
231 u.extend_from_slice(&i.to_be_bytes());
232 i += 1;
233 let mut derived = prf(is_sha512, passphrase, &u)?;
234 for _ in 0..count.saturating_sub(1) {
235 let actual = prf(is_sha512, passphrase, &derived)?;
236 for (d, a) in derived.iter_mut().zip(actual.iter()) {
237 *d ^= a;
238 }
239 }
240 key_material.extend_from_slice(&derived);
241 }
242 key_material.truncate(keylen);
243 Ok(key_material)
244}
245
246pub fn derive_master_key_from_prekey(
255 mk: &MasterKey,
256 pre_key: &[u8],
257) -> Result<[u8; MASTER_KEY_LEN], DpapiError> {
258 let is_sha512 = uses_sha512(mk.alg_id_hash)?;
259 let cipher = cipher_alg_info(mk.alg_id_encrypt)
260 .ok_or(DpapiError::UnsupportedAlgId(mk.alg_id_encrypt))?;
261 let digest_len = hash_alg_info(mk.alg_id_hash)
262 .ok_or(DpapiError::UnsupportedAlgId(mk.alg_id_hash))?
263 .digest_len;
264
265 let derived = derive_key(
266 is_sha512,
267 pre_key,
268 &mk.salt,
269 cipher.key_len + cipher.iv_len,
270 mk.rounds,
271 )?;
272 let crypt_key = &derived[..cipher.key_len];
273 let iv = &derived[cipher.key_len..cipher.key_len + cipher.iv_len];
274
275 let cleartext = if mk.alg_id_encrypt == CALG_AES_256 {
276 cbc_decrypt_no_pad::<Aes256>(crypt_key, iv, &mk.data)?
277 } else {
278 cbc_decrypt_no_pad::<TdesEde3>(crypt_key, iv, &mk.data)?
279 };
280
281 if cleartext.len() < MASTER_KEY_LEN || cleartext.len() < 16 + digest_len {
282 return Err(DpapiError::DecryptionFailed);
283 }
284 let master_key_bytes = &cleartext[cleartext.len() - MASTER_KEY_LEN..];
285 let hmac_salt = &cleartext[..16];
286 let stored_hmac = &cleartext[16..16 + digest_len];
287
288 let hmac_key = prf(is_sha512, pre_key, hmac_salt)?;
290 let calc = prf(is_sha512, &hmac_key, master_key_bytes)?;
291 if calc.get(..digest_len) != Some(stored_hmac) {
292 return Err(DpapiError::HmacMismatch);
293 }
294
295 let mut out = [0u8; MASTER_KEY_LEN];
296 out.copy_from_slice(master_key_bytes);
297 Ok(out)
298}
299
300fn cbc_decrypt_no_pad<C>(key: &[u8], iv: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, DpapiError>
303where
304 C: cipher::BlockCipher + cipher::BlockDecryptMut + cipher::KeyInit + cipher::BlockSizeUser,
305{
306 let mut buf = ciphertext.to_vec();
307 let dec = CbcDec::<C>::new_from_slices(key, iv).map_err(|_| DpapiError::InvalidKeyLength)?;
308 let out = dec
309 .decrypt_padded_mut::<NoPadding>(&mut buf)
310 .map_err(|_| DpapiError::DecryptionFailed)?;
311 Ok(out.to_vec())
312}
313
314pub fn prekey_from_sha1(sid: &str, pwd_sha1: &[u8; 20]) -> Result<[u8; 20], DpapiError> {
321 let sid_utf16 = utf16le_with_nul(sid);
322 let mut mac =
323 Hmac::<Sha1>::new_from_slice(pwd_sha1).map_err(|_| DpapiError::InvalidKeyLength)?;
324 mac.update(&sid_utf16);
325 let out = mac.finalize().into_bytes();
326 let mut k = [0u8; 20];
327 k.copy_from_slice(&out);
328 Ok(k)
329}
330
331pub fn prekey_from_password(sid: &str, password: &str) -> Result<[u8; 20], DpapiError> {
335 let mut h = Sha1::new();
336 h.update(utf16le(password));
337 let digest: [u8; 20] = h.finalize().into();
338 prekey_from_sha1(sid, &digest)
339}
340
341pub fn derive_master_key_from_password(
346 file: &MasterKeyFile,
347 sid: &str,
348 password: &str,
349) -> Result<[u8; MASTER_KEY_LEN], DpapiError> {
350 let mk = parse_master_key(&file.master_key)?;
351 let pre_key = prekey_from_password(sid, password)?;
352 derive_master_key_from_prekey(&mk, &pre_key)
353}
354
355pub fn derive_master_key_from_domain_backup(
365 _domain_key: &[u8],
366 _pvk: &[u8],
367) -> Result<[u8; MASTER_KEY_LEN], DpapiError> {
368 Err(DpapiError::DomainBackupUnsupported)
369}
370
371fn utf16le(s: &str) -> Vec<u8> {
373 s.encode_utf16().flat_map(u16::to_le_bytes).collect()
374}
375
376fn utf16le_with_nul(s: &str) -> Vec<u8> {
378 let mut v = utf16le(s);
379 v.extend_from_slice(&[0, 0]);
380 v
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386
387 fn hex(s: &str) -> Vec<u8> {
388 (0..s.len())
389 .step_by(2)
390 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
391 .collect()
392 }
393
394 const SYSTEM_MK_FILE: &str = "020000000000000000000000650061003900350065006200610038002d0062006100300030002d0034006500310061002d0062003400330066002d00350031006500610033003000310037003100640031003100000000000000000006000000b00000000000000090000000000000001400000000000000000000000000000002000000f42f61ea0c9647bf403819898452089ff84300000e80000010660000e2441dec11a6b6f03ffebba1e71473f78da46a52b12caff7df9925ed6ac89d84050ad15cfe88ec50ece201e5a80eb198909ff8c781510f78859b96cfade433c83a7f3fc19926b0280e6a196ef1b0b5e1b3c1ab426120da53f24e5989f8a7d3dde86ac444901401a6df6407f550d197ff27c91abb5c331250b5a7ce58c1f61fd0d656360df58e6f5b5faac639661aa89402000000bdc1f9592357353a2a3ebcf8cedc72bbf84300000e800000106600001231d67ec689fea9f6de6bd66a21e28d5405232df71deae5bf3ab63c6cb2cac08ad17456979d72b70de13afc2b61d05434161191dcdfb24aaac7ed0275f71eff9f936a559aff4be6301ba99d66bd8e07b1a325c73c7a4da97117f80551a3f0a75da9fcc37c4d2bb43b5a9ef1684446ae0300000000000000000000000000000000000000";
403 const SYSTEM_USER_KEY: &str = "458dc597034d8801fc6fe3b342817caabb81a0cb";
404 const SYSTEM_MASTER_KEY: &str = "682a9b8923ff4ca7ce0ef7e4cee061f0ff942cd31c7703ec60792740b2e7d0b1b5115d1ff77e10b77e189e0d6e99d5b668190ecd44fa84e82e049f406e2c2a59";
405
406 #[test]
407 fn parse_system_masterkey_file_header() {
408 let f = parse_masterkey_file(&hex(SYSTEM_MK_FILE)).expect("parse file");
409 assert_eq!(f.version, 2);
410 assert_eq!(f.guid, "ea95eba8-ba00-4e1a-b43f-51ea30171d11");
411 assert_eq!(f.flags, 0x6);
412 assert_eq!(f.master_key.len(), 176);
413 assert_eq!(f.backup_key.len(), 144);
414 assert_eq!(f.cred_hist.len(), 20);
415 assert_eq!(f.domain_key.len(), 0);
416 }
417
418 #[test]
419 fn parse_system_master_key_subblob_fields() {
420 let f = parse_masterkey_file(&hex(SYSTEM_MK_FILE)).expect("parse file");
421 let mk = parse_master_key(&f.master_key).expect("parse mk");
422 assert_eq!(mk.version, 2);
423 assert_eq!(mk.rounds, 17400);
424 assert_eq!(mk.alg_id_hash, 0x800e);
425 assert_eq!(mk.alg_id_encrypt, 0x6610);
426 assert_eq!(mk.salt.to_vec(), hex("f42f61ea0c9647bf403819898452089f"));
427 }
428
429 #[test]
432 fn derive_system_master_key_matches_impacket() {
433 let f = parse_masterkey_file(&hex(SYSTEM_MK_FILE)).expect("parse file");
434 let mk = parse_master_key(&f.master_key).expect("parse mk");
435 let pre_key = hex(SYSTEM_USER_KEY);
436 let derived = derive_master_key_from_prekey(&mk, &pre_key).expect("derive");
437 assert_eq!(derived.to_vec(), hex(SYSTEM_MASTER_KEY));
438 }
439
440 #[test]
441 fn wrong_prekey_fails_hmac() {
442 let f = parse_masterkey_file(&hex(SYSTEM_MK_FILE)).expect("parse file");
443 let mk = parse_master_key(&f.master_key).expect("parse mk");
444 let bad = [0xABu8; 20];
445 assert!(matches!(
446 derive_master_key_from_prekey(&mk, &bad),
447 Err(DpapiError::HmacMismatch)
448 ));
449 }
450
451 const DERIVE_SID: &str = "S-1-5-21-1455520393-2011455520393-2019809541-4133251990-500";
456 const DERIVE_PWD: &str = "Admin456";
457 const DERIVE_PWD_SHA1: &str = "7ca54db25c28c72a5ec9a43b08bf75937c8b5fc6";
458 const DERIVE_PREKEY: &str = "742ab02b5f80ea5658ffecd49491f77e9b3c536a";
459
460 #[test]
461 fn prekey_from_password_matches_impacket() {
462 let k = prekey_from_password(DERIVE_SID, DERIVE_PWD).expect("derive prekey");
463 assert_eq!(k.to_vec(), hex(DERIVE_PREKEY));
464 }
465
466 #[test]
467 fn prekey_from_sha1_matches_impacket() {
468 let mut sha1 = [0u8; 20];
469 sha1.copy_from_slice(&hex(DERIVE_PWD_SHA1));
470 let k = prekey_from_sha1(DERIVE_SID, &sha1).expect("derive prekey");
471 assert_eq!(k.to_vec(), hex(DERIVE_PREKEY));
472 }
473
474 #[test]
475 fn domain_backup_path_refuses() {
476 assert!(matches!(
477 derive_master_key_from_domain_backup(&[0u8; 16], &[0u8; 16]),
478 Err(DpapiError::DomainBackupUnsupported)
479 ));
480 }
481}