use crate::error::Hwp2MdError;
use aes::cipher::{generic_array::GenericArray, BlockDecrypt, KeyInit};
use aes::Aes128;
pub(crate) const HWPTAG_DISTRIBUTE_DOC_DATA: u16 = 0x0026;
const MIN_SEED_LEN: usize = 20;
const AES_BLOCK_SIZE: usize = 16;
struct MsvcLcg {
seed: u32,
}
impl MsvcLcg {
fn new(seed: u32) -> Self {
Self { seed }
}
fn rand(&mut self) -> u32 {
self.seed = self.seed.wrapping_mul(214_013).wrapping_add(2_531_011);
(self.seed >> 16) & 0x7FFF
}
}
pub(crate) fn decrypt_seed(seed_data: &[u8]) -> Result<Vec<u8>, Hwp2MdError> {
if seed_data.len() < MIN_SEED_LEN {
return Err(Hwp2MdError::HwpParse(format!(
"DISTRIBUTE_DOC_DATA seed too short: {} bytes (need at least {MIN_SEED_LEN})",
seed_data.len(),
)));
}
let initial_seed = u32::from_le_bytes([seed_data[0], seed_data[1], seed_data[2], seed_data[3]]);
let mut lcg = MsvcLcg::new(initial_seed);
let mut decrypted = seed_data.to_vec();
for byte in &mut decrypted {
let lcg_byte = (lcg.rand() & 0xFF) as u8;
*byte ^= lcg_byte;
}
Ok(decrypted)
}
pub(crate) fn extract_aes_key(decrypted_seed: &[u8]) -> Result<[u8; 16], Hwp2MdError> {
if decrypted_seed.is_empty() {
return Err(Hwp2MdError::HwpParse("decrypted seed is empty".into()));
}
let delta = (decrypted_seed[0] & 0x0F) as usize;
let offset = 4 + delta;
let end = offset + AES_BLOCK_SIZE;
if end > decrypted_seed.len() {
return Err(Hwp2MdError::HwpParse(format!(
"AES key would extend past seed: offset={offset}, seed_len={}",
decrypted_seed.len(),
)));
}
let mut key = [0u8; 16];
key.copy_from_slice(&decrypted_seed[offset..end]);
Ok(key)
}
pub(crate) fn decrypt_viewtext(data: &[u8], key: &[u8; 16]) -> Result<Vec<u8>, Hwp2MdError> {
let cipher = Aes128::new(GenericArray::from_slice(key));
let mut out = data.to_vec();
let block_count = out.len() / AES_BLOCK_SIZE;
for i in 0..block_count {
let start = i * AES_BLOCK_SIZE;
let end = start + AES_BLOCK_SIZE;
let block = GenericArray::from_mut_slice(&mut out[start..end]);
cipher.decrypt_block(block);
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn msvc_lcg_seed_zero_known_sequence() {
let mut lcg = MsvcLcg::new(0);
assert_eq!(lcg.rand(), 38);
assert_eq!(lcg.rand(), 7_719);
assert_eq!(lcg.rand(), 21_238);
assert_eq!(lcg.rand(), 2_437);
}
#[test]
fn msvc_lcg_output_fits_in_15_bits() {
let mut lcg = MsvcLcg::new(12_345);
for _ in 0..1_000 {
assert!(lcg.rand() <= 0x7FFF);
}
}
#[test]
fn msvc_lcg_wraps_without_panic() {
let mut lcg = MsvcLcg::new(u32::MAX);
let _ = lcg.rand();
}
#[test]
fn decrypt_seed_rejects_short_payload() {
let short = vec![0u8; MIN_SEED_LEN - 1];
assert!(decrypt_seed(&short).is_err());
}
#[test]
fn decrypt_seed_output_length_matches_input() {
let data = vec![0u8; 256];
let out = decrypt_seed(&data).unwrap();
assert_eq!(out.len(), 256);
}
#[test]
fn decrypt_seed_xors_with_lcg_output() {
let seed = vec![0u8; MIN_SEED_LEN];
let out = decrypt_seed(&seed).unwrap();
let expected_byte = (MsvcLcg::new(0).rand() & 0xFF) as u8;
assert_eq!(out[0], expected_byte);
}
#[test]
fn decrypt_seed_is_not_its_own_inverse() {
let original = vec![0xAA_u8; 64];
let decrypted = decrypt_seed(&original).unwrap();
assert_ne!(decrypted[0], original[0]);
}
#[test]
fn decrypt_seed_known_vector() {
let mut seed = vec![0u8; MIN_SEED_LEN];
let out = decrypt_seed(&seed).unwrap();
assert_eq!(out[0], 0x26);
seed[0] = 0x26;
let out2 = decrypt_seed(&seed).unwrap();
let mut check_lcg = MsvcLcg::new(38);
let expected = (check_lcg.rand() & 0xFF) as u8;
assert_eq!(out2[0], 0x26_u8 ^ expected);
}
#[test]
fn extract_aes_key_rejects_empty() {
assert!(extract_aes_key(&[]).is_err());
}
#[test]
fn extract_aes_key_rejects_too_short() {
let mut data = vec![0u8; 34];
data[0] = 0x0F; assert!(extract_aes_key(&data).is_err());
}
#[test]
fn extract_aes_key_with_zero_delta() {
let mut data = vec![0u8; 256];
data[0] = 0x00;
for i in 0..16 {
data[4 + i] = (i + 1) as u8;
}
let key = extract_aes_key(&data).unwrap();
for (i, &b) in key.iter().enumerate() {
assert_eq!(b, (i + 1) as u8);
}
}
#[test]
fn extract_aes_key_with_max_delta() {
let mut data = vec![0u8; 256];
data[0] = 0x0F;
for i in 0..16 {
data[19 + i] = 0xAA;
}
let key = extract_aes_key(&data).unwrap();
assert!(key.iter().all(|&b| b == 0xAA));
}
#[test]
fn extract_aes_key_only_low_nibble_of_first_byte_used() {
let mut data = vec![0u8; 256];
data[0] = 0xF5;
for i in 0..16 {
data[9 + i] = 0x55;
}
let key = extract_aes_key(&data).unwrap();
assert!(key.iter().all(|&b| b == 0x55));
}
#[test]
fn decrypt_viewtext_empty_data() {
let key = [0u8; 16];
let out = decrypt_viewtext(&[], &key).unwrap();
assert!(out.is_empty());
}
#[test]
fn decrypt_viewtext_single_block_roundtrip() {
use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit};
let key = [
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf,
0x4f, 0x3c,
];
let plaintext = [
0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93,
0x17, 0x2a,
];
let cipher = Aes128::new(GenericArray::from_slice(&key));
let mut ciphertext = GenericArray::from(plaintext);
cipher.encrypt_block(&mut ciphertext);
let decrypted = decrypt_viewtext(ciphertext.as_slice(), &key).unwrap();
assert_eq!(decrypted.as_slice(), plaintext.as_slice());
}
#[test]
fn decrypt_viewtext_multiple_blocks_roundtrip() {
use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit};
let key = [0x00u8; 16];
let plaintext = [0x42u8; 48];
let cipher = Aes128::new(GenericArray::from_slice(&key));
let mut ciphertext = plaintext.to_vec();
for chunk in ciphertext.chunks_mut(16) {
let block = GenericArray::from_mut_slice(chunk);
cipher.encrypt_block(block);
}
let decrypted = decrypt_viewtext(&ciphertext, &key).unwrap();
assert_eq!(decrypted.as_slice(), plaintext.as_slice());
}
#[test]
fn decrypt_viewtext_partial_trailing_block_left_unmodified() {
use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit};
let key = [0x00u8; 16];
let full_block = [0x55u8; 16];
let trailing = [0xAAu8; 5];
let cipher_enc = Aes128::new(GenericArray::from_slice(&key));
let mut encrypted_block = GenericArray::from(full_block);
cipher_enc.encrypt_block(&mut encrypted_block);
let mut input = encrypted_block.to_vec();
input.extend_from_slice(&trailing);
let out = decrypt_viewtext(&input, &key).unwrap();
assert_eq!(&out[..16], full_block.as_slice());
assert_eq!(&out[16..], trailing.as_slice());
}
#[test]
fn full_pipeline_deterministic() {
let mut seed = vec![0u8; 256];
seed[0] = 0x10;
for i in 0..16 {
seed[10 + i] = i as u8; }
let decrypted = decrypt_seed(&seed).unwrap();
let key = extract_aes_key(&decrypted).unwrap();
assert_eq!(key.len(), 16);
let decrypted2 = decrypt_seed(&seed).unwrap();
let key2 = extract_aes_key(&decrypted2).unwrap();
assert_eq!(key, key2);
}
}