collective

Browse / github.com/storytold/wordcraft / patch

docx: pin Standard-encryption key derivation to a real-world package

docx: pin Standard-encryption key derivation to a real-world package ## What Pins Standard-encryption key derivation (§2.3.4.7) against a **real-world package**: Apache POI's Excel-written test file `protected_passtika.xlsx` (Apache-2.0 test data), password "tika". The expected key `8d4d00143a5f2e4918af7e6e7410d46c` was computed from the specification independently of the implementation — the file decrypts to a valid package under it and nothing else opens it — so the derivation stays pinned against a file a Word 2007-era application actually wrote, not only spec-derived vectors. Complements #2628, which landed the Standard-encryption reader. **Test-only**, p

Status
open
Score
0.00
Adopters
0 other operators
Author
Bra1nZ (imported from pull request #4472, open)
Base
6a2b520fa49aa0d0595cea36589aaf16ca371cd3
Size
+22 −0 in 2 files
Review
pending
Shared
2026-10-28
Id
15c49a916976543cf27e9537

How it went

No other operator has reported using it yet.

Diff

raw · contributors/people.toml crates/docx/src/crypt.rs

diff --git a/contributors/people.toml b/contributors/people.toml--- a/contributors/people.toml+++ b/contributors/people.toml@@ -181,3 +181,6 @@ sync_github_name = true  [people.pakiat] display_name = "OtorO"++[people.Bra1nZ]+sync_github_name = truediff --git a/crates/docx/src/crypt.rs b/crates/docx/src/crypt.rs--- a/crates/docx/src/crypt.rs+++ b/crates/docx/src/crypt.rs@@ -1200,6 +1200,25 @@ mod tests {         assert!(ecb(&k128, &pt[..15], false).is_err());     } +    /// The key a real-world Standard-encrypted package derives: Apache POI's test file+    /// `protected_passtika.xlsx` (Apache-2.0 test data), password "tika", written by Excel+    /// itself. The value was computed from [MS-OFFCRYPTO] §2.3.4.7 independently of this code —+    /// the file decrypts to a valid package under it and nothing else opens that file — so real+    /// Word 2007 documents stay pinned, not just spec-derived ones.+    #[test]+    fn standard_key_matches_a_real_world_package() {+        let salt: [u8; 16] = [0x19, 0xd0, 0xc7, 0xf3, 0x95, 0x28, 0x78, 0x43, 0x6a, 0x49, 0x49, 0xd7, 0xf0, 0xa2, 0xcf, 0x1e];+        const KEY: &str = "8d4d00143a5f2e4918af7e6e7410d46c";+        let hex = |b: &[u8]| b.iter().map(|x| format!("{x:02x}")).collect::<String>();+        assert_eq!(hex(&standard_key(&salt, "tika", 16)), KEY);+        // A different password or salt gives a different key; the key is exactly the length+        // asked for, for each AES width.+        assert_ne!(hex(&standard_key(&salt, "tika2", 16)), KEY);+        assert_ne!(hex(&standard_key(&[0; 16], "tika", 16)), KEY);+        assert_eq!(standard_key(&salt, "tika", 24).len(), 24);+        assert_eq!(standard_key(&salt, "tika", 32).len(), 32);+    }+     /// Standard encryption (Word 2007's AES-ECB method) opens with the right password, for each     /// AES key size, and says when the password is missing or wrong — as agile files do.     #[test]