archive-codec 1.7.1 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -184,7 +184,7 @@ const baseHash = deriveOfficeRc4BaseHash(password, salt);
184
184
  const plaintext = decryptOfficeRc4(baseHash, streamOffset, ciphertext);
185
185
  ```
186
186
 
187
- `deriveOfficeRc4BaseHash`/`deriveOfficeRc4BlockKey`/`decryptOfficeRc4` implement [MS-OFFCRYPTO] 2.3.6.1/2.3.6.2's RC4 encryption header scheme: a per-workbook base hash derived once from the password and the header's own salt, then a real 40-bit RC4 key re-derived from that base hash at every 1024-byte boundary of the underlying decrypted stream. Both the key length and the block-reset interval come from Apache POI's own `Biff8EncryptionKey`, a mature independent implementation, rather than the published spec's own more generic prose — see each constant's own doc comment for the discrepancy. `decryptOfficeRc4` takes the byte offset a chunk starts at within the whole encrypted stream (not within the chunk itself), so a stream can be decrypted in arbitrary pieces, not only from its own start, and still land on the correct per-block key throughout.
187
+ `deriveOfficeRc4BaseHash`/`deriveOfficeRc4BlockKey`/`decryptOfficeRc4` implement [MS-OFFCRYPTO] 2.3.6.1/2.3.6.2's RC4 encryption header scheme: a per-workbook base hash derived once from the password and the header's own salt, then a full 128-bit RC4 key re-derived from that base hash at every 1024-byte boundary of the underlying decrypted stream — despite [MS-OFFCRYPTO] 2.3.6.1's own field descriptions twice stating "encrypted using a 40-bit RC4 cipher", which is wrong; the real key is Hfinal in full, confirmed against Apache POI's `BinaryRC4Decryptor` and nolze/msoffcrypto-tool's own doctested test vectors, both cross-checked directly rather than trusted from the spec's own prose — see each constant's own doc comment for the full account, including the truncated-to-5-bytes implementation this package shipped briefly before the cross-check caught it. `decryptOfficeRc4` takes the byte offset a chunk starts at within the whole encrypted stream (not within the chunk itself), so a stream can be decrypted in arbitrary pieces, not only from its own start, and still land on the correct per-block key throughout.
188
188
 
189
189
  This is exactly the piece `xls-codec`'s `FilePass`-record reader needs and nothing more: locating the `FilePass` record, reading its own encryption-header bytes, and verifying the password against `EncryptedVerifier`/`EncryptedVerifierHash` are `xls-codec`'s own concern, not this package's — `archive-codec` carries only the format-agnostic cryptography, never a `.xls`-specific byte layout. `md5`/`rc4` are exported individually too, for `doc-codec`/`ppt-codec`'s own eventual integration against the identical header scheme.
190
190
 
@@ -4,10 +4,10 @@ const require_crypto_rc4 = require("./rc4.cjs");
4
4
  //#region src/crypto/office-rc4.ts
5
5
  /** [MS-OFFCRYPTO] 2.3.6.1's own EncryptedVerifier/EncryptedVerifierHash length, and the salt length the same header carries. */
6
6
  const OFFICE_RC4_VERIFIER_LENGTH = 16;
7
- /** The span of the underlying decrypted stream one derived RC4 key covers before the next block's key takes over -- confirmed against Apache POI's Biff8EncryptionKey, since [MS-OFFCRYPTO] 2.3.6.2's own prose does not state it. */
7
+ /** The span of the underlying decrypted stream one derived RC4 key covers before the next block's key takes over -- confirmed against Apache POI's Biff8DecryptingStream.RC4_REKEYING_INTERVAL, since [MS-OFFCRYPTO] 2.3.6.2's own prose does not state it. */
8
8
  const OFFICE_RC4_BLOCK_SIZE = 1024;
9
- /** The real derived RC4 key length for this scheme (40 bits) -- confirmed against Apache POI's Biff8EncryptionKey, since [MS-OFFCRYPTO] 2.3.6.2's own generic prose ("the first 128 bits of Hfinal") describes a different, longer-key variant this header shape does not use. */
10
- const KEY_LENGTH_BYTES = 5;
9
+ /** The intermediate H0/H1 truncation [MS-OFFCRYPTO] 2.3.6.2 states explicitly ("H0's own first 5 bytes", "H1's own first 5 bytes") -- distinct from the FINAL per-block key length, which is Hfinal in full (see this file's own top comment) and is never truncated. */
10
+ const INTERMEDIATE_HASH_LENGTH_BYTES = 5;
11
11
  function passwordToUtf16LeBytes(password) {
12
12
  const bytes = new Uint8Array(password.length * 2);
13
13
  for (let i = 0; i < password.length; i += 1) {
@@ -19,15 +19,15 @@ function passwordToUtf16LeBytes(password) {
19
19
  }
20
20
  /** The per-workbook intermediate hash (H1's own first 5 bytes) every block's key derives from -- computed once per password+salt pair, then reused across every block via deriveOfficeRc4BlockKey, since recomputing H0/H1 per block would be needless repeated work over the identical 336-byte buffer. */
21
21
  function deriveOfficeRc4BaseHash(password, salt) {
22
- const truncated = require_crypto_md5.md5(passwordToUtf16LeBytes(password)).subarray(0, KEY_LENGTH_BYTES);
22
+ const truncated = require_crypto_md5.md5(passwordToUtf16LeBytes(password)).subarray(0, INTERMEDIATE_HASH_LENGTH_BYTES);
23
23
  const unit = /* @__PURE__ */ new Uint8Array(21);
24
24
  unit.set(truncated, 0);
25
- unit.set(salt, KEY_LENGTH_BYTES);
25
+ unit.set(salt, INTERMEDIATE_HASH_LENGTH_BYTES);
26
26
  const buffer336 = new Uint8Array(unit.length * 16);
27
27
  for (let i = 0; i < 16; i += 1) buffer336.set(unit, i * unit.length);
28
- return require_crypto_md5.md5(buffer336).subarray(0, KEY_LENGTH_BYTES);
28
+ return require_crypto_md5.md5(buffer336).subarray(0, INTERMEDIATE_HASH_LENGTH_BYTES);
29
29
  }
30
- /** The real, block-specific 40-bit RC4 key: MD5(baseHash + the block number as 4 little-endian bytes), truncated to 5 bytes. */
30
+ /** The real, block-specific RC4 key: MD5(baseHash + the block number as 4 little-endian bytes) in full -- all 16 bytes, never truncated (see this file's own top comment for why that matters). */
31
31
  function deriveOfficeRc4BlockKey(baseHash, blockNumber) {
32
32
  const input = new Uint8Array(baseHash.length + 4);
33
33
  input.set(baseHash, 0);
@@ -35,7 +35,7 @@ function deriveOfficeRc4BlockKey(baseHash, blockNumber) {
35
35
  input[baseHash.length + 1] = blockNumber >>> 8 & 255;
36
36
  input[baseHash.length + 2] = blockNumber >>> 16 & 255;
37
37
  input[baseHash.length + 3] = blockNumber >>> 24 & 255;
38
- return require_crypto_md5.md5(input).subarray(0, KEY_LENGTH_BYTES);
38
+ return require_crypto_md5.md5(input);
39
39
  }
40
40
  /**
41
41
  * Decrypts `data` -- a byte range of the underlying OLE stream starting at `streamOffset` bytes from the very start of that stream -- against the RC4 encryption header scheme, re-deriving the block key at every 1024-byte boundary `data` crosses. RC4 is symmetric, so this same function also encrypts; nothing in this package uses it that way, since nothing in this family writes an encrypted legacy binary document.
@@ -1,11 +1,11 @@
1
1
  //#region src/crypto/office-rc4.d.ts
2
2
  /** [MS-OFFCRYPTO] 2.3.6.1's own EncryptedVerifier/EncryptedVerifierHash length, and the salt length the same header carries. */
3
3
  declare const OFFICE_RC4_VERIFIER_LENGTH = 16;
4
- /** The span of the underlying decrypted stream one derived RC4 key covers before the next block's key takes over -- confirmed against Apache POI's Biff8EncryptionKey, since [MS-OFFCRYPTO] 2.3.6.2's own prose does not state it. */
4
+ /** The span of the underlying decrypted stream one derived RC4 key covers before the next block's key takes over -- confirmed against Apache POI's Biff8DecryptingStream.RC4_REKEYING_INTERVAL, since [MS-OFFCRYPTO] 2.3.6.2's own prose does not state it. */
5
5
  declare const OFFICE_RC4_BLOCK_SIZE = 1024;
6
6
  /** The per-workbook intermediate hash (H1's own first 5 bytes) every block's key derives from -- computed once per password+salt pair, then reused across every block via deriveOfficeRc4BlockKey, since recomputing H0/H1 per block would be needless repeated work over the identical 336-byte buffer. */
7
7
  declare function deriveOfficeRc4BaseHash(password: string, salt: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>;
8
- /** The real, block-specific 40-bit RC4 key: MD5(baseHash + the block number as 4 little-endian bytes), truncated to 5 bytes. */
8
+ /** The real, block-specific RC4 key: MD5(baseHash + the block number as 4 little-endian bytes) in full -- all 16 bytes, never truncated (see this file's own top comment for why that matters). */
9
9
  declare function deriveOfficeRc4BlockKey(baseHash: Uint8Array<ArrayBuffer>, blockNumber: number): Uint8Array<ArrayBuffer>;
10
10
  /**
11
11
  * Decrypts `data` -- a byte range of the underlying OLE stream starting at `streamOffset` bytes from the very start of that stream -- against the RC4 encryption header scheme, re-deriving the block key at every 1024-byte boundary `data` crosses. RC4 is symmetric, so this same function also encrypts; nothing in this package uses it that way, since nothing in this family writes an encrypted legacy binary document.
@@ -1,11 +1,11 @@
1
1
  //#region src/crypto/office-rc4.d.ts
2
2
  /** [MS-OFFCRYPTO] 2.3.6.1's own EncryptedVerifier/EncryptedVerifierHash length, and the salt length the same header carries. */
3
3
  declare const OFFICE_RC4_VERIFIER_LENGTH = 16;
4
- /** The span of the underlying decrypted stream one derived RC4 key covers before the next block's key takes over -- confirmed against Apache POI's Biff8EncryptionKey, since [MS-OFFCRYPTO] 2.3.6.2's own prose does not state it. */
4
+ /** The span of the underlying decrypted stream one derived RC4 key covers before the next block's key takes over -- confirmed against Apache POI's Biff8DecryptingStream.RC4_REKEYING_INTERVAL, since [MS-OFFCRYPTO] 2.3.6.2's own prose does not state it. */
5
5
  declare const OFFICE_RC4_BLOCK_SIZE = 1024;
6
6
  /** The per-workbook intermediate hash (H1's own first 5 bytes) every block's key derives from -- computed once per password+salt pair, then reused across every block via deriveOfficeRc4BlockKey, since recomputing H0/H1 per block would be needless repeated work over the identical 336-byte buffer. */
7
7
  declare function deriveOfficeRc4BaseHash(password: string, salt: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>;
8
- /** The real, block-specific 40-bit RC4 key: MD5(baseHash + the block number as 4 little-endian bytes), truncated to 5 bytes. */
8
+ /** The real, block-specific RC4 key: MD5(baseHash + the block number as 4 little-endian bytes) in full -- all 16 bytes, never truncated (see this file's own top comment for why that matters). */
9
9
  declare function deriveOfficeRc4BlockKey(baseHash: Uint8Array<ArrayBuffer>, blockNumber: number): Uint8Array<ArrayBuffer>;
10
10
  /**
11
11
  * Decrypts `data` -- a byte range of the underlying OLE stream starting at `streamOffset` bytes from the very start of that stream -- against the RC4 encryption header scheme, re-deriving the block key at every 1024-byte boundary `data` crosses. RC4 is symmetric, so this same function also encrypts; nothing in this package uses it that way, since nothing in this family writes an encrypted legacy binary document.
@@ -3,10 +3,10 @@ import { rc4 } from "./rc4.js";
3
3
  //#region src/crypto/office-rc4.ts
4
4
  /** [MS-OFFCRYPTO] 2.3.6.1's own EncryptedVerifier/EncryptedVerifierHash length, and the salt length the same header carries. */
5
5
  const OFFICE_RC4_VERIFIER_LENGTH = 16;
6
- /** The span of the underlying decrypted stream one derived RC4 key covers before the next block's key takes over -- confirmed against Apache POI's Biff8EncryptionKey, since [MS-OFFCRYPTO] 2.3.6.2's own prose does not state it. */
6
+ /** The span of the underlying decrypted stream one derived RC4 key covers before the next block's key takes over -- confirmed against Apache POI's Biff8DecryptingStream.RC4_REKEYING_INTERVAL, since [MS-OFFCRYPTO] 2.3.6.2's own prose does not state it. */
7
7
  const OFFICE_RC4_BLOCK_SIZE = 1024;
8
- /** The real derived RC4 key length for this scheme (40 bits) -- confirmed against Apache POI's Biff8EncryptionKey, since [MS-OFFCRYPTO] 2.3.6.2's own generic prose ("the first 128 bits of Hfinal") describes a different, longer-key variant this header shape does not use. */
9
- const KEY_LENGTH_BYTES = 5;
8
+ /** The intermediate H0/H1 truncation [MS-OFFCRYPTO] 2.3.6.2 states explicitly ("H0's own first 5 bytes", "H1's own first 5 bytes") -- distinct from the FINAL per-block key length, which is Hfinal in full (see this file's own top comment) and is never truncated. */
9
+ const INTERMEDIATE_HASH_LENGTH_BYTES = 5;
10
10
  function passwordToUtf16LeBytes(password) {
11
11
  const bytes = new Uint8Array(password.length * 2);
12
12
  for (let i = 0; i < password.length; i += 1) {
@@ -18,15 +18,15 @@ function passwordToUtf16LeBytes(password) {
18
18
  }
19
19
  /** The per-workbook intermediate hash (H1's own first 5 bytes) every block's key derives from -- computed once per password+salt pair, then reused across every block via deriveOfficeRc4BlockKey, since recomputing H0/H1 per block would be needless repeated work over the identical 336-byte buffer. */
20
20
  function deriveOfficeRc4BaseHash(password, salt) {
21
- const truncated = md5(passwordToUtf16LeBytes(password)).subarray(0, KEY_LENGTH_BYTES);
21
+ const truncated = md5(passwordToUtf16LeBytes(password)).subarray(0, INTERMEDIATE_HASH_LENGTH_BYTES);
22
22
  const unit = /* @__PURE__ */ new Uint8Array(21);
23
23
  unit.set(truncated, 0);
24
- unit.set(salt, KEY_LENGTH_BYTES);
24
+ unit.set(salt, INTERMEDIATE_HASH_LENGTH_BYTES);
25
25
  const buffer336 = new Uint8Array(unit.length * 16);
26
26
  for (let i = 0; i < 16; i += 1) buffer336.set(unit, i * unit.length);
27
- return md5(buffer336).subarray(0, KEY_LENGTH_BYTES);
27
+ return md5(buffer336).subarray(0, INTERMEDIATE_HASH_LENGTH_BYTES);
28
28
  }
29
- /** The real, block-specific 40-bit RC4 key: MD5(baseHash + the block number as 4 little-endian bytes), truncated to 5 bytes. */
29
+ /** The real, block-specific RC4 key: MD5(baseHash + the block number as 4 little-endian bytes) in full -- all 16 bytes, never truncated (see this file's own top comment for why that matters). */
30
30
  function deriveOfficeRc4BlockKey(baseHash, blockNumber) {
31
31
  const input = new Uint8Array(baseHash.length + 4);
32
32
  input.set(baseHash, 0);
@@ -34,7 +34,7 @@ function deriveOfficeRc4BlockKey(baseHash, blockNumber) {
34
34
  input[baseHash.length + 1] = blockNumber >>> 8 & 255;
35
35
  input[baseHash.length + 2] = blockNumber >>> 16 & 255;
36
36
  input[baseHash.length + 3] = blockNumber >>> 24 & 255;
37
- return md5(input).subarray(0, KEY_LENGTH_BYTES);
37
+ return md5(input);
38
38
  }
39
39
  /**
40
40
  * Decrypts `data` -- a byte range of the underlying OLE stream starting at `streamOffset` bytes from the very start of that stream -- against the RC4 encryption header scheme, re-deriving the block key at every 1024-byte boundary `data` crosses. RC4 is symmetric, so this same function also encrypts; nothing in this package uses it that way, since nothing in this family writes an encrypted legacy binary document.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archive-codec",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "ZIP-in-ZIP recursive walking with depth and cumulative decompressed-size guards, bounded classic OLE compound-file ([MS-CFB]) reading and writing, and [MS-OLEPS] Property Set Stream reading and writing - zero document-format knowledge, the archive and container utility package for the documents.js family.",
5
5
  "type": "module",
6
6
  "repository": {