pad_message wrote 0x00 into the last byte of the current block and deferred the 0x80 pad byte to the next block when mbi == 63, producing incorrect digests for every message whose length is 63 mod 64 (and correspondingly wrong HMAC tags). Always write the pad byte in the current block first, then flush an extra block only when the 8-byte length field no longer fits. Adds regression tests for SHA-256 boundary lengths 55/56/63/64/65 and HMAC with a 63-byte message. Bumps to 1.0.4 for crates.io publication. Ref: docs/LAGUNA-AUDIT.md
2.9 KiB
Laguna audit: local SHA-256 padding fix (1.0.4)
Date: 2026-09-17
Mode: local uncommitted changes vs HEAD
Scope: Cargo.toml, Cargo.lock, src/sha256.rs, tests/sha256.rs, tests/hmac.rs
Diff: 5 files, +74 / −21
Verdict: Correct. No bugs. Two minor suggestions addressed.
Summary
This change fixes a SHA-256 padding bug for messages whose length is 63 mod 64,
bumps the crate from 1.0.3 to 1.0.4, adds regression tests, and removes the
publish = ["kellnr"] restriction for crates.io publication.
The old pad_message in src/sha256.rs used the condition self.mbi < MB_SIZE - 1
to decide whether to write the 0x80 pad byte in the current block before
zero-fill and block processing. When mbi == 63 (i.e. the message is 63 mod 64
bytes), that condition was false, so position 63 was zero-filled instead of
receiving 0x80. The block was then processed with message || 0x00, and 0x80
was written at index 0 of the next block — producing a digest of
message || 0x00 || 0x80 || ... instead of the correct message || 0x80 || 0x00 || ....
The new code always writes the pad byte at self.mb[self.mbi] first (safe because
update flushes full blocks, so mbi is always < MB_SIZE on entry), then
zero-fills and processes the current block only when the 8-byte length field no
longer fits (mbi > MB_SIZE - 8).
HMAC-SHA-256 was affected too, since its inner hash processes 64-byte ipad + message bytes; a 63-byte message makes the inner length 127 (63 mod 64).
Verification
- The six new test vectors match
sha256sumand Pythonhmac/hashlibexactly: SHA-256 at lengths 55, 56, 63, 64, 65 bytes (alla), and HMAC-SHA-256 over a 63-byte message with the 20-byte0x0bkey. cargo testpasses: 11 tests across unit, integration, and doc suites.cargo clippy --all-targetsis clean.
Issues
1 — resolved — comment refinement: src/sha256.rs:177
The branch comment originally restated the code ("Zero-fill and process it, then start a fresh block for the length"). Trimmed to state the why:
// Fewer than 8 bytes remain; flush this block and start
// a fresh block for the length field.
2 — resolved — comment refinement: tests/hmac.rs:63
The test comment referenced "the length that triggered the padding bug" (bug history) rather than the invariant. Reworded to state the invariant:
// A 63-byte message makes the inner SHA-256 process 127 bytes
// (64 key-pad + 63 message), which is 63 mod 64 — the boundary
// where the pad byte must start a new block.
Packaging change
Removing publish = ["kellnr"] from Cargo.toml is intentional so cargo publish targets crates.io for the 1.0.4 release.
Counts
| Severity | Count | State |
|---|---|---|
| bug | 0 | — |
| suggestion | 2 | resolved |
| packaging | 1 | intentional |