ECMA-376 Standard Encryption¶
🟡 Legacy Encryption — password-based document-open encryption for OOXML containers (Office 2007 default, superseded by Agile)
Overview¶
- Binary
EncryptionInfostructure (not XML) with fixed algorithms: SHA-1 hash, AES cipher, ECB mode — no algorithm negotiation - KDF is a bespoke 50,000-iteration SHA-1 construction with ipad/opad key expansion, loosely based on PKCS#5 v2.0 but not standard PBKDF2
- AES key lengths of 128, 192, or 256 bits; only the key size varies between profiles
- Default for Office 2007; Office 2010+ defaults to ECMA-376 Agile Encryption but can still produce standard encryption in compatibility mode
- Used by: Word, Excel, PowerPoint, OneNote
- Spec: [MS-OFFCRYPTO] SS2.3.4.5--SS2.3.4.9
KDF Pipeline¶
flowchart LR
A["Password\n(UTF-16LE, no NUL)"] --> B["H0 = SHA-1(salt || pw)\nsalt = 16 bytes"]
B --> C["Spin Loop\n50,000 iterations\nHn = SHA-1(LE32(i) || Hn-1)\ni = 0 .. 49999"]
C --> D["Hfinal = SHA-1(Hn || LE32(0x00000000))\nblock key = 0"]
D --> E["ipad: SHA-1((0x36*64) XOR Hfinal) = X1\nopad: SHA-1((0x5C*64) XOR Hfinal) = X2"]
E --> F["X3 = X1 || X2\n(40 bytes)"]
F --> G["keyDerived = X3[0:keyLen]\nAES-128: 16B\nAES-192: 24B\nAES-256: 32B"]
Verification Flow¶
flowchart TD
A["Derive AES key\nfrom password + salt"] --> B["AES-ECB Decrypt\nEncryptedVerifier\n(16 bytes)"]
B --> C["plaintext verifier\n(16 bytes)"]
C --> D["SHA-1(verifier)\n= 20 bytes"]
A --> E["AES-ECB Decrypt\nEncryptedVerifierHash\n(32 bytes)"]
E --> F["storedHash =\ndecrypted[0:20]"]
D --> G{"SHA-1(verifier)\n==\nstoredHash?"}
F --> G
G -->|"Yes"| H["Password correct\nUse same key to\ndecrypt EncryptedPackage"]
G -->|"No"| I["Wrong password"]
On-Disk Structure¶
flowchart LR
subgraph OLE["OLE Compound File"]
direction TB
DS["0x06DataSpaces\nStrongEncryptionDataSpace\nStrongEncryptionTransform"]
EI["EncryptionInfo Stream"]
EP["EncryptedPackage Stream"]
DS --> EI --> EP
end
subgraph EIS["EncryptionInfo Layout"]
direction TB
VER["Version (4 bytes)\nvMajor=0x0003/0x0004\nvMinor=0x0002"]
FLAGS["Flags copy (4 bytes)\n0x00000024"]
HDRSZ["HeaderSize (4 bytes)"]
HDR["EncryptionHeader\nAlgID: 0x660E/F/10\nAlgIDHash: 0x8004\nKeySize, CSPName"]
VERIF["EncryptionVerifier\nSalt (16B)\nEncryptedVerifier (16B)\nEncryptedVerifierHash (32B)"]
VER --> FLAGS --> HDRSZ --> HDR --> VERIF
end
subgraph EPS["EncryptedPackage Layout"]
direction TB
SS["StreamSize (8 bytes, u64 LE)"]
ED["Encrypted OOXML ZIP\nAES-ECB, padded to\n16-byte boundary"]
SS --> ED
end
EI --> VER
EP --> SS
Quick Reference¶
| Parameter | Value | Notes |
|---|---|---|
| Hash | SHA-1 (20 B) | Fixed, no alternatives |
| Cipher | AES-128 / AES-192 / AES-256 | ECB mode, fixed |
| Iteration Count | 50,000 | Counter 0..49999, LE32, prepended |
| Salt | 16 bytes | Random, in EncryptionVerifier.Salt |
| Block Key | 0x00000000 | Single scalar, appended to Hn |
| ipad / opad | 0x36 / 0x5C | 64-byte buffers XORed with Hfinal |
| Password | UTF-16LE, no NUL | Max 255 Unicode code points |
| vMinor | 0x0002 | Distinguishes from Agile (0x0004) |
| Flags | 0x00000024 | fCryptoAPI + fAES |
Related Algorithms¶
| Algorithm | Relationship |
|---|---|
| ECMA-376 Agile | Successor — CBC mode, configurable hash/cipher, XML descriptor |
| RC4 CryptoAPI | Shares EncryptionHeader/EncryptionVerifier structures; RC4 + un-iterated KDF |
| RC4 MD5 Legacy | 40-bit RC4 encryption for binary documents |
| XOR Obfuscation | Weakest legacy scheme |
| ISO Protection Hash | Similar iterated hash but iterator APPENDED (not prepended) |
Extraction Target¶
Fields to extract from the \EncryptionInfo binary stream:
| Field | Source | Type | Example (AES-128) |
|---|---|---|---|
| Salt | EncryptionVerifier.Salt (offset +4 into verifier) |
16 raw bytes | a3 f1 ... (random) |
| Key size | EncryptionHeader.KeySize |
u32 LE | 0x00000080 (128) |
| AlgID | EncryptionHeader.AlgID |
s32 LE | 0x0000660E |
| Encrypted verifier | EncryptionVerifier.EncryptedVerifier |
16 raw bytes | (ciphertext) |
| Encrypted verifier hash | EncryptionVerifier.EncryptedVerifierHash |
32 raw bytes | (ciphertext) |
| Verifier hash size | EncryptionVerifier.VerifierHashSize |
u32 LE | 0x00000014 (20) |
| Version minor | EncryptionVersionInfo.vMinor |
u16 LE | 0x0002 |
Python function signatures:
def derive_standard_key(
password: str,
salt: bytes, # EncryptionVerifier.Salt, 16 bytes
key_bits: int, # EncryptionHeader.KeySize: 128, 192, or 256
) -> bytes: ... # Returns AES key (key_bits // 8 bytes)
def verify_standard(
password: str,
salt: bytes, # EncryptionVerifier.Salt, 16 bytes
key_bits: int, # EncryptionHeader.KeySize
enc_verifier: bytes, # EncryptionVerifier.EncryptedVerifier, 16 bytes
enc_verifier_hash: bytes, # EncryptionVerifier.EncryptedVerifierHash, 32 bytes
) -> bool: ... # True if password is correct
1. What it is and where it is used¶
- ECMA-376 Standard Encryption is one of the three sub-approaches defined under ECMA-376 Document Encryption in [MS-OFFCRYPTO] section 1.3.3.4
- Uses a binary
EncryptionInfostructure (as opposed to the XML descriptor used by agile encryption) - Derives an AES encryption key from a user-supplied password via 50,000 iterations of SHA-1
- Encrypts the entire OOXML package as a single stream using AES in ECB mode ([MS-OFFCRYPTO] section 2.3.4.7)
- Hash algorithm is fixed at SHA-1 and cipher is fixed at AES; no other algorithms are permitted ([MS-OFFCRYPTO] section 2.3.4.5)
- The KDF is described by the spec as "derived from PKCS #5: Password-Based Cryptography Version 2.0 [RFC2898]" but is not standard PBKDF2 — it is a bespoke iterated SHA-1 construction with an ipad/opad key-expansion step ([MS-OFFCRYPTO] section 2.3.4.7)
Standard encryption protects the document-open (read) password for ECMA-376 [ECMA-376] documents:
- Office Open XML files (
.docx,.xlsx,.pptxand their macro-enabled counterparts.docm,.xlsm,.pptm) stored as encrypted OLE Compound File Binary (CFB) containers - Does not protect modify/write-reservation passwords, sheet/workbook protection, or VBA project passwords (those are separate mechanisms)
- When standard encryption is applied, the on-disk file is not a ZIP/OPC package but an OLE compound file ([MS-CFB]) whose root storage contains the
\EncryptionInfoand\EncryptedPackagestreams along with the\0x06DataSpacesstorage ([MS-OFFCRYPTO] section 2.3.4)
Products that use standard encryption:
- Office 2007 writes standard encryption by default for password-protected OOXML files
- Office 2010 and later default to agile encryption (ecma376-agile.md) but can produce standard encryption when explicitly selected or when compatibility mode dictates ([MS-OFFCRYPTO] section 2.3.4.5, note <9>)
- Third-party producers (Apache POI, LibreOffice, custom OOXML implementations) may also emit standard encryption
- The same standard KDF and verifier path is reused by OneNote password protection (onenote.md)
- For format-level details of where each application stores its password protection trigger, see word.md, excel.md, and powerpoint.md
Related schemes:
- Successor: agile encryption (ecma376-agile.md, [MS-OFFCRYPTO] section 2.3.4.10)
- Predecessors replaced by standard encryption: CryptoAPI RC4 (rc4-cryptoapi-sha1.md, section 2.3.5), 40-bit RC4 (rc4-md5-legacy.md, section 2.3.6), XOR obfuscation (xor-obfuscation.md, section 2.3.7)
- Extensible (third-party) encryption ([MS-OFFCRYPTO] section 2.3.4.6) is a distinct sub-approach using a custom CSP and is not covered by this document
2. Profiles / parameters¶
All profiles share the same KDF (SHA-1, 50000-round iteration, ipad/opad key expansion), the same salt size (16 bytes), the same verifier hash algorithm (SHA-1, 20-byte output), and AES in ECB mode. They differ only in the AES key length. Standard encryption is AES-only and SHA-1-only: AlgID MUST be one of the AES values and AlgIDHash MUST be 0x00008004 ([MS-OFFCRYPTO] section 2.3.4.5).
| Parameter | AES-128 | AES-192 | AES-256 | Spec reference |
|---|---|---|---|---|
| Hash algorithm | SHA-1 | SHA-1 | SHA-1 | section 2.3.4.5, 2.3.4.7 |
AlgIDHash |
0x00008004 | 0x00008004 | 0x00008004 | section 2.3.2 |
| Iteration count | 50000 | 50000 | 50000 | section 2.3.4.7 |
| Cipher | AES-128 | AES-192 | AES-256 | section 2.3.4.5 |
| Cipher mode | ECB | ECB | ECB | section 2.3.4.7 |
AlgID |
0x0000660E | 0x0000660F | 0x00006610 | section 2.3.2 |
KeySize (bits) |
0x00000080 (128) | 0x000000C0 (192) | 0x00000100 (256) | section 2.3.2 |
| Derived key length (bytes) | 16 | 24 | 32 | section 2.3.4.7 |
| Key material used | X1[0:16] |
X1[0:20] + X2[0:4] |
X1[0:20] + X2[0:12] |
section 2.3.4.7 |
ProviderType |
0x00000018 | 0x00000018 | 0x00000018 | section 2.3.2, note <8> |
| Salt size | 16 bytes | 16 bytes | 16 bytes | section 2.3.3 |
SaltSize field |
0x00000010 | 0x00000010 | 0x00000010 | section 2.3.3 |
VerifierHashSize |
0x00000014 (20) | 0x00000014 (20) | 0x00000014 (20) | section 2.3.3 |
EncryptedVerifier size |
16 bytes | 16 bytes | 16 bytes | section 2.3.3 |
EncryptedVerifierHash size |
32 bytes | 32 bytes | 32 bytes | section 2.3.3 |
EncryptionVersionInfo version values:
vMajorMUST be 0x0002, 0x0003, or 0x0004;vMinorMUST be 0x0002 ([MS-OFFCRYPTO] section 2.3.4.5)- The
vMinorvalue 0x0002 distinguishes standard encryption from agile encryption (vMinor0x0004, [MS-OFFCRYPTO] section 2.3.4.10) - Office 2007 RTM writes
vMajor0x0003; Office 2007 SP2, Office 2010, and Office 2013 writevMajor0x0004 ([MS-OFFCRYPTO] section 2.3.4.5, note <9>)
Password character encoding:
- Passwords MUST be provided as an array of Unicode characters ([MS-OFFCRYPTO] section 2.3.4.7), in practice UTF-16LE with no trailing NUL terminator hashed
- Passwords are limited to 255 Unicode code points ([MS-OFFCRYPTO] section 4.1.3.1)
- Additional limitations on the length and characters are implementation-dependent ([MS-OFFCRYPTO] section 2.3.4.7)
3. On-disk structure and byte layout¶
3.1 OLE Compound File container¶
An encrypted ECMA-376 document is stored as an OLE compound file ([MS-CFB]) rather than a ZIP/OPC package. The container uses the data spaces construct ([MS-OFFCRYPTO] section 2.1) and MUST contain the following root-level streams and storages ([MS-OFFCRYPTO] sections 2.3.4.1–2.3.4.5):
\0x06DataSpacesstorage — data space metadata (section 2.1.1)\EncryptionInfostream — cryptographic parameters (section 2.3.4.5)\EncryptedPackagestream — the encrypted OOXML ZIP payload (section 2.3.4.4)
All multi-byte integer values in these structures are little-endian ([MS-OFFCRYPTO] section 1.3.6).
3.2 DataSpaces structure for ECMA-376 encryption¶
The \0x06DataSpaces storage MUST be populated as follows ([MS-OFFCRYPTO] sections 2.3.4.1–2.3.4.3):
DataSpaceMap ([MS-OFFCRYPTO] section 2.3.4.1): The \0x06DataSpaces\DataSpaceMap stream MUST contain a DataSpaceMap structure (section 2.1.6) containing exactly one DataSpaceMapEntry (section 2.1.6.1). The entry MUST have DataSpaceName equal to "StrongEncryptionDataSpace" and exactly one ReferenceComponents entry with name "EncryptedPackage" and type 0x00000000 (stream).
DataSpaceInfo ([MS-OFFCRYPTO] section 2.3.4.2): The \0x06DataSpaces\DataSpaceInfo storage MUST contain a stream named "StrongEncryptionDataSpace" with a DataSpaceDefinition structure (section 2.1.7) having exactly one TransformReferences entry: "StrongEncryptionTransform".
TransformInfo ([MS-OFFCRYPTO] section 2.3.4.3): The \0x06DataSpaces\TransformInfo storage MUST contain a storage named "StrongEncryptionTransform" with a "0x06Primary" stream containing an IRMDSTransformInfo structure (section 2.2.6) with the following fixed values:
| Field | Value | Spec reference |
|---|---|---|
TransformInfoHeader.TransformType |
0x00000001 | section 2.3.4.3 |
TransformInfoHeader.TransformID |
"{FF9A3F03-56EF-4613-BDD5-5A41C1D07246}" |
section 2.3.4.3 |
TransformInfoHeader.TransformName |
"Microsoft.Container.EncryptionTransform" |
section 2.3.4.3 |
TransformInfoHeader.ReaderVersion |
"1.0" |
section 2.3.4.3 |
TransformInfoHeader.UpdaterVersion |
"1.0" |
section 2.3.4.3 |
TransformInfoHeader.WriterVersion |
"1.0" |
section 2.3.4.3 |
Following the IRMDSTransformInfo structure, an EncryptionTransformInfo structure (section 2.1.9) MUST exist specifying the encryption algorithm. However, if the algorithms specified in EncryptionTransformInfo differ from those in the \EncryptionInfo stream, the \EncryptionInfo stream MUST be considered authoritative ([MS-OFFCRYPTO] section 2.3.4.3).
EncryptionTransformInfo structure ([MS-OFFCRYPTO] section 2.1.9):
| Field | Size | Value | Meaning |
|---|---|---|---|
EncryptionName |
variable (UTF-8-LP-P4) | "AES 128", "AES 192", or "AES 256" |
Encryption algorithm name |
EncryptionBlockSize |
4 bytes | 0x00000010 (16) | AES block size |
CipherMode |
4 bytes | 0x00000000 | Reserved (not CBC/CFB; ECB mode is specified elsewhere) |
Reserved |
4 bytes | 0x00000004 | Reserved |
3.3 \EncryptionInfo stream layout (Standard Encryption) — [MS-OFFCRYPTO] section 2.3.4.5¶
| Field | Offset | Size (bytes) | Type | Meaning |
|---|---|---|---|---|
EncryptionVersionInfo.vMajor |
0 | 2 | u16 LE | MUST be 0x0002, 0x0003, or 0x0004 |
EncryptionVersionInfo.vMinor |
2 | 2 | u16 LE | MUST be 0x0002 |
EncryptionHeader.Flags (copy) |
4 | 4 | u32 LE | Copy of Flags from the EncryptionHeader that follows |
EncryptionHeaderSize |
8 | 4 | u32 LE | Size in bytes of the EncryptionHeader structure that follows |
EncryptionHeader |
12 | EncryptionHeaderSize |
struct | See section 3.4 |
EncryptionVerifier |
12 + EncryptionHeaderSize |
72 | struct | See section 3.5 |
3.4 EncryptionHeader structure — [MS-OFFCRYPTO] section 2.3.2¶
Offsets are relative to the start of the EncryptionHeader (stream offset 12).
| Field | Offset | Size (bytes) | Type | Value for standard AES |
|---|---|---|---|---|
Flags |
0 | 4 | u32 LE (EncryptionHeaderFlags) | fCryptoAPI and fAES bits MUST be set; fDocProps MUST be 0 |
SizeExtra |
4 | 4 | u32 LE | MUST be 0x00000000 |
AlgID |
8 | 4 | s32 LE | 0x0000660E (AES-128), 0x0000660F (AES-192), or 0x00006610 (AES-256) |
AlgIDHash |
12 | 4 | s32 LE | MUST be 0x00008004 (SHA-1) |
KeySize |
16 | 4 | u32 LE | 0x00000080 (128), 0x000000C0 (192), or 0x00000100 (256) |
ProviderType |
20 | 4 | u32 LE | SHOULD be 0x00000018 (AES) |
Reserved1 |
24 | 4 | u32 LE | Undefined, MUST be ignored |
Reserved2 |
28 | 4 | u32 LE | MUST be 0x00000000, MUST be ignored |
CSPName |
32 | variable | UTF-16LE, NUL-terminated | SHOULD be "Microsoft Enhanced RSA and AES Cryptographic Provider" or "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)" (notes <10>, <11>) |
3.4.1 EncryptionHeaderFlags bit layout — [MS-OFFCRYPTO] section 2.3.1¶
The Flags field is a 32-bit little-endian value with the following bit definitions (LSB-first ordering as presented in the spec):
| Bit position | Name | Standard AES value | Meaning |
|---|---|---|---|
| 0 (A) | Reserved1 |
0 | MUST be 0, MUST be ignored |
| 1 (B) | Reserved2 |
0 | MUST be 0, MUST be ignored |
| 2 (C) | fCryptoAPI |
1 | MUST be 1 unless fExternal is 1 |
| 3 (D) | fDocProps |
0 | MUST be 0 if document properties are encrypted |
| 4 (E) | fExternal |
0 | MUST be 1 if extensible encryption is used; if 1, all other bits MUST be 0 |
| 5 (F) | fAES |
1 | MUST be 1 if protected content is an ECMA-376 document; if set, fCryptoAPI MUST also be 1 |
| 6–31 | Unused |
0 | Undefined, MUST be ignored |
For standard AES encryption, the combined Flags value is 0x00000024 (fCryptoAPI | fAES = bits 2 and 5 set).
3.4.2 AlgID and Flags valid combinations for standard encryption — [MS-OFFCRYPTO] section 2.3.2¶
Flags.fCryptoAPI |
Flags.fAES |
Flags.fExternal |
AlgID |
Algorithm |
|---|---|---|---|---|
| 1 | 1 | 0 | 0x00000000 | 128-bit AES (determined by Flags) |
| 1 | 1 | 0 | 0x0000660E | 128-bit AES |
| 1 | 1 | 0 | 0x0000660F | 192-bit AES |
| 1 | 1 | 0 | 0x00006610 | 256-bit AES |
3.4.3 AlgIDHash values — [MS-OFFCRYPTO] section 2.3.2¶
AlgIDHash |
Flags.fExternal |
Algorithm |
|---|---|---|
| 0x00000000 | 0 | SHA-1 |
| 0x00008004 | 0 | SHA-1 |
| 0x00000000 | 1 | Determined by the application (extensible encryption only) |
3.4.4 KeySize values — [MS-OFFCRYPTO] section 2.3.2¶
| Algorithm | KeySize value(s) |
Notes |
|---|---|---|
| Any | 0x00000000 | Determined by Flags |
| AES | 0x00000080, 0x000000C0, 0x00000100 | 128-bit, 192-bit, or 256-bit |
KeySize MUST be a multiple of 8 bits. If Flags does not have fCryptoAPI set, KeySize MUST be 0x00000000 ([MS-OFFCRYPTO] section 2.3.2).
3.4.5 ProviderType values — [MS-OFFCRYPTO] section 2.3.2¶
| Algorithm | Value |
|---|---|
| Any | 0x00000000 (determined by Flags) |
| RC4 | 0x00000001 |
| AES | 0x00000018 |
If Flags does not have fCryptoAPI set, ProviderType MUST be 0x00000000 ([MS-OFFCRYPTO] section 2.3.2).
3.5 EncryptionVerifier structure — [MS-OFFCRYPTO] section 2.3.3¶
Offsets are relative to the start of the EncryptionVerifier (stream offset 12 + EncryptionHeaderSize).
| Field | Offset | Size (bytes) | Type | Meaning |
|---|---|---|---|---|
SaltSize |
0 | 4 | u32 LE | MUST be 0x00000010 (16) |
Salt |
4 | 16 | byte[16] | Random salt used during password hash generation; MUST NOT be the same data used for the verifier |
EncryptedVerifier |
20 | 16 | byte[16] | Random 16-byte Verifier value encrypted using the derived key (one AES block) |
VerifierHashSize |
36 | 4 | u32 LE | Number of bytes needed to contain the hash of the Verifier; MUST be 0x00000014 (20) for SHA-1 |
EncryptedVerifierHash |
40 | 32 | byte[32] | Encrypted hash of the Verifier value; for AES, MUST be 32 bytes (two AES blocks); only the first VerifierHashSize bytes of the decrypted value are meaningful |
The EncryptionVerifier for AES is always 72 bytes total: 4 + 16 + 16 + 4 + 32 = 72 ([MS-OFFCRYPTO] section 2.3.3).
3.6 \EncryptedPackage stream — [MS-OFFCRYPTO] section 2.3.4.4¶
| Field | Offset | Size (bytes) | Type | Meaning |
|---|---|---|---|---|
StreamSize |
0 | 8 | u64 LE | Number of bytes of plaintext data encrypted within EncryptedData, not including the size of StreamSize itself |
EncryptedData |
8 | variable | byte[] | The encrypted OOXML ZIP package, encrypted using the algorithm specified in \EncryptionInfo |
The actual size of the \EncryptedPackage stream can be larger than StreamSize + 8 because the ciphertext is padded to a multiple of the encryption block size (16 bytes for AES) ([MS-OFFCRYPTO] section 2.3.4.4).
4. Key derivation — [MS-OFFCRYPTO] section 2.3.4.7¶
The encryption key for ECMA-376 standard encryption MUST be generated using the following method, which the spec describes as derived from PKCS #5 v2.0 [RFC2898]. Let H() be SHA-1 (as determined by EncryptionHeader.AlgIDHash), let Hn be the hash data of the nth iteration, and let + represent concatenation.
4.1 Initial password hash¶
The password MUST be provided as an array of Unicode characters (UTF-16LE, no trailing NUL). The initial password hash is:
where salt is the 16-byte random value stored in EncryptionVerifier.Salt ([MS-OFFCRYPTO] section 2.3.4.7). The salt MUST be 16 bytes and MUST be stored in the EncryptionVerifier.Salt field.
4.2 Iterated hashing (50000 iterations)¶
The hash is iterated using the following approach:
where iterator is an unsigned 32-bit value, encoded as 4 bytes in little-endian byte order, that is initially set to 0x00000000 and incremented monotonically on each iteration until 50000 iterations have been performed. The value of iterator on the last iteration MUST be 49999 ([MS-OFFCRYPTO] section 2.3.4.7). The iterator value is prepended (not appended) to the previous hash output, making each iteration's input exactly 24 bytes (4-byte counter + 20-byte SHA-1 digest).
4.3 Final hash with block number¶
After the 50000th iteration, the final hash data is produced by appending the block number:
where block is the 4-byte little-endian encoding of block number 0x00000000 ([MS-OFFCRYPTO] section 2.3.4.7). The input to this hash is 24 bytes (20-byte iterated hash + 4-byte block number). The encryption algorithm MUST be specified in EncryptionHeader.AlgID and MUST use ECB mode ([MS-OFFCRYPTO] section 2.3.4.7).
4.4 Key expansion (ipad/opad derivation)¶
The encryption key is derived from Hfinal as follows ([MS-OFFCRYPTO] section 2.3.4.7):
-
Let
cbRequiredKeyLengthbe the required key length in bytes for the encryption algorithm as specified by theEncryptionHeaderstructure. Note thatcbRequiredKeyLengthMUST be less than or equal to 40. -
Let
cbHashbe the number of bytes output by the hashing algorithm H. For SHA-1,cbHash= 20. -
Form a 64-byte buffer by repeating the constant 0x36 sixty-four times. XOR
Hfinalinto the firstcbHashbytes of this buffer. Compute a hash of the resulting 64-byte buffer using hashing algorithm H. This yields a hash value of lengthcbHash. Let the resulting value be calledX1. -
Form another 64-byte buffer by repeating the constant 0x5C sixty-four times. XOR
Hfinalinto the firstcbHashbytes of this buffer. Compute a hash of the resulting 64-byte buffer using hashing algorithm H. This yields a hash value of lengthcbHash. Let the resulting value be calledX2. -
Concatenate
X1withX2to formX3, which will yield a value twice the length ofcbHash(40 bytes). -
Let
keyDerivedbe equal to the firstcbRequiredKeyLengthbytes ofX3.
4.5 Full key derivation pseudocode¶
# ECMA-376 Standard Encryption Key Derivation
# Per [MS-OFFCRYPTO] section 2.3.4.7
#
# Inputs:
# password -- the user password (string)
# salt -- 16 bytes from EncryptionVerifier.Salt
# key_bits -- key size in bits: 128, 192, or 256 (from EncryptionHeader.KeySize)
#
# Output:
# keyDerived -- the AES encryption key (key_bits / 8 bytes)
func derive_key(password, salt, key_bits):
# Step 1: Encode password as UTF-16LE, no trailing NUL
pw = encode_utf16le(password)
# Step 2: Initial hash -- H0 = H(salt + password)
# salt is 16 bytes, pw is 2 * len(password) bytes
h = SHA1(salt || pw) # 20-byte digest
# Step 3: Iterated hashing -- 50000 iterations
# iterator is a 4-byte unsigned integer, little-endian
# iterator starts at 0, increments to 49999
for i in 0 .. 49999:
h = SHA1(le32(i) || h) # 4-byte LE counter prepended to 20-byte digest
# input to each SHA-1 is exactly 24 bytes
# Step 4: Final hash with block number 0
# Hfinal = H(Hn + block), block = 0x00000000 LE
h_final = SHA1(h || le32(0x00000000)) # 20-byte + 4-byte = 24-byte input
# Step 5: Key expansion via ipad/opad
cbHash = 20 # SHA-1 output size
cbRequiredKeyLength = key_bits / 8 # 16, 24, or 32 bytes
# X1: ipad expansion
buf_ipad = bytes([0x36] * 64) # 64-byte buffer filled with 0x36
for k in 0 .. (cbHash - 1):
buf_ipad[k] = buf_ipad[k] XOR h_final[k] # XOR Hfinal into first cbHash bytes
X1 = SHA1(buf_ipad) # 20-byte digest
# X2: opad expansion
buf_opad = bytes([0x5C] * 64) # 64-byte buffer filled with 0x5C
for k in 0 .. (cbHash - 1):
buf_opad[k] = buf_opad[k] XOR h_final[k] # XOR Hfinal into first cbHash bytes
X2 = SHA1(buf_opad) # 20-byte digest
# X3: concatenation
X3 = X1 || X2 # 40 bytes total
# Step 6: Truncate to required key length
keyDerived = X3[0 : cbRequiredKeyLength]
# AES-128: X3[0:16] = X1[0:16]
# AES-192: X3[0:24] = X1[0:20] + X2[0:4]
# AES-256: X3[0:32] = X1[0:20] + X2[0:12]
return keyDerived
4.6 Endianness note¶
SHA-1 processes big-endian 32-bit words internally per FIPS 180, but the iterator counter and block number values fed into the hash input message are 4-byte little-endian values as specified by [MS-OFFCRYPTO] section 2.3.4.7. These are not SHA-1 internal state values but data bytes in the hash input stream.
5. Password verifier generation — [MS-OFFCRYPTO] section 2.3.4.8¶
The password verifier uses the EncryptionVerifier structure ([MS-OFFCRYPTO] section 2.3.3). The EncryptionVerifier MUST be set using the following process ([MS-OFFCRYPTO] section 2.3.3):
# Password Verifier Generation
# Per [MS-OFFCRYPTO] sections 2.3.3 and 2.3.4.8
#
# Inputs:
# password -- the user password
# salt -- 16 random bytes (same salt used in key generation)
# key_bits -- 128, 192, or 256
#
# Outputs:
# EncryptionVerifier structure fields
func generate_verifier(password, salt, key_bits):
# Step 1: Generate random salt, write to EncryptionVerifier.Salt
# salt = random_bytes(16)
# (salt is also used in key derivation)
# Step 2: Derive the encryption key from password and salt, block number 0
key = derive_key(password, salt, key_bits)
# Step 3: Generate 16 bytes of random data as the Verifier
verifier = random_bytes(16)
# Step 4: Encrypt the Verifier, write to EncryptedVerifier
encrypted_verifier = AES_ECB_encrypt(key, verifier) # 16 bytes (one AES block)
# Step 5: Write the hash size
verifier_hash_size = 20 # SHA-1 output length
# Step 6: Hash the Verifier
verifier_hash = SHA1(verifier) # 20 bytes
# Step 7: Encrypt the hash, zero-padded to 32 bytes, write to EncryptedVerifierHash
padded_hash = verifier_hash || bytes([0x00] * 12) # pad to 32 bytes (two AES blocks)
encrypted_verifier_hash = AES_ECB_encrypt(key, padded_hash) # 32 bytes
# Store in EncryptionVerifier structure:
# SaltSize = 0x00000010
# Salt = salt (16 bytes)
# EncryptedVerifier = encrypted_verifier (16 bytes)
# VerifierHashSize = 0x00000014 (20)
# EncryptedVerifierHash = encrypted_verifier_hash (32 bytes)
return (salt, encrypted_verifier, verifier_hash_size, encrypted_verifier_hash)
The password verifier Salt field MUST be equal to the salt created during password key generation ([MS-OFFCRYPTO] section 2.3.4.8). The Salt MUST NOT be the same data used for the verifier stored encrypted in the EncryptedVerifier field ([MS-OFFCRYPTO] section 2.3.3).
6. Password verification / decryption check — [MS-OFFCRYPTO] section 2.3.4.9¶
Passwords MUST be verified using the following steps ([MS-OFFCRYPTO] section 2.3.4.9):
# Password Verification (Standard Encryption)
# Per [MS-OFFCRYPTO] section 2.3.4.9
#
# Inputs:
# password -- candidate password
# salt -- EncryptionVerifier.Salt (16 bytes)
# key_bits -- EncryptionHeader.KeySize (128, 192, or 256)
# encrypted_verifier -- EncryptionVerifier.EncryptedVerifier (16 bytes)
# encrypted_hash -- EncryptionVerifier.EncryptedVerifierHash (32 bytes)
# verifier_hash_size -- EncryptionVerifier.VerifierHashSize (20)
#
# Output:
# True if password is correct, False otherwise
func verify_password(password, salt, key_bits, encrypted_verifier, encrypted_hash, verifier_hash_size):
# Step 1: Generate an encryption key as specified in section 2.3.4.7
key = derive_key(password, salt, key_bits)
# Step 2: Decrypt the EncryptedVerifier to obtain the Verifier value
# The resulting Verifier value MUST be an array of 16 bytes
verifier = AES_ECB_decrypt(key, encrypted_verifier) # 16 bytes
# Step 3: Decrypt the EncryptedVerifierHash to obtain the hash of the Verifier
# The number of bytes used by the encrypted Verifier hash MUST be 32
# The number of bytes used by the decrypted Verifier hash is given by
# VerifierHashSize, which MUST be 20
decrypted_hash = AES_ECB_decrypt(key, encrypted_hash) # 32 bytes
stored_hash = decrypted_hash[0 : verifier_hash_size] # first 20 bytes
# Step 4: Calculate the SHA-1 hash of the Verifier from step 2
calculated_hash = SHA1(verifier) # 20 bytes
# Step 5: Compare the results of step 3 and step 4
# If the two hash values do not match, the password is incorrect
return (calculated_hash == stored_hash) # 160-bit comparison
6.1 Decryption of \EncryptedPackage¶
Once the password is verified, the same derived key is used to decrypt the \EncryptedPackage stream. The EncryptedData field (starting at offset 8 of the stream, after the 8-byte StreamSize field) is decrypted using AES-ECB with keyDerived. After decryption, only the first StreamSize bytes of the plaintext constitute the original OOXML ZIP package; any trailing bytes from the final AES block's padding MUST be discarded ([MS-OFFCRYPTO] section 2.3.4.4).
7. Reference constants¶
Every constant required to implement ECMA-376 standard encryption:
| Constant | Value | Spec reference |
|---|---|---|
| Iteration count | 50000 (counter values 0 through 49999) | [MS-OFFCRYPTO] section 2.3.4.7 |
Block number for Hfinal |
0x00000000 (4-byte little-endian, appended to iterated hash) | [MS-OFFCRYPTO] section 2.3.4.7 |
| ipad byte | 0x36, repeated 64 times | [MS-OFFCRYPTO] section 2.3.4.7 step 3 |
| opad byte | 0x5C, repeated 64 times | [MS-OFFCRYPTO] section 2.3.4.7 step 4 |
| ipad/opad buffer size | 64 bytes | [MS-OFFCRYPTO] section 2.3.4.7 |
cbHash (SHA-1 output length) |
20 bytes | [MS-OFFCRYPTO] section 2.3.4.7 step 2 |
Maximum cbRequiredKeyLength |
40 bytes | [MS-OFFCRYPTO] section 2.3.4.7 step 1 |
SaltSize |
0x00000010 (16 bytes) | [MS-OFFCRYPTO] section 2.3.3 |
VerifierHashSize (SHA-1) |
0x00000014 (20 bytes) | [MS-OFFCRYPTO] section 2.3.3 |
EncryptedVerifier size |
16 bytes (one AES block) | [MS-OFFCRYPTO] section 2.3.3 |
EncryptedVerifierHash size (AES) |
32 bytes (two AES blocks) | [MS-OFFCRYPTO] section 2.3.3 |
AlgIDHash for SHA-1 |
0x00008004 | [MS-OFFCRYPTO] section 2.3.2 |
AlgID for AES-128 |
0x0000660E | [MS-OFFCRYPTO] section 2.3.2 |
AlgID for AES-192 |
0x0000660F | [MS-OFFCRYPTO] section 2.3.2 |
AlgID for AES-256 |
0x00006610 | [MS-OFFCRYPTO] section 2.3.2 |
AlgID for RC4 |
0x00006801 | [MS-OFFCRYPTO] section 2.3.2 |
KeySize for AES-128 |
0x00000080 (128 bits) | [MS-OFFCRYPTO] section 2.3.2 |
KeySize for AES-192 |
0x000000C0 (192 bits) | [MS-OFFCRYPTO] section 2.3.2 |
KeySize for AES-256 |
0x00000100 (256 bits) | [MS-OFFCRYPTO] section 2.3.2 |
ProviderType for AES |
0x00000018 | [MS-OFFCRYPTO] section 2.3.2 |
ProviderType for RC4 |
0x00000001 | [MS-OFFCRYPTO] section 2.3.2 |
Flags for standard AES |
0x00000024 (fCryptoAPI | fAES) |
[MS-OFFCRYPTO] section 2.3.1 |
SizeExtra |
0x00000000 | [MS-OFFCRYPTO] section 2.3.2 |
EncryptionVersionInfo.vMinor (standard) |
0x0002 | [MS-OFFCRYPTO] section 2.3.4.5 |
EncryptionVersionInfo.vMinor (agile) |
0x0004 | [MS-OFFCRYPTO] section 2.3.4.10 |
TransformType |
0x00000001 | [MS-OFFCRYPTO] section 2.3.4.3 |
TransformID |
"{FF9A3F03-56EF-4613-BDD5-5A41C1D07246}" |
[MS-OFFCRYPTO] section 2.3.4.3 |
EncryptionBlockSize (AES) |
0x00000010 (16 bytes) | [MS-OFFCRYPTO] section 2.1.9 |
DataSpaceName |
"StrongEncryptionDataSpace" |
[MS-OFFCRYPTO] section 2.3.4.1 |
TransformReference |
"StrongEncryptionTransform" |
[MS-OFFCRYPTO] section 2.3.4.2 |
| Maximum password length | 255 Unicode code points | [MS-OFFCRYPTO] section 4.1.3.1 |
| SHA-1 initial hash values | H0=0x67452301, H1=0xEFCDAB89, H2=0x98BADCFE, H3=0x10325476, H4=0xC3D2E1F0 | FIPS 180 |
7.1 Constants that belong to other algorithms¶
The following constants are defined in [MS-OFFCRYPTO] but do NOT apply to standard encryption:
- The agile encryption block-key constants (e.g.,
\xfe\xa7\xd2\x76...forverifierHashInput,\xd7\xaa\x0f\x6d...forverifierHashValue,\x14\x6e\x0b\xe7...forencryptedKeyValue) belong to agile encryption ([MS-OFFCRYPTO] section 2.3.4.13; see ecma376-agile.md). - The
InitialCodeArray,XorMatrix,PadArray, and 0xCE4B password verifier constant belong to XOR obfuscation ([MS-OFFCRYPTO] sections 2.3.7.1–2.3.7.2; see xor-obfuscation.md). - The
mpbbCryptpermutation array belongs to 40-bit RC4 encryption ([MS-OFFCRYPTO] section 2.3.6.2; see rc4-md5-legacy.md).
The only "block key" in standard encryption is the scalar value 0x00000000 appended in the Hfinal computation step ([MS-OFFCRYPTO] section 2.3.4.7).
8. Security considerations — [MS-OFFCRYPTO] section 4.1.3.1¶
- No CBC mode, no corruption detection. Standard encryption does not support CBC and has no provision for detecting corruption. AES in ECB mode is not known to be subject to bit-flipping attacks, but agile encryption addresses this by requiring CBC and corruption detection ([MS-OFFCRYPTO] section 4.1.3.1).
- ECB mode weakness. Identical plaintext blocks encrypt to identical ciphertext blocks — an inherent property of ECB mode ([SP800-38A]). The spec acknowledges AES-ECB "is not known to be subject to bit-flipping attacks" but ECB does not provide semantic security.
- SHA-1 only. The hash algorithm is fixed at SHA-1; no SHA-2 option exists. The spec recommends SHA-2 for agile encryption ([MS-OFFCRYPTO] section 4.1.3.1), reinforcing that SHA-1 is not preferred for newer implementations.
- Password length limitation. Passwords are limited to 255 Unicode code points ([MS-OFFCRYPTO] section 4.1.3.1). Additional limitations on length and characters are implementation-dependent ([MS-OFFCRYPTO] section 2.3.4.7).
- Non-standard KDF. While the spec states the key generation method "is derived from PKCS #5: Password-Based Cryptography Version 2.0 [RFC2898]" ([MS-OFFCRYPTO] section 2.3.4.7), the actual construction differs from standard PBKDF2/HMAC-SHA1: the counter is prepended rather than appended per HMAC conventions, there is no HMAC key schedule, and the ipad/opad expansion is applied only once at the end rather than on every iteration.
9. Spec references¶
| Section | Title | Content |
|---|---|---|
| [MS-OFFCRYPTO] section 1.3.3.4 | ECMA-376 Document Encryption | Overview of the three ECMA-376 encryption sub-approaches (standard, agile, extensible) |
| [MS-OFFCRYPTO] section 1.3.6 | Byte Ordering | All data in this format is little-endian |
| [MS-OFFCRYPTO] section 2.1.9 | EncryptionTransformInfo | EncryptionName, EncryptionBlockSize, CipherMode, Reserved fields for the transform |
| [MS-OFFCRYPTO] section 2.3.1 | EncryptionHeaderFlags | Bit definitions for Flags: fCryptoAPI, fDocProps, fExternal, fAES |
| [MS-OFFCRYPTO] section 2.3.2 | EncryptionHeader | Flags, SizeExtra, AlgID, AlgIDHash, KeySize, ProviderType, CSPName field definitions and value tables |
| [MS-OFFCRYPTO] section 2.3.3 | EncryptionVerifier | SaltSize, Salt, EncryptedVerifier, VerifierHashSize, EncryptedVerifierHash field definitions and verifier generation procedure |
| [MS-OFFCRYPTO] section 2.3.4 | ECMA-376 Document Encryption | Container-level requirements for encrypted ECMA-376 documents |
| [MS-OFFCRYPTO] section 2.3.4.1 | DataSpaceMap Stream | DataSpaceName = "StrongEncryptionDataSpace", ReferenceComponent = "EncryptedPackage" |
| [MS-OFFCRYPTO] section 2.3.4.2 | DataSpaceInfo Storage | TransformReference = "StrongEncryptionTransform" |
| [MS-OFFCRYPTO] section 2.3.4.3 | TransformInfo Storage | TransformID = "{FF9A3F03-56EF-4613-BDD5-5A41C1D07246}", TransformName = "Microsoft.Container.EncryptionTransform", followed by EncryptionTransformInfo |
| [MS-OFFCRYPTO] section 2.3.4.4 | \EncryptedPackage Stream |
StreamSize (u64 LE) + EncryptedData structure |
| [MS-OFFCRYPTO] section 2.3.4.5 | \EncryptionInfo Stream (Standard Encryption) |
Version words (vMajor = 0x0002/0x0003/0x0004, vMinor = 0x0002), EncryptionHeader.Flags copy, EncryptionHeaderSize, EncryptionHeader, EncryptionVerifier; mandated AES AlgID and SHA-1 AlgIDHash values |
| [MS-OFFCRYPTO] section 2.3.4.6 | \EncryptionInfo Stream (Extensible Encryption) |
Third-party CSP encryption; vMinor = 0x0003; not applicable to standard encryption |
| [MS-OFFCRYPTO] section 2.3.4.7 | ECMA-376 Document Encryption Key Generation (Standard Encryption) | Full KDF: H0 = H(salt+password), 50000 iterations with LE32 counter prepended, Hfinal = H(Hn+block) with block=0, ipad/opad key expansion (0x36/0x5C), X1/X2/X3 concatenation, keyDerived truncation, ECB mode |
| [MS-OFFCRYPTO] section 2.3.4.8 | Password Verifier Generation (Standard Encryption) | Verifier encrypted under key derived with block number 0 |
| [MS-OFFCRYPTO] section 2.3.4.9 | Password Verification (Standard Encryption) | Decrypt EncryptedVerifier (16 bytes) and EncryptedVerifierHash (32 bytes, first 20 used), compare SHA-1 of verifier against stored hash |
| [MS-OFFCRYPTO] section 2.3.4.10 | \EncryptionInfo Stream (Agile Encryption) |
Agile encryption descriptor; vMinor = 0x0004; see ecma376-agile.md |
| [MS-OFFCRYPTO] section 4.1.3.1 | ECMA-376 Document Encryption (Security) | No CBC / no corruption detection in standard encryption; SHA-2 preferred for agile; password limit 255 code points |
| [RFC2898] | PKCS #5 v2.0 | Referenced as the basis for the key derivation method |
| [FIPS 197] | AES | Advanced Encryption Standard specification |
| [RFC4634] | SHA and HMAC-SHA | SHA-1 specification |
| [SP800-38A] | Block Cipher Modes of Operation | ECB and CBC mode specifications |
| [ECMA-376] | Office Open XML File Formats | ECMA-376 standard, Part 2 Annex C Table C-5 bit 0 defines password encryption |
| [MS-CFB] | Compound File Binary File Format | OLE compound file container format |
Cross-references within this collection¶
| Related document | Relationship |
|---|---|
| ecma376-agile.md | Successor scheme (Office 2010+ default); uses XML descriptor, CBC mode, configurable hash/cipher; vMinor = 0x0004 |
| rc4-cryptoapi-sha1.md | CryptoAPI RC4 encryption ([MS-OFFCRYPTO] section 2.3.5); shares the EncryptionHeader/EncryptionVerifier structures but uses RC4 and un-iterated KDF |
| rc4-md5-legacy.md | 40-bit RC4 encryption ([MS-OFFCRYPTO] section 2.3.6); legacy binary document encryption |
| xor-obfuscation.md | XOR obfuscation ([MS-OFFCRYPTO] section 2.3.7); weakest legacy scheme |
| word.md | Word format-level details for password protection triggers |
| excel.md | Excel format-level details for password protection triggers |
| powerpoint.md | PowerPoint format-level details for password protection triggers |
| onenote.md | OneNote reuses the standard KDF/verifier path |
| irm-rms-encryption.md | IRM/RMS encryption ([MS-OFFCRYPTO] section 2.2); certificate-based, not password-based |