Fix SHA-256 padding bug for messages of length 63 mod 64

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
This commit is contained in:
2026-09-17 12:25:23 -07:00
parent 65c4b220ae
commit 7755be49e9
8 changed files with 254 additions and 18 deletions
+23
View File
@@ -58,6 +58,29 @@ fn test_hmac_02() -> Result<()> {
Ok(())
}
#[test]
fn test_hmac_pad_boundary() -> Result<()> {
// 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.
let k: [u8; 20] = [
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
];
let msg = [0x61u8; 63];
let output = b"2396ff2784cd6b8bdf0ac13df75a30de92e3f15374065b9f21ac81a268a93904";
let mut digest: [u8; sha256::SIZE] = [0; sha256::SIZE];
let mut hdigest: [u8; 64] = [0; 64];
let mut h = hmac::HMAC_SHA256::new(&k)?;
h.update(&msg)?;
h.finalize(&mut digest)?;
to_hex(&digest, &mut hdigest);
assert_eq!(&hdigest, output);
Ok(())
}
#[test]
fn test_hmac_03() -> Result<()> {
let k: [u8; 25] = [
+29
View File
@@ -20,6 +20,35 @@ fn test_self_test() -> Result<()> {
Ok(())
}
#[test]
fn test_padding_boundaries() -> Result<()> {
let tests: &[(usize, &[u8])] = &[
(55, b"9f4390f8d30c2dd92ec9f095b65e2b9ae9b0a925a5258e241c9f1e910f734318"),
(56, b"b35439a4ac6f0948b6d6f9e3c6af0f5f590ce20f1bde7090ef7970686ec6738a"),
(63, b"7d3e74a05d7db15bce4ad9ec0658ea98e3f06eeecf16b4c6fff2da457ddc2f34"),
(64, b"ffe054fe7ae0cb6dc65c3af9b61d5209f439851db43d0ba5997337df154668eb"),
(65, b"635361c48bb9eab14198e76ea8ab7f1a41685d6ad62aa9146d301d4f17eb0ae0"),
];
let mut h = sha256::SHA256::default();
let mut d: [u8; 32] = [0; 32];
let mut s: [u8; 64] = [0; 64];
let input_buf = [0x61u8; 65];
let mut i: usize = 0;
while i < tests.len() {
let (len, expected) = tests[i];
h.update(&input_buf[..len])?;
h.finalize(&mut d)?;
to_hex(&d, &mut s);
assert_eq!(&s, expected, "length {}", len);
h.reset()?;
i += 1;
}
Ok(())
}
#[test]
fn test_golden_tests() -> Result<()> {
let golden_tests: &[HashTest] = &[