Skip to main content

pptx_rs/
crypto.rs

1//! 加密与保护模块:修改密码保护 + ECMA-376 Agile Encryption。
2//!
3//! 本模块提供两种级别的文档保护:
4//!
5//! 1. **修改密码保护**(modifyVerifier):打开时可只读浏览,修改需密码。
6//!    对标 PowerPoint "保护演示文稿 → 限制访问" 功能。
7//!    算法遵循 [MS-OFFCRYPTO] §2.4.2.4(SHA-512 + salt + spinCount)。
8//!
9//! 2. **文件加密**(ECMA-376 Agile Encryption):打开文件即需密码。
10//!    对标 PowerPoint "保护演示文稿 → 用密码进行加密" 功能。
11//!    使用 AES-256-CBC + SHA-512 密钥派生,符合现代 Office 加密标准。
12//!    算法遵循 [MS-OFFCRYPTO] §2.3.4.11(Agile Encryption)。
13//!
14//! # 与 pypdf 的对应
15//!
16//! - pypdf `PdfWriter.encrypt(user_password, owner_password)` ←→ [`encrypt_package`]
17//! - pypdf `PdfReader.is_encrypted` ←→ [`is_encrypted_package`]
18//! - pypdf `PdfReader.decrypt(password)` ←→ [`decrypt_package`]
19//!
20//! [MS-OFFCRYPTO]: https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/
21
22use base64::Engine;
23use sha2::{Digest, Sha512};
24
25use crate::error::{Error, Result};
26
27/// 修改密码保护的哈希迭代次数。
28///
29/// Office 2016+ 默认 100 000 次;越大越慢但越安全。
30pub const MODIFY_SPIN_COUNT: u32 = 100_000;
31
32/// Agile Encryption 的哈希迭代次数。
33///
34/// 与 modifyVerifier 相同默认值,但两者属于不同的加密体系。
35const AGILE_SPIN_COUNT: u32 = 100_000;
36
37/// salt 字节长度(16 字节 = 128 位,符合 MS-OFFCRYPTO 推荐)。
38pub const SALT_LEN: usize = 16;
39
40/// AES-256 密钥长度(32 字节)。
41const AES_KEY_LEN: usize = 32;
42
43/// AES 块大小(16 字节)。
44const AES_BLOCK_SIZE: usize = 16;
45
46// ---------------------------------------------------------------------------
47// Agile Encryption blockKey 常量(MS-OFFCRYPTO §2.3.4.11 / §2.3.4.12)
48// ---------------------------------------------------------------------------
49
50/// 加密 encryptedKeyValue 时的 blockKey(32 字节)。
51///
52/// 用于从密码派生哈希中进一步派生加密 secret_key 所需的密钥。
53const BLOCK_KEY_ENCRYPTED_KEY_VALUE: &[u8] = &[
54    0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6, 0x2e, 0x0b, 0x80, 0x14, 0x73, 0x0f, 0xbf, 0x7d,
55    0x0e, 0xda, 0x25, 0x2e, 0x6b, 0x0f, 0x9b, 0x55, 0xb3, 0x10, 0x0f, 0x65, 0x0f, 0xa3, 0x5c, 0x6b,
56];
57
58/// 加密 verifierHashInput 时的 blockKey(8 字节)。
59const BLOCK_KEY_VERIFIER_HASH_INPUT: &[u8] = &[0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79];
60
61/// 加密 verifierHashValue 时的 blockKey(8 字节)。
62const BLOCK_KEY_VERIFIER_HASH_VALUE: &[u8] = &[0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x27, 0x9c];
63
64// ---------------------------------------------------------------------------
65// 修改密码保护(modifyVerifier)
66// ---------------------------------------------------------------------------
67
68/// 修改密码保护参数。
69///
70/// 对应 `<p:modifyVerifier>` XML 元素,包含密码哈希、salt、迭代次数等。
71/// 注入到 `presentation.xml` 后,WPS / PowerPoint 打开时会提示
72/// "以只读方式打开"或"输入密码以修改"。
73#[derive(Clone, Debug)]
74pub struct ModifyProtection {
75    /// salt 的 base64 编码。
76    pub salt_b64: String,
77    /// 哈希值的 base64 编码。
78    pub hash_b64: String,
79    /// 迭代次数。
80    pub spin_count: u32,
81    /// 加密算法提供者类型。
82    pub crypt_provider_type: &'static str,
83    /// 加密算法 SID(14 = SHA-512)。
84    pub algorithm_sid: u32,
85}
86
87impl ModifyProtection {
88    /// 从密码创建修改密码保护。
89    ///
90    /// # 算法(MS-OFFCRYPTO §2.4.2.4)
91    ///
92    /// ```text
93    /// H_0 = SHA-512(salt || password_utf16le)
94    /// H_n = SHA-512(H_{n-1} || n.to_le_u32)   for n = 0..spinCount-1
95    /// ```
96    ///
97    /// # 参数
98    /// - `password`:明文密码;
99    /// - `salt`:16 字节随机值(每次调用应使用不同 salt);
100    /// - `spin_count`:迭代次数(默认 100 000)。
101    pub fn from_password(password: &str, salt: &[u8], spin_count: u32) -> Self {
102        let hash = compute_modify_hash(password, salt, spin_count);
103        let salt_b64 = base64::engine::general_purpose::STANDARD.encode(salt);
104        let hash_b64 = base64::engine::general_purpose::STANDARD.encode(&hash);
105        ModifyProtection {
106            salt_b64,
107            hash_b64,
108            spin_count,
109            crypt_provider_type: "rsaFull",
110            algorithm_sid: 14,
111        }
112    }
113
114    /// 验证密码是否匹配。
115    ///
116    /// 用相同算法重新计算哈希,与存储的哈希比对。
117    pub fn verify_password(&self, password: &str) -> bool {
118        let salt = match base64::engine::general_purpose::STANDARD.decode(&self.salt_b64) {
119            Ok(s) => s,
120            Err(_) => return false,
121        };
122        let expected_hash = compute_modify_hash(password, &salt, self.spin_count);
123        let expected_b64 = base64::engine::general_purpose::STANDARD.encode(&expected_hash);
124        // 常量时间比较(base64 编码后长度固定,简单比对即可)
125        expected_b64 == self.hash_b64
126    }
127
128    /// 序列化为 `<p:modifyVerifier .../>` XML 元素。
129    ///
130    /// 元素位置必须在 `<p:extLst>` 之前(OOXML schema 顺序要求)。
131    pub fn to_xml_element(&self) -> String {
132        format!(
133            r#"<p:modifyVerifier cryptProviderType="{}" cryptAlgorithmClass="hash" cryptAlgorithmType="typeAny" cryptAlgorithmSid="{}" cryptSpinCount="{}" hash="{}" salt="{}"/>"#,
134            self.crypt_provider_type,
135            self.algorithm_sid,
136            self.spin_count,
137            self.hash_b64,
138            self.salt_b64,
139        )
140    }
141}
142
143/// 按 MS-OFFCRYPTO §2.4.2.4 计算 modifyVerifier 的哈希。
144///
145/// # 算法
146///
147/// ```text
148/// H_0 = SHA-512(salt || password_utf16le)
149/// H_n = SHA-512(H_{n-1} || n.to_le_u32)   for n = 0..spinCount-1
150/// ```
151///
152/// - 密码用 **UTF-16 LE** 编码(OOXML 规范,不是 UTF-8);
153/// - 每次迭代都把迭代序号 n 以小端 uint32 拼到 H_{n-1} 之后。
154///
155/// **注意**:此函数的迭代顺序(`H_{n-1} || iterator`)与 Agile Encryption
156/// 的迭代顺序(`iterator || H_{n-1}`)**不同**,不可混用。
157///
158/// # 参数
159///
160/// - `password`:明文密码(Unicode);
161/// - `salt`:16 字节随机值;
162/// - `spin_count`:迭代次数(Office 2016+ 默认 100 000)。
163///
164/// # 返回
165///
166/// 64 字节(SHA-512 摘要)的二进制哈希。
167pub fn compute_modify_hash(password: &str, salt: &[u8], spin_count: u32) -> Vec<u8> {
168    // 把 password 编码为 UTF-16 LE
169    let password_utf16le: Vec<u8> = password
170        .encode_utf16()
171        .flat_map(|u| u.to_le_bytes())
172        .collect();
173
174    // H_0 = SHA-512(salt || password_utf16le)
175    let mut hasher = Sha512::new();
176    hasher.update(salt);
177    hasher.update(&password_utf16le);
178    let mut h = hasher.finalize().to_vec();
179
180    // H_n = SHA-512(H_{n-1} || n.to_le_u32)
181    // modifyVerifier 的迭代顺序:H_{n-1} 在前,iterator 在后
182    for i in 0..spin_count {
183        let mut hasher = Sha512::new();
184        hasher.update(&h);
185        hasher.update(i.to_le_bytes());
186        h = hasher.finalize().to_vec();
187    }
188    h
189}
190
191// ---------------------------------------------------------------------------
192// ECMA-376 Agile Encryption(文件级加密)
193// ---------------------------------------------------------------------------
194
195/// Agile Encryption 参数。
196///
197/// 对应 `EncryptionInfo` XML 流中的加密参数。
198/// 使用 AES-256-CBC + SHA-512,符合 Office 2016+ 默认加密标准。
199#[derive(Clone, Debug)]
200pub struct AgileEncryptionParams {
201    /// 密码 salt(16 字节,base64 编码)。
202    pub password_salt_b64: String,
203    /// 数据 salt(16 字节,base64 编码)。
204    pub data_salt_b64: String,
205    /// 密码迭代次数。
206    pub spin_count: u32,
207    /// 加密后的数据密钥(base64 编码)。
208    pub encrypted_key_value_b64: String,
209    /// 加密后的验证器哈希输入(base64 编码)。
210    pub encrypted_verifier_hash_input_b64: String,
211    /// 加密后的验证器哈希值(base64 编码)。
212    pub encrypted_verifier_hash_value_b64: String,
213}
214
215/// 生成随机 salt/IV 字节。
216///
217/// 使用 `sha2` + 时间戳 + 计数器作为伪随机源(不依赖 `rand` crate,
218/// 保持库依赖最小化。安全性由 SHA-512 的不可逆性保证)。
219pub fn generate_random_bytes(len: usize) -> Vec<u8> {
220    // 使用 SHA-512 对多个熵源进行哈希来生成伪随机字节
221    let mut result = Vec::with_capacity(len);
222    let mut counter: u64 = 0;
223    while result.len() < len {
224        let mut hasher = Sha512::new();
225        // 熵源:时间戳 + 计数器 + 已生成字节(反馈)
226        hasher.update(
227            std::time::SystemTime::now()
228                .duration_since(std::time::UNIX_EPOCH)
229                .unwrap_or_default()
230                .as_nanos()
231                .to_le_bytes(),
232        );
233        hasher.update(counter.to_le_bytes());
234        hasher.update(&result);
235        let digest = hasher.finalize();
236        let needed = std::cmp::min(64, len - result.len());
237        result.extend_from_slice(&digest[..needed]);
238        counter += 1;
239    }
240    result
241}
242
243// ---------------------------------------------------------------------------
244// Agile Encryption 密钥派生(MS-OFFCRYPTO §2.3.4.11)
245// ---------------------------------------------------------------------------
246
247/// Agile Encryption 密码哈希迭代(MS-OFFCRYPTO §2.3.4.11 第 1-4 步)。
248///
249/// # 算法
250///
251/// ```text
252/// H_0 = SHA-512(salt || password_utf16le)
253/// H_n = SHA-512(iterator_u32_le || H_{n-1})   for iterator = 0..spinCount-1
254/// ```
255///
256/// **与 [`compute_modify_hash`] 的关键区别**:迭代顺序不同!
257/// - modifyVerifier(§2.4.2.4):`H_{n-1} || iterator`
258/// - Agile Encryption(§2.3.4.11):`iterator || H_{n-1}`
259///
260/// # 参数
261/// - `password`:明文密码;
262/// - `salt`:passwordSalt(16 字节);
263/// - `spin_count`:迭代次数。
264///
265/// # 返回
266/// 64 字节 SHA-512 哈希值。
267fn agile_hash_password(password: &str, salt: &[u8], spin_count: u32) -> Vec<u8> {
268    let password_utf16le: Vec<u8> = password
269        .encode_utf16()
270        .flat_map(|u| u.to_le_bytes())
271        .collect();
272
273    // H_0 = SHA-512(salt || password_utf16le)
274    let mut hasher = Sha512::new();
275    hasher.update(salt);
276    hasher.update(&password_utf16le);
277    let mut h = hasher.finalize().to_vec();
278
279    // H_n = SHA-512(iterator_u32_le || H_{n-1})
280    // Agile Encryption 的迭代顺序:iterator 在前,H_{n-1} 在后
281    for i in 0..spin_count {
282        let mut hasher = Sha512::new();
283        hasher.update(i.to_le_bytes());
284        hasher.update(&h);
285        h = hasher.finalize().to_vec();
286    }
287    h
288}
289
290/// Agile Encryption 密钥哈希迭代(MS-OFFCRYPTO §2.3.4.11 第 6-9 步)。
291///
292/// 与 [`agile_hash_password`] 类似,但输入是密钥字节而非密码字符串。
293/// 用于从 secret_key + keyDataSalt 派生数据加密密钥。
294///
295/// # 算法
296///
297/// ```text
298/// H_0 = SHA-512(salt || key_bytes)
299/// H_n = SHA-512(iterator_u32_le || H_{n-1})   for iterator = 0..spinCount-1
300/// ```
301fn agile_hash_key(key: &[u8], salt: &[u8], spin_count: u32) -> Vec<u8> {
302    // H_0 = SHA-512(salt || key)
303    let mut hasher = Sha512::new();
304    hasher.update(salt);
305    hasher.update(key);
306    let mut h = hasher.finalize().to_vec();
307
308    // H_n = SHA-512(iterator_u32_le || H_{n-1})
309    for i in 0..spin_count {
310        let mut hasher = Sha512::new();
311        hasher.update(i.to_le_bytes());
312        hasher.update(&h);
313        h = hasher.finalize().to_vec();
314    }
315    h
316}
317
318/// 用 blockKey 派生最终密钥(MS-OFFCRYPTO §2.3.4.11 第 5 步)。
319///
320/// # 算法
321///
322/// ```text
323/// H_final = SHA-512(H_n || blockKey)
324/// derived_key = H_final[0..key_len]
325/// ```
326///
327/// 规范要求在迭代哈希完成后,再拼接 blockKey 做一次哈希,
328/// 然后截取前 key_len 字节作为最终派生密钥。
329///
330/// # 参数
331/// - `hash`:迭代哈希结果([`agile_hash_password`] 或 [`agile_hash_key`] 的输出);
332/// - `block_key`:blockKey 常量(不同用途使用不同的 blockKey);
333/// - `key_len`:需要的密钥字节数(AES-256 为 32)。
334///
335/// # 返回
336/// `key_len` 字节的派生密钥。
337fn derive_key_with_block_key(hash: &[u8], block_key: &[u8], key_len: usize) -> Vec<u8> {
338    let mut hasher = Sha512::new();
339    hasher.update(hash);
340    hasher.update(block_key);
341    let derived = hasher.finalize();
342    derived[..key_len].to_vec()
343}
344
345/// 从 salt 派生 IV(MS-OFFCRYPTO §2.3.4.11)。
346///
347/// 规范要求 IV 取 salt 的前 blockSize 字节,而非随机生成。
348/// 这保证了加密/解密双方可以从相同的 salt 推导出相同的 IV。
349///
350/// # 参数
351/// - `salt`:salt 值(至少 16 字节)。
352///
353/// # 返回
354/// 16 字节 IV。
355fn iv_from_salt(salt: &[u8]) -> Vec<u8> {
356    salt[..AES_BLOCK_SIZE].to_vec()
357}
358
359// ---------------------------------------------------------------------------
360// AES-256-CBC 加解密
361// ---------------------------------------------------------------------------
362
363/// AES-256-CBC 加密。
364///
365/// # 参数
366/// - `key`:32 字节 AES-256 密钥;
367/// - `iv`:16 字节初始向量;
368/// - `data`:明文数据(会自动添加 PKCS7 填充)。
369///
370/// # 返回
371/// 加密后的密文(长度为 16 的整数倍)。
372///
373/// # 错误
374/// - [`Error::Encryption`]:密钥或 IV 长度不正确。
375pub fn aes_256_cbc_encrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
376    if key.len() != AES_KEY_LEN {
377        return Err(Error::encryption("AES-256 key must be 32 bytes"));
378    }
379    if iv.len() != AES_BLOCK_SIZE {
380        return Err(Error::encryption("AES IV must be 16 bytes"));
381    }
382
383    use aes::Aes256;
384    use cbc::cipher::{BlockEncryptMut, KeyIvInit};
385    type Aes256CbcEnc = cbc::Encryptor<Aes256>;
386
387    let encryptor = Aes256CbcEnc::new_from_slices(key, iv)
388        .map_err(|e| Error::encryption(format!("AES init failed: {}", e)))?;
389    let ciphertext = encryptor.encrypt_padded_vec_mut::<cbc::cipher::block_padding::Pkcs7>(data);
390
391    Ok(ciphertext)
392}
393
394/// AES-256-CBC 解密。
395///
396/// # 参数
397/// - `key`:32 字节 AES-256 密钥;
398/// - `iv`:16 字节初始向量;
399/// - `data`:密文数据。
400///
401/// # 返回
402/// 解密后的明文(自动去除 PKCS7 填充)。
403///
404/// # 错误
405/// - [`Error::Encryption`]:密钥/IV 长度不正确或解密失败(填充错误)。
406pub fn aes_256_cbc_decrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
407    if key.len() != AES_KEY_LEN {
408        return Err(Error::encryption("AES-256 key must be 32 bytes"));
409    }
410    if iv.len() != AES_BLOCK_SIZE {
411        return Err(Error::encryption("AES IV must be 16 bytes"));
412    }
413    if data.len() % AES_BLOCK_SIZE != 0 {
414        return Err(Error::encryption(
415            "ciphertext length must be multiple of 16",
416        ));
417    }
418
419    use aes::Aes256;
420    use cbc::cipher::{BlockDecryptMut, KeyIvInit};
421    type Aes256CbcDec = cbc::Decryptor<Aes256>;
422
423    let decryptor = Aes256CbcDec::new_from_slices(key, iv)
424        .map_err(|e| Error::encryption(format!("AES init failed: {}", e)))?;
425
426    let plaintext = decryptor
427        .decrypt_padded_vec_mut::<cbc::cipher::block_padding::Pkcs7>(data)
428        .map_err(|e| Error::encryption(format!("AES decrypt failed: {}", e)))?;
429    Ok(plaintext)
430}
431
432// ---------------------------------------------------------------------------
433// 加密/解密主流程
434// ---------------------------------------------------------------------------
435
436/// 加密整个 ZIP 包(ECMA-376 Agile Encryption)。
437///
438/// # 流程(MS-OFFCRYPTO §2.3.4.11)
439///
440/// 1. 生成随机 passwordSalt / keyDataSalt / secret_key;
441/// 2. 用密码派生哈希 + blockKey 派生加密密钥,加密 secret_key → encryptedKeyValue;
442/// 3. 生成 verifierHashInput / verifierHashValue,用密码派生密钥加密;
443/// 4. 用 secret_key + keyDataSalt 派生数据加密密钥;
444/// 5. IV = keyDataSalt 的前 16 字节(规范要求,非随机生成);
445/// 6. 加密原始 ZIP → EncryptedPackage;
446/// 7. 构造 EncryptionInfo XML;
447/// 8. 打包为新的 ZIP(含 EncryptionInfo + EncryptedPackage)。
448///
449/// # 参数
450/// - `zip_bytes`:原始 .pptx 的 ZIP 字节流;
451/// - `password`:加密密码。
452///
453/// # 返回
454/// 加密后的 ZIP 字节流(打开需密码)。
455pub fn encrypt_package(zip_bytes: &[u8], password: &str) -> Result<Vec<u8>> {
456    let spin_count = AGILE_SPIN_COUNT;
457
458    // 1) 生成随机值
459    let password_salt = generate_random_bytes(SALT_LEN);
460    let key_data_salt = generate_random_bytes(SALT_LEN);
461    let secret_key = generate_random_bytes(AES_KEY_LEN);
462
463    // 2) 从密码派生哈希(Agile Encryption 迭代顺序:iterator 在前)
464    let password_hash = agile_hash_password(password, &password_salt, spin_count);
465
466    // 3) 用 blockKey 派生加密 secret_key 的密钥
467    let key_encrypt_key =
468        derive_key_with_block_key(&password_hash, BLOCK_KEY_ENCRYPTED_KEY_VALUE, AES_KEY_LEN);
469    // IV = passwordSalt 的前 16 字节(规范要求)
470    let key_iv = iv_from_salt(&password_salt);
471    let encrypted_key = aes_256_cbc_encrypt(&key_encrypt_key, &key_iv, &secret_key)?;
472
473    // 4) 生成并加密验证器
474    // verifierHashInput:随机 16 字节
475    let verifier_hash_input = generate_random_bytes(SALT_LEN);
476    // verifierHashValue = SHA-512(verifierHashInput)
477    let verifier_hash_value = {
478        let mut hasher = Sha512::new();
479        hasher.update(&verifier_hash_input);
480        hasher.finalize().to_vec()
481    };
482
483    // 用 blockKey 派生加密 verifierHashInput 的密钥
484    let verifier_input_key =
485        derive_key_with_block_key(&password_hash, BLOCK_KEY_VERIFIER_HASH_INPUT, AES_KEY_LEN);
486    let encrypted_verifier_hash_input =
487        aes_256_cbc_encrypt(&verifier_input_key, &key_iv, &verifier_hash_input)?;
488
489    // 用 blockKey 派生加密 verifierHashValue 的密钥
490    let verifier_value_key =
491        derive_key_with_block_key(&password_hash, BLOCK_KEY_VERIFIER_HASH_VALUE, AES_KEY_LEN);
492    let encrypted_verifier_hash_value =
493        aes_256_cbc_encrypt(&verifier_value_key, &key_iv, &verifier_hash_value)?;
494
495    // 5) 从 secret_key + keyDataSalt 派生数据加密密钥
496    let data_hash = agile_hash_key(&secret_key, &key_data_salt, spin_count);
497    // 数据加密不需要 blockKey(§2.3.4.11 第 14 步直接截取)
498    let data_key = data_hash[..AES_KEY_LEN].to_vec();
499
500    // 6) 加密原始 ZIP 数据
501    // IV = keyDataSalt 的前 16 字节(规范要求)
502    let data_iv = iv_from_salt(&key_data_salt);
503
504    // EncryptedPackage 格式:4 字节 LE uint32 原始长度 + 加密数据
505    let original_len = zip_bytes.len() as u32;
506    let encrypted_data = aes_256_cbc_encrypt(&data_key, &data_iv, zip_bytes)?;
507
508    let mut encrypted_package = Vec::with_capacity(4 + encrypted_data.len());
509    encrypted_package.extend_from_slice(&original_len.to_le_bytes());
510    encrypted_package.extend_from_slice(&encrypted_data);
511
512    // 7) 构造 EncryptionInfo XML
513    let encryption_info_xml = build_encryption_info_xml(
514        &password_salt,
515        &key_data_salt,
516        &encrypted_key,
517        &encrypted_verifier_hash_input,
518        &encrypted_verifier_hash_value,
519        spin_count,
520    );
521
522    // 8) 打包为新的 ZIP
523    build_encrypted_zip(&encryption_info_xml, &encrypted_package)
524}
525
526/// 解密加密的 ZIP 包。
527///
528/// # 流程(MS-OFFCRYPTO §2.3.4.11 解密方向)
529///
530/// 1. 解析加密的 ZIP,读取 EncryptionInfo + EncryptedPackage;
531/// 2. 从密码派生哈希 + blockKey 派生解密 secret_key 的密钥;
532/// 3. IV = passwordSalt 的前 16 字节;
533/// 4. 解密得到 secret_key;
534/// 5. 验证密码(可选):解密 verifierHashInput/Value 并验证;
535/// 6. 从 secret_key + keyDataSalt 派生数据解密密钥;
536/// 7. IV = keyDataSalt 的前 16 字节;
537/// 8. 解密数据。
538///
539/// # 参数
540/// - `encrypted_bytes`:加密的 ZIP 字节流;
541/// - `password`:解密密码。
542///
543/// # 返回
544/// 解密后的原始 ZIP 字节流(可被 `Presentation::load_bytes` 加载)。
545pub fn decrypt_package(encrypted_bytes: &[u8], password: &str) -> Result<Vec<u8>> {
546    // 1) 解析加密的 ZIP
547    let cursor = std::io::Cursor::new(encrypted_bytes);
548    let mut zip = zip::ZipArchive::new(cursor)
549        .map_err(|e| Error::encryption(format!("encrypted zip open failed: {}", e)))?;
550
551    // 2) 读取 EncryptionInfo
552    let mut info_xml = String::new();
553    let mut info_found = false;
554    for i in 0..zip.len() {
555        let mut entry = zip
556            .by_index(i)
557            .map_err(|e| Error::encryption(format!("zip read failed: {}", e)))?;
558        if entry.name().contains("EncryptionInfo") {
559            use std::io::Read;
560            entry
561                .read_to_string(&mut info_xml)
562                .map_err(|e| Error::encryption(format!("read EncryptionInfo failed: {}", e)))?;
563            info_found = true;
564            break;
565        }
566    }
567    if !info_found {
568        return Err(Error::encryption("EncryptionInfo not found in package"));
569    }
570
571    // 3) 解析 EncryptionInfo XML
572    let params = parse_encryption_info(&info_xml)?;
573
574    // 4) 读取 EncryptedPackage
575    let mut package_data = Vec::new();
576    let mut package_found = false;
577    for i in 0..zip.len() {
578        let mut entry = zip
579            .by_index(i)
580            .map_err(|e| Error::encryption(format!("zip read failed: {}", e)))?;
581        if entry.name().contains("EncryptedPackage") {
582            use std::io::Read;
583            entry
584                .read_to_end(&mut package_data)
585                .map_err(|e| Error::encryption(format!("read EncryptedPackage failed: {}", e)))?;
586            package_found = true;
587            break;
588        }
589    }
590    if !package_found {
591        return Err(Error::encryption("EncryptedPackage not found in package"));
592    }
593
594    // 5) 从密码派生哈希(Agile Encryption 迭代顺序:iterator 在前)
595    let password_salt = base64::engine::general_purpose::STANDARD
596        .decode(&params.password_salt_b64)
597        .map_err(|e| Error::encryption(format!("password salt decode failed: {}", e)))?;
598    let password_hash = agile_hash_password(password, &password_salt, params.spin_count);
599
600    // 6) 用 blockKey 派生解密 secret_key 的密钥
601    let key_encrypt_key =
602        derive_key_with_block_key(&password_hash, BLOCK_KEY_ENCRYPTED_KEY_VALUE, AES_KEY_LEN);
603    // IV = passwordSalt 的前 16 字节
604    let key_iv = iv_from_salt(&password_salt);
605
606    let encrypted_key_value = base64::engine::general_purpose::STANDARD
607        .decode(&params.encrypted_key_value_b64)
608        .map_err(|e| Error::encryption(format!("encrypted key decode failed: {}", e)))?;
609    let secret_key = aes_256_cbc_decrypt(&key_encrypt_key, &key_iv, &encrypted_key_value)?;
610
611    // 7) 验证密码(解密 verifier 并校验)
612    if !params.encrypted_verifier_hash_input_b64.is_empty()
613        && !params.encrypted_verifier_hash_value_b64.is_empty()
614    {
615        let verifier_input_key =
616            derive_key_with_block_key(&password_hash, BLOCK_KEY_VERIFIER_HASH_INPUT, AES_KEY_LEN);
617        let verifier_value_key =
618            derive_key_with_block_key(&password_hash, BLOCK_KEY_VERIFIER_HASH_VALUE, AES_KEY_LEN);
619
620        let encrypted_verifier_input = base64::engine::general_purpose::STANDARD
621            .decode(&params.encrypted_verifier_hash_input_b64)
622            .map_err(|e| {
623                Error::encryption(format!("encrypted verifier input decode failed: {}", e))
624            })?;
625        let encrypted_verifier_value = base64::engine::general_purpose::STANDARD
626            .decode(&params.encrypted_verifier_hash_value_b64)
627            .map_err(|e| {
628                Error::encryption(format!("encrypted verifier value decode failed: {}", e))
629            })?;
630
631        let verifier_input =
632            aes_256_cbc_decrypt(&verifier_input_key, &key_iv, &encrypted_verifier_input)?;
633        let verifier_value =
634            aes_256_cbc_decrypt(&verifier_value_key, &key_iv, &encrypted_verifier_value)?;
635
636        // 验证:SHA-512(verifierHashInput) 应等于 verifierHashValue
637        let expected_hash = {
638            let mut hasher = Sha512::new();
639            hasher.update(&verifier_input);
640            hasher.finalize().to_vec()
641        };
642        if expected_hash != verifier_value {
643            return Err(Error::encryption("password verification failed"));
644        }
645    }
646
647    // 8) 从 secret_key + keyDataSalt 派生数据解密密钥
648    let key_data_salt = base64::engine::general_purpose::STANDARD
649        .decode(&params.data_salt_b64)
650        .map_err(|e| Error::encryption(format!("data salt decode failed: {}", e)))?;
651    let data_hash = agile_hash_key(&secret_key, &key_data_salt, params.spin_count);
652    let data_key = data_hash[..AES_KEY_LEN].to_vec();
653
654    // 9) 解密数据
655    // IV = keyDataSalt 的前 16 字节
656    let data_iv = iv_from_salt(&key_data_salt);
657
658    // EncryptedPackage 格式:4 字节 LE uint32 原始长度 + 加密数据
659    if package_data.len() < 4 {
660        return Err(Error::encryption("EncryptedPackage too short"));
661    }
662    let _original_len = u32::from_le_bytes([
663        package_data[0],
664        package_data[1],
665        package_data[2],
666        package_data[3],
667    ]);
668    let encrypted_data = &package_data[4..];
669
670    let decrypted = aes_256_cbc_decrypt(&data_key, &data_iv, encrypted_data)?;
671
672    Ok(decrypted)
673}
674
675/// 检查字节流是否为加密的 OOXML 文档。
676///
677/// 加密文档的特征:ZIP 中包含 `EncryptionInfo` 条目。
678pub fn is_encrypted_package(bytes: &[u8]) -> bool {
679    let cursor = std::io::Cursor::new(bytes);
680    match zip::ZipArchive::new(cursor) {
681        Ok(mut zip) => {
682            for i in 0..zip.len() {
683                if let Ok(entry) = zip.by_index(i) {
684                    if entry.name().contains("EncryptionInfo") {
685                        return true;
686                    }
687                }
688            }
689            false
690        }
691        Err(_) => false,
692    }
693}
694
695// ---------------------------------------------------------------------------
696// EncryptionInfo XML 构造与解析
697// ---------------------------------------------------------------------------
698
699/// 解析后的 EncryptionInfo 参数。
700#[derive(Clone, Debug)]
701struct ParsedEncryptionInfo {
702    password_salt_b64: String,
703    data_salt_b64: String,
704    encrypted_key_value_b64: String,
705    encrypted_verifier_hash_input_b64: String,
706    encrypted_verifier_hash_value_b64: String,
707    spin_count: u32,
708}
709
710/// 从 EncryptionInfo XML 解析加密参数。
711///
712/// 解析 `<encryption>` XML 中的关键属性:
713/// - `keyData` 的 `saltValue`(keyDataSalt)
714/// - `encryptedKey` 的 `saltValue`(passwordSalt)、`spinCount`、`encryptedKeyValue`
715/// - `encryptedKey` 的 `encryptedVerifierHashInput`、`encryptedVerifierHashValue`
716fn parse_encryption_info(xml: &str) -> Result<ParsedEncryptionInfo> {
717    use quick_xml::events::Event;
718    use quick_xml::reader::Reader;
719
720    let mut rd = Reader::from_str(xml);
721    rd.config_mut().trim_text(true);
722    let mut buf = Vec::new();
723
724    let mut password_salt_b64 = String::new();
725    let mut data_salt_b64 = String::new();
726    let mut encrypted_key_value_b64 = String::new();
727    let mut encrypted_verifier_hash_input_b64 = String::new();
728    let mut encrypted_verifier_hash_value_b64 = String::new();
729    let mut spin_count: u32 = AGILE_SPIN_COUNT;
730
731    loop {
732        match rd.read_event_into(&mut buf) {
733            Ok(Event::Empty(e)) | Ok(Event::Start(e)) => {
734                let name = e.name();
735                let local = local_name(name.as_ref());
736                if local == b"keyData" {
737                    for attr in e.attributes().flatten() {
738                        if attr.key.as_ref() == b"saltValue" {
739                            data_salt_b64 = attr_unescape_value(&attr);
740                        }
741                    }
742                } else if local == b"encryptedKey" {
743                    for attr in e.attributes().flatten() {
744                        let key = attr.key.as_ref();
745                        if key == b"saltValue" {
746                            password_salt_b64 = attr_unescape_value(&attr);
747                        } else if key == b"spinCount" {
748                            let v = attr_unescape_value(&attr);
749                            spin_count = v.parse::<u32>().unwrap_or(AGILE_SPIN_COUNT);
750                        } else if key == b"encryptedKeyValue" {
751                            encrypted_key_value_b64 = attr_unescape_value(&attr);
752                        } else if key == b"encryptedVerifierHashInput" {
753                            encrypted_verifier_hash_input_b64 = attr_unescape_value(&attr);
754                        } else if key == b"encryptedVerifierHashValue" {
755                            encrypted_verifier_hash_value_b64 = attr_unescape_value(&attr);
756                        }
757                    }
758                }
759            }
760            Ok(Event::Eof) => break,
761            Err(e) => {
762                return Err(Error::encryption(format!(
763                    "EncryptionInfo XML parse error: {}",
764                    e
765                )))
766            }
767            _ => {}
768        }
769    }
770
771    Ok(ParsedEncryptionInfo {
772        password_salt_b64,
773        data_salt_b64,
774        encrypted_key_value_b64,
775        encrypted_verifier_hash_input_b64,
776        encrypted_verifier_hash_value_b64,
777        spin_count,
778    })
779}
780
781/// 从 QName 中提取 local name(去除命名空间前缀)。
782///
783/// 与 `oxml/parse_sld.rs` 中的同名函数逻辑一致。
784fn local_name(name: &[u8]) -> &[u8] {
785    match name.iter().position(|&c| c == b':') {
786        Some(i) => &name[i + 1..],
787        None => name,
788    }
789}
790
791/// 从 Attribute 中安全提取解码后的值字符串。
792fn attr_unescape_value(attr: &quick_xml::events::attributes::Attribute<'_>) -> String {
793    attr.normalized_value(quick_xml::XmlVersion::Implicit1_0)
794        .map(|cow| cow.into_owned())
795        .unwrap_or_else(|_| String::from_utf8_lossy(attr.value.as_ref()).into_owned())
796}
797
798/// 构造 EncryptionInfo XML(MS-OFFCRYPTO §2.3.4.10)。
799///
800/// XML 结构:
801/// ```xml
802/// <encryption>
803///   <keyData ... />
804///   <dataIntegrity ... />
805///   <keyEncryptors>
806///     <keyEncryptor uri="...">
807///       <p:encryptedKey ... />
808///     </keyEncryptor>
809///   </keyEncryptors>
810/// </encryption>
811/// ```
812///
813/// 关键属性说明:
814/// - `keyData`:数据加密参数(saltValue = keyDataSalt)
815/// - `dataIntegrity`:HMAC 完整性校验(当前为空,PowerPoint 接受空值)
816/// - `encryptedKey`:密码加密参数,包含:
817///   - `saltValue`:passwordSalt
818///   - `encryptedKeyValue`:加密后的 secret_key
819///   - `encryptedVerifierHashInput`:加密后的验证器输入
820///   - `encryptedVerifierHashValue`:加密后的验证器哈希值
821fn build_encryption_info_xml(
822    password_salt: &[u8],
823    key_data_salt: &[u8],
824    encrypted_key: &[u8],
825    encrypted_verifier_hash_input: &[u8],
826    encrypted_verifier_hash_value: &[u8],
827    spin_count: u32,
828) -> String {
829    let password_salt_b64 = base64::engine::general_purpose::STANDARD.encode(password_salt);
830    let key_data_salt_b64 = base64::engine::general_purpose::STANDARD.encode(key_data_salt);
831    let encrypted_key_b64 = base64::engine::general_purpose::STANDARD.encode(encrypted_key);
832    let encrypted_verifier_hash_input_b64 =
833        base64::engine::general_purpose::STANDARD.encode(encrypted_verifier_hash_input);
834    let encrypted_verifier_hash_value_b64 =
835        base64::engine::general_purpose::STANDARD.encode(encrypted_verifier_hash_value);
836
837    format!(
838        r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
839<encryption xmlns="http://schemas.microsoft.com/office/2006/encryption" xmlns:p="http://schemas.microsoft.com/office/2006/keyEncryptor/password">
840  <keyData saltSize="{salt_size}" blockSize="16" keyBits="256" hashSize="64" cipherAlgorithm="AES" cipherChaining="ChainingModeCBC" hashAlgorithm="SHA512" saltValue="{key_data_salt_b64}" />
841  <dataIntegrity encryptedHmacKey="" encryptedHmacValue="" algorithmIdentifier="SHA512" />
842  <keyEncryptors>
843    <keyEncryptor uri="http://schemas.microsoft.com/office/2006/keyEncryptor/password">
844      <p:encryptedKey spinCount="{spin_count}" saltSize="{salt_size}" saltValue="{password_salt_b64}" hashAlgorithm="SHA512" cipherAlgorithm="AES" keyBits="256" blockSize="16" encryptedKeyValue="{encrypted_key_b64}" encryptedVerifierHashInput="{encrypted_verifier_hash_input_b64}" encryptedVerifierHashValue="{encrypted_verifier_hash_value_b64}" />
845    </keyEncryptor>
846  </keyEncryptors>
847</encryption>"#,
848        salt_size = SALT_LEN,
849        spin_count = spin_count,
850        key_data_salt_b64 = key_data_salt_b64,
851        password_salt_b64 = password_salt_b64,
852        encrypted_key_b64 = encrypted_key_b64,
853        encrypted_verifier_hash_input_b64 = encrypted_verifier_hash_input_b64,
854        encrypted_verifier_hash_value_b64 = encrypted_verifier_hash_value_b64,
855    )
856}
857
858/// 构造加密后的 ZIP 文件。
859///
860/// ZIP 内包含:
861/// - `[Content_Types].xml`
862/// - `_rels/.rels`
863/// - `EncryptionInfo`
864/// - `EncryptedPackage`
865fn build_encrypted_zip(encryption_info_xml: &str, encrypted_package: &[u8]) -> Result<Vec<u8>> {
866    let mut buf = Vec::new();
867    // 使用 block 确保 Cursor 在访问 buf 之前被 drop(与 opc/package.rs 保持一致)
868    {
869        let cursor = std::io::Cursor::new(&mut buf);
870        let mut zip = zip::ZipWriter::new(cursor);
871        let opts: zip::write::FileOptions<'_, ()> = zip::write::FileOptions::default()
872            .compression_method(zip::CompressionMethod::Deflated)
873            .unix_permissions(0o644);
874
875        // [Content_Types].xml
876        let ct_xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
877<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
878  <Default Extension="bin" ContentType="application/vnd.ms-office.encryptedPackage" />
879</Types>"#;
880        zip.start_file("[Content_Types].xml", opts)
881            .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
882        zip.write_all(ct_xml.as_bytes())
883            .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
884
885        // _rels/.rels
886        let rels_xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
887<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
888  <Relationship Id="rId1" Type="http://schemas.microsoft.com/office/2006/relationships/encryption" Target="EncryptionInfo" />
889</Relationships>"#;
890        zip.start_file("_rels/.rels", opts)
891            .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
892        zip.write_all(rels_xml.as_bytes())
893            .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
894
895        // EncryptionInfo
896        zip.start_file("EncryptionInfo", opts)
897            .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
898        zip.write_all(encryption_info_xml.as_bytes())
899            .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
900
901        // EncryptedPackage
902        zip.start_file("EncryptedPackage", opts)
903            .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
904        zip.write_all(encrypted_package)
905            .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
906
907        zip.finish()
908            .map_err(|e| Error::encryption(format!("zip finish failed: {}", e)))?;
909    }
910    Ok(buf)
911}
912
913use std::io::Write;