From 6d230ab91b7a0da9e18ed14c76668a788d6923b3 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Tue, 8 Sep 2026 11:54:07 -0500 Subject: [PATCH 1/6] Added docs to SHA2 and SHA3 about cloning mid-stream (cherry picked from commit 1f4a84eabf1f864acb29bad263c48a6e7c365b7f) Replayed onto the HMAC/HKDF utility-crate split, which had restructured both crate doc headers. Adapted while resolving the conflict: * link [`SHA256Internal`] / [`SHA512Internal`] and [`SHA3Internal`] rather than [`SHA2`] / [`SHA3`]; the latter two are not type names in either crate and would have been dead intra-doc links. * keep the "## HMAC" pointer that the split added to the sha3 header. * move sha2's "Memory Usage" and "Security Considerations" below "Suspending and resuming execution", so that both crates' doc headers present the same section order. sha3 was already in that order. Assisted-by: Claude Code:claude-opus-5 Co-Authored-By: Claude Opus 5 (1M context) --- crypto/sha2/src/lib.rs | 24 ++++++++++++++++++++++++ crypto/sha3/src/lib.rs | 30 +++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index 86dd7090..215ed646 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -66,6 +66,30 @@ //! See [`SHA512t`] for documentation around defining a custom truncation length of SHA512 other //! than the [`SHA512_224`] and [`SHA512_256`] defined in FIPS 180-4. //! +//! # Cloning mid-stream +//! +//! Sometimes it is necessary to clone the state of the hash function after absorbing some input, for +//! example, if you have absorbed a large file and need to hash it with two different trailer suffixes. +//! +//! The [`SHA256Internal`] and [`SHA512Internal`] structs impl [`Clone`] for this purpose. +//! +//! ```rust +//! use bouncycastle_core::traits::Hash; +//! use bouncycastle_sha2 as sha2; +//! +//! let mut sha2 = sha2::SHA256::new(); +//! sha2.do_update(b"Some input"); +//! // Clone it so that we can finish this instance, and then continue feeding in more input. +//! let mut sha2_2 = sha2.clone(); +//! +//! // Finish the first instance +//! let output: Vec = sha2.do_final(); +//! +//! // Feed more into the second instance then squeeze it +//! sha2_2.do_update(b"Some more input"); +//! let output2 = sha2_2.do_final(); +//! ``` +//! //! # Suspending and resuming execution //! //! When hashing a large message, it can be advantageous to be able to suspend the operation diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index 272a812a..e382f593 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -129,11 +129,35 @@ //! ## HMAC //! See [hmac]. //! +//! # Cloning mid-stream +//! +//! Sometimes it is necessary to clone the state of the hash function after absorbing some input, for +//! example, if you have absorbed a large file and need to hash it with two different trailer suffixes. +//! +//! The [`SHA3Internal`] struct impls [`Clone`] for this purpose. +//! +//! ```rust +//! use bouncycastle_core::traits::Hash; +//! use bouncycastle_sha3 as sha3; +//! +//! let mut sha3 = sha3::SHA3_256::new(); +//! sha3.do_update(b"Some input"); +//! // Clone it so that we can finish this instance, and then continue feeding in more input. +//! let mut sha3_2 = sha3.clone(); +//! +//! // Finish the first instance +//! let output: Vec = sha3.do_final(); +//! +//! // Feed more into the second instance then squeeze it +//! sha3_2.do_update(b"Some more input"); +//! let output2 = sha3_2.do_final(); +//! ``` +//! //! # Suspending and resuming execution //! -//! When hashing a large message, it can be advantageous to be able to suspend the operation -//! to a cache and resume it later; for example if waiting for the message to stream over a slow network -//! connection. +//! Suspending is similar to cloning, but compresses the hash function's state into a byte array that can be +//! cached and then resumed at a later time, potentially from a different machine. The typical usage for this +//! is when waiting for a slow IO operation. //! //! For this reason, all SHA3 algorithms impl [`Suspendable`]. //! From 149fdb51d039875b3e7f6e77d34fc38cb38b3ce4 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sat, 12 Sep 2026 13:02:43 -0500 Subject: [PATCH 2/6] hmac, sha2, sha3: move the HMAC tests to the crates that publish the aliases The HMAC test suite exercised HMAC over SHA-2 and SHA-3, which forced bouncycastle-hmac to carry bouncycastle-sha2, -sha3, -rng and -hex as dev-dependencies purely in order to test itself. That was a deliberate cycle through dev-dependencies, flagged with a todo, left over from the utility-crate split. Split the suite by the hash it exercises and move each half to the crate that publishes the aliases under test: * crypto/sha2/tests/hmac_tests.rs -- HMAC-SHA224/256/384/512: the full RFC 4231 suite, suspend/resume, keygen, and weak-RNG rejection. * crypto/sha3/tests/hmac_tests.rs -- HMAC-SHA3-224/256/384/512: the 143-byte long-key regression (SHA3-224 has the largest block at 144 bytes), truncated-tag rejection, suspend/resume, and keygen. No test bodies or expected values were changed; the suite was partitioned, not rewritten. bouncycastle-hmac's [dev-dependencies] are gone entirely and with them the dependency cycle. The hex dev-dependency follows the tests to bouncycastle-sha2, which did not previously have one. This leaves bouncycastle-hmac with no tests of its own. That is inherent to it being a utility crate rather than a gap in coverage: HMAC cannot be instantiated without a hash, and this crate must not depend on the hash crates. Every behaviour it had is still exercised, from the two suites above. On rebasing onto feature/simple-ciphers, its HMAC-SHA512/224 and -SHA512/256 tests follow SHA-2 here, and its HMAC-SM3 tests move to a new crypto/sm3/tests/hmac_tests.rs. Assisted-by: Claude Code:claude-opus-5 Co-Authored-By: Claude Opus 5 (1M context) --- crypto/hmac/Cargo.toml | 9 -- crypto/{hmac => sha2}/tests/hmac_tests.rs | 143 +------------------- crypto/sha3/tests/hmac_tests.rs | 142 +++++++++++++++++++ crypto/sm3/tests/hmac_tests.rs | 157 ++++++++++++++++++++++ 4 files changed, 302 insertions(+), 149 deletions(-) rename crypto/{hmac => sha2}/tests/hmac_tests.rs (87%) create mode 100644 crypto/sha3/tests/hmac_tests.rs create mode 100644 crypto/sm3/tests/hmac_tests.rs diff --git a/crypto/hmac/Cargo.toml b/crypto/hmac/Cargo.toml index ba9c9643..7f46211b 100644 --- a/crypto/hmac/Cargo.toml +++ b/crypto/hmac/Cargo.toml @@ -6,12 +6,3 @@ edition.workspace = true [dependencies] bouncycastle-core.workspace = true bouncycastle-utils.workspace = true - -[dev-dependencies] -bouncycastle-core-test-framework.workspace = true -criterion.workspace = true -bouncycastle-hex.workspace = true -bouncycastle-rng.workspace = true -bouncycastle-sha2.workspace = true -bouncycastle-sha3.workspace = true -bouncycastle-sm3.workspace = true diff --git a/crypto/hmac/tests/hmac_tests.rs b/crypto/sha2/tests/hmac_tests.rs similarity index 87% rename from crypto/hmac/tests/hmac_tests.rs rename to crypto/sha2/tests/hmac_tests.rs index 1093c697..510bbdac 100644 --- a/crypto/hmac/tests/hmac_tests.rs +++ b/crypto/sha2/tests/hmac_tests.rs @@ -1,5 +1,5 @@ #[cfg(test)] -mod hmac_tests { +mod hmac_sha2_tests { use bouncycastle_core::errors::{KeyMaterialError, MACError, RNGError}; use bouncycastle_core::key_material; use bouncycastle_core::key_material::{ @@ -10,14 +10,10 @@ mod hmac_tests { use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::mac::TestFrameworkMAC; use bouncycastle_hex as hex; - use bouncycastle_hmac::*; + use bouncycastle_hmac::{HMAC, MIN_FIPS_DIGEST_LEN}; use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; use bouncycastle_sha2::hmac::*; use bouncycastle_sha2::*; - use bouncycastle_sha3::hmac::*; - use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; - use bouncycastle_sm3::SM3; - use bouncycastle_sm3::hmac::*; #[test] fn simple_tests() { @@ -62,46 +58,6 @@ mod hmac_tests { ); } - #[test] - fn test_type_aliases() { - let key = KeyMaterial512::from_bytes_as_type( - b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", - KeyType::MACKey).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA224::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA256::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA384::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA512::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA512_224::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA512_256::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA3_224::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA3_256::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA3_384::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SHA3_512::new(&key).unwrap(); - - _ = HMAC::::new(&key).unwrap(); - _ = HMAC_SM3::new(&key).unwrap(); - } - #[test] fn constructor_tests() { let short_key = KeyMaterial256::from_bytes_as_type( @@ -171,13 +127,6 @@ mod hmac_tests { mac.do_update(b"Hi There"); let tag = mac.do_final(); assert!(HMAC_SHA512::new(&key).unwrap().verify(b"Hi There", &tag)); - - // SHA3-224 has the largest block (144 bytes); a 143-byte key exercises the top of the range. - let key = KeyMaterial::<200>::from_bytes_as_type(&[0x0B; 143], KeyType::MACKey).unwrap(); - let mut mac = HMAC_SHA3_224::new(&key).unwrap(); - mac.do_update(b"Hi There"); - let tag = mac.do_final(); - assert!(HMAC_SHA3_224::new(&key).unwrap().verify(b"Hi There", &tag)); } #[test] @@ -270,21 +219,6 @@ mod hmac_tests { let bytes_written = mac.do_final_out(&mut out).unwrap(); assert_eq!(bytes_written, MIN_FIPS_DIGEST_LEN); assert_eq!(&out, b"\x89\x6f\xb1\x12"); - - // fail case: mac value is correct but truncated - let mac = HMAC_SHA3_224::new(&key).unwrap(); - let mut mac_val = mac.mac(b"Polly want a cracker?"); - let verifier = HMAC_SHA3_224::new(&key).unwrap(); - assert!(verifier.verify(b"Polly want a cracker?", &mac_val)); - - // truncation of the mac value is considered a fail - let verifier = HMAC_SHA3_224::new(&key).unwrap(); - assert!(!verifier.verify(b"Polly want a cracker?", &mac_val[..mac_val.len() - 1])); - - // .. as is some extra bytes at the end - let verifier = HMAC_SHA3_224::new(&key).unwrap(); - mac_val.extend_from_slice(&[0u8; 4]); - assert!(!verifier.verify(b"Polly want a cracker?", &mac_val)); } #[test] @@ -300,11 +234,6 @@ mod hmac_tests { assert_eq!(HMAC_SHA512_256_NAME, "HMAC-SHA512/256"); assert_eq!(HMAC_SHA512_224::MAX_SECURITY_STRENGTH, SecurityStrength::_112bit); assert_eq!(HMAC_SHA512_256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); - assert_eq!(HMAC_SHA3_224::ALG_NAME, HMAC_SHA3_224_NAME); - assert_eq!(HMAC_SHA3_256::ALG_NAME, HMAC_SHA3_256_NAME); - assert_eq!(HMAC_SHA3_384::ALG_NAME, HMAC_SHA3_384_NAME); - assert_eq!(HMAC_SHA3_512::ALG_NAME, HMAC_SHA3_512_NAME); - assert_eq!(HMAC_SM3::ALG_NAME, HMAC_SM3_NAME); } #[cfg(test)] @@ -718,65 +647,6 @@ mod hmac_tests { } } - /// HMAC-SM3 known answers. There is no RFC 4231 equivalent for SM3, so these reuse the RFC 4231 - /// keys/messages (cases 1, 2 and 6) with expected values generated by - /// `openssl dgst -sm3 -mac HMAC` and independently confirmed with bc-java's - /// `HMac(new SM3Digest())`, plus a zero-length key. - #[test] - fn hmac_sm3_known_answers() { - use bouncycastle_core::key_material::KeyMaterial; - let test_framework = TestFrameworkMAC::new(); - - // RFC4231 Test Case 1 key/message - test_framework.test_mac::( - &KeyMaterial::<20>::from_bytes_as_type( - &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), - KeyType::MACKey, - ) - .unwrap(), - b"Hi There", - &hex::decode("51b00d1fb49832bfb01c3ce27848e59f871d9ba938dc563b338ca964755cce70") - .unwrap(), - ); - // RFC4231 Test Case 2 key/message - test_framework.test_mac::( - &KeyMaterial::<4>::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), - b"what do ya want for nothing?", - &hex::decode("2e87f1d16862e6d964b50a5200bf2b10b764faa9680a296a2405f24bec39f882") - .unwrap(), - ); - // RFC4231 Test Case 6 key/message: key larger than the 64-byte block, so it is hashed first - test_framework.test_mac::( - &KeyMaterial::<131>::from_bytes_as_type(&[0xaa; 131], KeyType::MACKey).unwrap(), - b"Test Using Larger Than Block-Size Key - Hash Key First", - &hex::decode("b4fd844e13342002f0b2e0690ea7741f1497d993a70494cea601e657bedf67a0") - .unwrap(), - ); - - // zero-length key (weak; needs new_allow_weak_key) - let mut zero_length_key = KeyMaterial256::default(); - key_material::do_hazardous_operations(&mut zero_length_key, |k| { - k.set_key_type(KeyType::MACKey) - }) - .unwrap(); - let mut mac = HMAC_SM3::new_allow_weak_key(&zero_length_key).unwrap(); - mac.do_update(b"abc"); - assert_eq!( - mac.do_final(), - hex::decode("36525058ca466791502435c910517f1a7e86613d5f35ac1f18a94def0eaac81f") - .unwrap() - ); - - assert_eq!( - HMAC_SM3::new( - &KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap() - ) - .unwrap() - .output_len(), - 32 - ); - } - #[test] fn suspendable_keyed_state() { use bouncycastle_core::errors::SuspendableError; @@ -834,8 +704,6 @@ mod hmac_tests { round_trip(HMAC_SHA512::new(&key).unwrap(), &key, msg); round_trip(HMAC_SHA512_224::new(&key).unwrap(), &key, msg); round_trip(HMAC_SHA512_256::new(&key).unwrap(), &key, msg); - round_trip(HMAC_SHA3_256::new(&key).unwrap(), &key, msg); - round_trip(HMAC_SM3::new(&key).unwrap(), &key, msg); // test suspend / resume with a key larger than block size let long_key = @@ -867,7 +735,7 @@ mod hmac_tests { /// * `HMAC::new(&key)` accepts the freshly generated key, without error. /// /// HashDRBG_SHA512 is used throughout because it is the only built-in DRBG that meets the - /// 256-bit strength that HMAC-SHA512 and HMAC-SHA3-512 claim; see `keygen_rejects_weak_rng`. + /// 256-bit strength that HMAC-SHA512 claims; see `keygen_rejects_weak_rng`. macro_rules! keygen_test { ($test_name:ident, $hmac:ident, $n:literal) => { #[test] @@ -893,11 +761,6 @@ mod hmac_tests { keygen_test!(keygen_hmac_sha512, HMAC_SHA512, 64); keygen_test!(keygen_hmac_sha512_224, HMAC_SHA512_224, 28); keygen_test!(keygen_hmac_sha512_256, HMAC_SHA512_256, 32); - keygen_test!(keygen_hmac_sha3_224, HMAC_SHA3_224, 28); - keygen_test!(keygen_hmac_sha3_256, HMAC_SHA3_256, 32); - keygen_test!(keygen_hmac_sha3_384, HMAC_SHA3_384, 48); - keygen_test!(keygen_hmac_sha3_512, HMAC_SHA3_512, 64); - keygen_test!(keygen_hmac_sm3, HMAC_SM3, 32); /// `keygen_from_rng` must refuse an RNG whose security strength is below the strength the HMAC /// claims, otherwise the returned key would be tagged stronger than the entropy behind it. diff --git a/crypto/sha3/tests/hmac_tests.rs b/crypto/sha3/tests/hmac_tests.rs new file mode 100644 index 00000000..0e777386 --- /dev/null +++ b/crypto/sha3/tests/hmac_tests.rs @@ -0,0 +1,142 @@ +#[cfg(test)] +mod hmac_sha3_tests { + use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; + use bouncycastle_core::traits::{Algorithm, MAC}; + use bouncycastle_core_test_framework::DUMMY_SEED; + use bouncycastle_rng::HashDRBG_SHA512; + use bouncycastle_sha3::hmac::*; + + #[test] + fn long_key() { + // Regression test: a key just under the maximum length before HMAC will hash it down. + // (RFC 2104 only pre-hashes keys *longer* than the block). + // This test is designed to detect an overflow-write and panic on HMAC's internal key buffer. + + // SHA3-224 has the largest block (144 bytes); a 143-byte key exercises the top of the range. + let key = KeyMaterial::<200>::from_bytes_as_type(&[0x0B; 143], KeyType::MACKey).unwrap(); + let mut mac = HMAC_SHA3_224::new(&key).unwrap(); + mac.do_update(b"Hi There"); + let tag = mac.do_final(); + assert!(HMAC_SHA3_224::new(&key).unwrap().verify(b"Hi There", &tag)); + } + + #[test] + fn negative_tests() { + let key = KeyMaterial256::from_bytes_as_type( + b"\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", + KeyType::MACKey, + ) + .unwrap(); + + // fail case: mac value is correct but truncated + let mac = HMAC_SHA3_224::new(&key).unwrap(); + let mut mac_val = mac.mac(b"Polly want a cracker?"); + let verifier = HMAC_SHA3_224::new(&key).unwrap(); + assert!(verifier.verify(b"Polly want a cracker?", &mac_val)); + + // truncation of the mac value is considered a fail + let verifier = HMAC_SHA3_224::new(&key).unwrap(); + assert!(!verifier.verify(b"Polly want a cracker?", &mac_val[..mac_val.len() - 1])); + + // .. as is some extra bytes at the end + let verifier = HMAC_SHA3_224::new(&key).unwrap(); + mac_val.extend_from_slice(&[0u8; 4]); + assert!(!verifier.verify(b"Polly want a cracker?", &mac_val)); + } + + #[test] + fn algorithm_tests() { + // Test the type aliases and string constants + assert_eq!(HMAC_SHA3_224::ALG_NAME, HMAC_SHA3_224_NAME); + assert_eq!(HMAC_SHA3_256::ALG_NAME, HMAC_SHA3_256_NAME); + assert_eq!(HMAC_SHA3_384::ALG_NAME, HMAC_SHA3_384_NAME); + assert_eq!(HMAC_SHA3_512::ALG_NAME, HMAC_SHA3_512_NAME); + } + + #[test] + fn suspendable_keyed_state() { + use bouncycastle_core::errors::SuspendableError; + use bouncycastle_core::suspendable_state::LIB_VERSION; + use bouncycastle_core::traits::SuspendableKeyed; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; + + let key = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap(); + let msg = b"Colorless green ideas sleep furiously"; + + // A helper that exercises the full round-trip for one HMAC variant. HMAC is keyed, so the + // key is NOT in the serialized state -- it is re-supplied (by reference) to + // from_serialized_state. + // The `+ 'static` on the trait object matches the associated type `type Key = dyn + // KeyMaterialTrait` (a bare `dyn` in an associated type defaults to `'static`). The concrete + // key types are owned, so they satisfy it. + fn round_trip( + mut hmac: H, + key: &(dyn KeyMaterialTrait + 'static), + input: &[u8], + ) where + H: MAC + Clone + SuspendableKeyed, + { + hmac.do_update(&input[..10]); + + // do the default trait-conformance tests + TestFrameworkSuspendableKeyedState::new().test(&hmac, key); + + // serialize the in-progress state (on a clone), then finish the original + let serialized_state = hmac.clone().suspend(); + + // the serialized state carries the library version header (from the inner hash) + let header: [u8; 3] = serialized_state[..3].try_into().unwrap(); + assert_eq!(header, <[u8; 3]>::from(LIB_VERSION)); + + hmac.do_update(&input[10..]); + let expected = hmac.do_final(); + + // rebuild from the serialized state (re-supplying the key), feed the identical remaining + // input, and confirm the MAC matches + let mut from_state = H::from_suspended(serialized_state, key).unwrap(); + from_state.do_update(&input[10..]); + assert_eq!(expected, from_state.do_final()); + + // a state whose version header is zeroed must be rejected (delegated to the hash's impl) + let mut busted = serialized_state; + busted[..3].copy_from_slice(&[0, 0, 0]); + match H::from_suspended(busted, key) { + Err(SuspendableError::IncompatibleVersion) => { /* good */ } + _ => panic!("Expected IncompatibleVersion for a zeroed version header"), + } + } + + round_trip(HMAC_SHA3_256::new(&key).unwrap(), &key, msg); + } + + /// Exercises the `keygen_from_rng()` function of each HMAC type alias: + /// * the generated key must not be the all-zero array, + /// * `keygen_from_rng()` returns a ready-to-use `KeyType::MACKey` key, so that + /// * `HMAC::new(&key)` accepts the freshly generated key, without error. + /// + /// HashDRBG_SHA512 is used throughout because it is the only built-in DRBG that meets the + /// 256-bit strength that HMAC-SHA3-512 claims. + macro_rules! keygen_test { + ($test_name:ident, $hmac:ident, $n:literal) => { + #[test] + fn $test_name() { + let mut rng = HashDRBG_SHA512::new_from_os(); + let key = $hmac::keygen_from_rng(&mut rng).expect("keygen_from_rng should succeed"); + + assert_eq!(key.key_len(), $n, "key should be the hash's output length"); + assert_eq!(key.key_type(), KeyType::MACKey, "keygen should return a MAC key"); + assert!( + key.ref_to_bytes().iter().any(|&b| b != 0), + "keygen produced an all-zero key" + ); + + $hmac::new(&key).expect("HMAC::new should accept a freshly generated key"); + } + }; + } + + keygen_test!(keygen_hmac_sha3_224, HMAC_SHA3_224, 28); + keygen_test!(keygen_hmac_sha3_256, HMAC_SHA3_256, 32); + keygen_test!(keygen_hmac_sha3_384, HMAC_SHA3_384, 48); + keygen_test!(keygen_hmac_sha3_512, HMAC_SHA3_512, 64); +} diff --git a/crypto/sm3/tests/hmac_tests.rs b/crypto/sm3/tests/hmac_tests.rs new file mode 100644 index 00000000..00e244a4 --- /dev/null +++ b/crypto/sm3/tests/hmac_tests.rs @@ -0,0 +1,157 @@ +#[cfg(test)] +mod hmac_sm3_tests { + use bouncycastle_core::key_material; + use bouncycastle_core::key_material::{KeyMaterial256, KeyMaterialTrait, KeyType}; + use bouncycastle_core::traits::{Algorithm, MAC}; + use bouncycastle_core_test_framework::DUMMY_SEED; + use bouncycastle_core_test_framework::mac::TestFrameworkMAC; + use bouncycastle_hex as hex; + use bouncycastle_rng::HashDRBG_SHA512; + use bouncycastle_sm3::hmac::*; + + /// HMAC-SM3 known answers. There is no RFC 4231 equivalent for SM3, so these reuse the RFC 4231 + /// keys/messages (cases 1, 2 and 6) with expected values generated by + /// `openssl dgst -sm3 -mac HMAC` and independently confirmed with bc-java's + /// `HMac(new SM3Digest())`, plus a zero-length key. + #[test] + fn hmac_sm3_known_answers() { + use bouncycastle_core::key_material::KeyMaterial; + let test_framework = TestFrameworkMAC::new(); + + // RFC4231 Test Case 1 key/message + test_framework.test_mac::( + &KeyMaterial::<20>::from_bytes_as_type( + &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), + KeyType::MACKey, + ) + .unwrap(), + b"Hi There", + &hex::decode("51b00d1fb49832bfb01c3ce27848e59f871d9ba938dc563b338ca964755cce70") + .unwrap(), + ); + // RFC4231 Test Case 2 key/message + test_framework.test_mac::( + &KeyMaterial::<4>::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), + b"what do ya want for nothing?", + &hex::decode("2e87f1d16862e6d964b50a5200bf2b10b764faa9680a296a2405f24bec39f882") + .unwrap(), + ); + // RFC4231 Test Case 6 key/message: key larger than the 64-byte block, so it is hashed first + test_framework.test_mac::( + &KeyMaterial::<131>::from_bytes_as_type(&[0xaa; 131], KeyType::MACKey).unwrap(), + b"Test Using Larger Than Block-Size Key - Hash Key First", + &hex::decode("b4fd844e13342002f0b2e0690ea7741f1497d993a70494cea601e657bedf67a0") + .unwrap(), + ); + + // zero-length key (weak; needs new_allow_weak_key) + let mut zero_length_key = KeyMaterial256::default(); + key_material::do_hazardous_operations(&mut zero_length_key, |k| { + k.set_key_type(KeyType::MACKey) + }) + .unwrap(); + let mut mac = HMAC_SM3::new_allow_weak_key(&zero_length_key).unwrap(); + mac.do_update(b"abc"); + assert_eq!( + mac.do_final(), + hex::decode("36525058ca466791502435c910517f1a7e86613d5f35ac1f18a94def0eaac81f") + .unwrap() + ); + + assert_eq!( + HMAC_SM3::new( + &KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap() + ) + .unwrap() + .output_len(), + 32 + ); + } + + #[test] + fn algorithm_tests() { + assert_eq!(HMAC_SM3::ALG_NAME, HMAC_SM3_NAME); + } + + #[test] + fn suspendable_keyed_state() { + use bouncycastle_core::errors::SuspendableError; + use bouncycastle_core::suspendable_state::LIB_VERSION; + use bouncycastle_core::traits::SuspendableKeyed; + use bouncycastle_core_test_framework::suspendable_state::TestFrameworkSuspendableKeyedState; + + let key = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::MACKey).unwrap(); + let msg = b"Colorless green ideas sleep furiously"; + + // A helper that exercises the full round-trip for one HMAC variant. HMAC is keyed, so the + // key is NOT in the serialized state -- it is re-supplied (by reference) to + // from_serialized_state. + // The `+ 'static` on the trait object matches the associated type `type Key = dyn + // KeyMaterialTrait` (a bare `dyn` in an associated type defaults to `'static`). The concrete + // key types are owned, so they satisfy it. + fn round_trip( + mut hmac: H, + key: &(dyn KeyMaterialTrait + 'static), + input: &[u8], + ) where + H: MAC + Clone + SuspendableKeyed, + { + hmac.do_update(&input[..10]); + + // do the default trait-conformance tests + TestFrameworkSuspendableKeyedState::new().test(&hmac, key); + + // serialize the in-progress state (on a clone), then finish the original + let serialized_state = hmac.clone().suspend(); + + // the serialized state carries the library version header (from the inner hash) + let header: [u8; 3] = serialized_state[..3].try_into().unwrap(); + assert_eq!(header, <[u8; 3]>::from(LIB_VERSION)); + + hmac.do_update(&input[10..]); + let expected = hmac.do_final(); + + // rebuild from the serialized state (re-supplying the key), feed the identical remaining + // input, and confirm the MAC matches + let mut from_state = H::from_suspended(serialized_state, key).unwrap(); + from_state.do_update(&input[10..]); + assert_eq!(expected, from_state.do_final()); + + // a state whose version header is zeroed must be rejected (delegated to the hash's impl) + let mut busted = serialized_state; + busted[..3].copy_from_slice(&[0, 0, 0]); + match H::from_suspended(busted, key) { + Err(SuspendableError::IncompatibleVersion) => { /* good */ } + _ => panic!("Expected IncompatibleVersion for a zeroed version header"), + } + } + + round_trip(HMAC_SM3::new(&key).unwrap(), &key, msg); + } + + /// Exercises the `keygen_from_rng()` function of each HMAC type alias: + /// * the generated key must not be the all-zero array, + /// * `keygen_from_rng()` returns a ready-to-use `KeyType::MACKey` key, so that + /// * `HMAC::new(&key)` accepts the freshly generated key, without error. + /// + macro_rules! keygen_test { + ($test_name:ident, $hmac:ident, $n:literal) => { + #[test] + fn $test_name() { + let mut rng = HashDRBG_SHA512::new_from_os(); + let key = $hmac::keygen_from_rng(&mut rng).expect("keygen_from_rng should succeed"); + + assert_eq!(key.key_len(), $n, "key should be the hash's output length"); + assert_eq!(key.key_type(), KeyType::MACKey, "keygen should return a MAC key"); + assert!( + key.ref_to_bytes().iter().any(|&b| b != 0), + "keygen produced an all-zero key" + ); + + $hmac::new(&key).expect("HMAC::new should accept a freshly generated key"); + } + }; + } + + keygen_test!(keygen_hmac_sm3, HMAC_SM3, 32); +} From 1602c05c5406f6da9a31cb8480ebf4028c9d668a Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sat, 12 Sep 2026 13:04:23 -0500 Subject: [PATCH 3/6] hmac: take the HMAC's security strength from the hash instead of restating it Assisted-by: Claude Code:claude-opus-5 --- crypto/hmac/src/lib.rs | 62 ++++++++++++++++++++++++++++------------- crypto/sha2/src/hmac.rs | 7 ----- crypto/sha3/src/hmac.rs | 5 ---- crypto/sm3/src/hmac.rs | 2 -- 4 files changed, 43 insertions(+), 33 deletions(-) diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index 0d38961e..4a54cc18 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -26,10 +26,12 @@ //! //! 1. Have a hash type that implements [`Hash`] + [`HashAlgParams`] + [`Default`]. Implementing //! [`Hash`] is documented in `bouncycastle-core`; nothing about it is HMAC-specific. -//! 2. Implement [`HMACParams`] for that hash type, supplying the HMAC's name, claimed security -//! strength and OID, plus the key type that [`HMAC::keygen_from_rng`] should return (typically a -//! `KeyMaterial` for an L that matches the size of the underlying hash function. This allows -//! this crate to provide blanket [`Algorithm`], [`AlgorithmOID`] and [`HMAC::keygen_from_rng`] impls. +//! 2. Implement [`HMACParams`] for that hash type, supplying the HMAC's name and OID, plus the key +//! type that [`HMAC::keygen_from_rng`] should return (typically a `KeyMaterial` for an L that +//! matches the size of the underlying hash function). This allows this crate to provide blanket +//! [`Algorithm`], [`AlgorithmOID`] and [`HMAC::keygen_from_rng`] impls. The claimed security +//! strength is not supplied here -- it is read off the hash's own +//! [`Algorithm::MAX_SECURITY_STRENGTH`]. //! 3. Publish a type alias for the instantiation, passing [`HashAlgParams::BLOCK_LEN`] as the key //! buffer length. Per RFC 2104 a key no longer than the hash's block is used verbatim, and only //! longer keys are pre-hashed down to the output length, so the buffer must hold a full block. @@ -49,7 +51,6 @@ //! impl HMACParams for SHA256 { //! type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; //! const HMAC_ALG_NAME: &'static str = "HMAC-SHA256"; -//! const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; //! /// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } //! const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 9]; //! const HMAC_OID_DER: &'static [u8] = @@ -67,11 +68,11 @@ //! These apply to every instantiation; the hash crates' `hmac` modules repeat the ones that matter //! most in day-to-day use. //! -//! * [`HMACParams::HMAC_MAX_SECURITY_STRENGTH`] is a claim that [`MAC::new`] enforces against the -//! key's tagged strength, and that [`HMAC::keygen_from_rng`] enforces against the RNG's. Declaring -//! a strength the underlying hash cannot support does not make the construction stronger, it just -//! makes the check wrong. NIST SP 800-107-r1 Section 5.3.4 gives the ceiling: the effective -//! strength is `min(strength of K, 2C)` for an internal chaining value of `C` bits. +//! * An HMAC claims the security strength of its underlying hash, and that claim is enforced in two +//! places: [`MAC::new`] checks it against the key's tagged strength, and +//! [`HMAC::keygen_from_rng`] checks it against the RNG's. NIST SP 800-107-r1 Section 5.3.4 gives +//! the ceiling: the effective strength is `min(strength of K, 2C)` for an internal chaining value +//! of `C` bits. //! * [`MAC::new_allow_weak_key`] deliberately skips the key-strength check. It exists for protocols //! that call for a weak or all-zero key -- an all-zero HKDF salt, for example -- and should not be //! used to silence an error from [`MAC::new`]. @@ -109,12 +110,27 @@ use core::fmt::{Debug, Display, Formatter}; /// [`HMAC::keygen_from_rng`] can be impl'd generically rather than being written out once /// per hash. /// -/// Each hash crate is expected to implement this trait for its own hash types and publishes the resulting type -/// alias. For example,`HMAC_SHA256` lives in `bouncycastle_sha2::hmac` and `HMAC_SHA3_256` in -/// `bouncycastle_sha3::hmac`. +/// Each hash crate is expected to implement this trait for its own hash types and publish the +/// resulting type alias. For example, `HMAC_SHA256` lives in `bouncycastle_sha2::hmac` and +/// `HMAC_SHA3_256` in `bouncycastle_sha3::hmac`. +/// +/// # Why the metadata comes in through a trait on the hash +/// +/// It is tempting to delete this trait and have each hash crate write the impls directly onto its +/// own alias instead -- `impl Algorithm for HMAC_SHA256` over in `bouncycastle-sha2`. That does not +/// compile. [`Algorithm`] belongs to `bouncycastle-core` and [`HMAC`] belongs to this crate, so both +/// are foreign there, and `HMAC` does not become a local type merely because `SHA256` +/// is: the orphan rule counts `Foreign` as foreign (RFC 2451), and rustc rejects the impl +/// with E0117. Only this crate may write `impl ... for HMAC<..>`, and this crate must not depend on +/// the hash crates -- breaking that cycle is the whole reason HMAC is a utility crate. A blanket +/// impl fed by a trait on the hash is therefore the only available shape, not a stylistic choice. /// /// The block length and the generated-key length do not need to be restated here since they are already /// carried by the hash itself as [`HashAlgParams::BLOCK_LEN`] and [`HashAlgParams::OUTPUT_LEN`]. +/// Neither does the claimed security strength: an HMAC claims the strength of its underlying hash, +/// so the blanket [`Algorithm`] impl reads [`Algorithm::MAX_SECURITY_STRENGTH`] straight off the +/// hash. Restating it here would only create a second copy that could disagree with the one +/// [`MAC::new`] actually enforces. pub trait HMACParams: Hash + HashAlgParams + Default { /// The key type produced by [`HMAC::keygen_from_rng`], sized to this hash's output length. /// @@ -125,11 +141,15 @@ pub trait HMACParams: Hash + HashAlgParams + Default { type MACKey: KeyMaterialTrait + Default; /// The name of the HMAC over this hash, as reported by [`Algorithm::ALG_NAME`]. + /// + /// This is always "HMAC-" followed by the hash's own [`Algorithm::ALG_NAME`] -- which is exactly + /// what [`HMAC`]'s [`Display`] impl formats at runtime -- but a `const` cannot concatenate two + /// `&'static str`s without an external crate, so it is restated here. const HMAC_ALG_NAME: &'static str; - /// The strength claimed by the HMAC over this hash, as reported by - /// [`Algorithm::MAX_SECURITY_STRENGTH`]. - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength; /// The OID of the HMAC over this hash in component form, as reported by [`AlgorithmOID::OID`]. + /// + /// Every hash/HMAC pair is assigned its own OID -- RFC 4231 for the SHA-2 family, the NIST + /// Computer Security Objects Register for SHA-3 -- so this cannot be derived from the hash's OID. const HMAC_OID: &'static [u32]; /// The DER encoding of [`HMACParams::HMAC_OID`], as reported by [`AlgorithmOID::OID_DER`]. const HMAC_OID_DER: &'static [u8]; @@ -137,7 +157,10 @@ pub trait HMACParams: Hash + HashAlgParams + Default { impl Algorithm for HMAC { const ALG_NAME: &'static str = HASH::HMAC_ALG_NAME; - const MAX_SECURITY_STRENGTH: SecurityStrength = HASH::HMAC_MAX_SECURITY_STRENGTH; + // An HMAC claims the security strength of the hash underneath it. Reading it off the hash rather + // than restating it per instantiation keeps the advertised strength identical to the one + // `HMAC::init` enforces against the key, which it takes from `Hash::max_security_strength`. + const MAX_SECURITY_STRENGTH: SecurityStrength = HASH::MAX_SECURITY_STRENGTH; } impl AlgorithmOID for HMAC { @@ -148,7 +171,8 @@ impl AlgorithmOID for HMAC` is written without an explicit buffer size; it is the // largest block length across all supported hashes, so it is always large enough. @@ -430,7 +454,7 @@ impl HMAC { pub fn keygen_from_rng(rng: &mut dyn RNG) -> Result { // Refuse to generate a key from an RNG that cannot back the strength this HMAC claims; // otherwise the key's tagged security strength would overstate its true entropy. - if rng.security_strength() < HASH::HMAC_MAX_SECURITY_STRENGTH { + if rng.security_strength() < HASH::MAX_SECURITY_STRENGTH { return Err(RNGError::SecurityStrengthInsufficientForAlgorithm); } diff --git a/crypto/sha2/src/hmac.rs b/crypto/sha2/src/hmac.rs index 7f61970a..1bc4c5eb 100644 --- a/crypto/sha2/src/hmac.rs +++ b/crypto/sha2/src/hmac.rs @@ -247,7 +247,6 @@ use crate::{SHA224, SHA256, SHA384, SHA512, SHA512_224, SHA512_256}; use crate::{SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN}; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::HashAlgParams; use bouncycastle_hmac::{HMAC, HMACParams}; @@ -280,7 +279,6 @@ pub type HMAC_SHA224 = HMAC::BLOCK_LEN }>; impl HMACParams for SHA224 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA224_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; /// Defined in RFC 4231: id-hmacWithSHA224 { digestAlgorithm 8 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 8]; const HMAC_OID_DER: &'static [u8] = @@ -293,7 +291,6 @@ pub type HMAC_SHA256 = HMAC::BLOCK_LEN }>; impl HMACParams for SHA256 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA256_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; /// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 9]; const HMAC_OID_DER: &'static [u8] = @@ -306,7 +303,6 @@ pub type HMAC_SHA384 = HMAC::BLOCK_LEN }>; impl HMACParams for SHA384 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA384_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; /// Defined in RFC 4231: id-hmacWithSHA384 { digestAlgorithm 10 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 10]; const HMAC_OID_DER: &'static [u8] = @@ -319,7 +315,6 @@ pub type HMAC_SHA512 = HMAC::BLOCK_LEN }>; impl HMACParams for SHA512 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA512_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; /// Defined in RFC 4231: id-hmacWithSHA512 { digestAlgorithm 11 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 11]; const HMAC_OID_DER: &'static [u8] = @@ -332,7 +327,6 @@ pub type HMAC_SHA512_224 = HMAC::BLO impl HMACParams for SHA512_224 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA512_224_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; /// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-224 { digestAlgorithm 12 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 12]; const HMAC_OID_DER: &'static [u8] = @@ -345,7 +339,6 @@ pub type HMAC_SHA512_256 = HMAC::BLO impl HMACParams for SHA512_256 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA512_256_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; /// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-256 { digestAlgorithm 13 } const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 13]; const HMAC_OID_DER: &'static [u8] = diff --git a/crypto/sha3/src/hmac.rs b/crypto/sha3/src/hmac.rs index 305d1207..52f14f61 100644 --- a/crypto/sha3/src/hmac.rs +++ b/crypto/sha3/src/hmac.rs @@ -254,7 +254,6 @@ use crate::SUSPENDED_SHA3_STATE_LEN; use crate::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::HashAlgParams; use bouncycastle_hmac::{HMAC, HMACParams}; @@ -283,7 +282,6 @@ pub type HMAC_SHA3_224 = HMAC::BLOCK_LEN impl HMACParams for SHA3_224 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA3_224_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-224 { hashAlgs 13 } const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 13]; const HMAC_OID_DER: &'static [u8] = @@ -296,7 +294,6 @@ pub type HMAC_SHA3_256 = HMAC::BLOCK_LEN impl HMACParams for SHA3_256 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA3_256_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-256 { hashAlgs 14 } const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 14]; const HMAC_OID_DER: &'static [u8] = @@ -309,7 +306,6 @@ pub type HMAC_SHA3_384 = HMAC::BLOCK_LEN impl HMACParams for SHA3_384 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA3_384_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-384 { hashAlgs 15 } const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 15]; const HMAC_OID_DER: &'static [u8] = @@ -322,7 +318,6 @@ pub type HMAC_SHA3_512 = HMAC::BLOCK_LEN impl HMACParams for SHA3_512 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SHA3_512_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-512 { hashAlgs 16 } const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 16]; const HMAC_OID_DER: &'static [u8] = diff --git a/crypto/sm3/src/hmac.rs b/crypto/sm3/src/hmac.rs index 0c0333f0..3e47b3dc 100644 --- a/crypto/sm3/src/hmac.rs +++ b/crypto/sm3/src/hmac.rs @@ -43,7 +43,6 @@ use crate::{SM3, SUSPENDED_SM3_STATE_LEN}; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::HashAlgParams; use bouncycastle_hmac::{HMAC, HMACParams}; @@ -67,7 +66,6 @@ pub type HMAC_SM3 = HMAC::BLOCK_LEN }>; impl HMACParams for SM3 { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; const HMAC_ALG_NAME: &'static str = HMAC_SM3_NAME; - const HMAC_MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; /// Assigned by the Chinese OSCCA (GM/T 0006): hmac-sm3 { sm3 2 } = 1.2.156.10197.1.401.2 const HMAC_OID: &'static [u32] = &[1, 2, 156, 10197, 1, 401, 2]; const HMAC_OID_DER: &'static [u8] = From 79887a14490e3337fbfac66bc2836873965ccc97 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sat, 12 Sep 2026 15:09:37 -0500 Subject: [PATCH 4/6] Refactoring of how the HMACParams work. --- crypto/hkdf/src/lib.rs | 46 ++++-- crypto/hkdf/tests/hkdf_tests.rs | 29 ++-- crypto/hmac/src/lib.rs | 242 +++++++++++++++--------------- crypto/sha2/src/hkdf.rs | 29 ++-- crypto/sha2/src/hmac.rs | 258 +++++++++++++++++++++++++------- crypto/sha2/tests/hmac_tests.rs | 114 +++++++------- crypto/sha3/src/hmac.rs | 160 +++++++++++++++----- crypto/sha3/tests/hmac_tests.rs | 13 +- crypto/sm3/src/hmac.rs | 40 +++-- 9 files changed, 619 insertions(+), 312 deletions(-) diff --git a/crypto/hkdf/src/lib.rs b/crypto/hkdf/src/lib.rs index 5b3c5e03..30c08fa4 100644 --- a/crypto/hkdf/src/lib.rs +++ b/crypto/hkdf/src/lib.rs @@ -47,6 +47,7 @@ //! use bouncycastle_core::key_material::{KeyMaterial256, KeyType}; //! use bouncycastle_core::traits::{KDF, SuspendableKeyed}; //! use bouncycastle_hkdf::HKDF; +//! use bouncycastle_sha2::hmac::HMAC_SHA384Params; //! use bouncycastle_sha2::{SHA384, SUSPENDED_SHA512_STATE_LEN}; //! //! // SHA-384 is a member of the SHA-512 family, so its suspended state is the SHA-512 one. @@ -54,7 +55,7 @@ //! //! #[allow(non_camel_case_types)] //! pub type HKDF_SHA384 = -//! HKDF; +//! HKDF; //! //! pub const HKDF_SHA384_NAME: &str = "HKDF-SHA384"; //! @@ -114,7 +115,7 @@ use bouncycastle_core::key_material::{ use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver}; use bouncycastle_core::traits::{Hash, HashAlgParams, KDF, MAC, Suspendable, SuspendableKeyed}; -use bouncycastle_hmac::HMAC; +use bouncycastle_hmac::{HMAC, HMACParams}; use bouncycastle_utils::{max, min}; use std::marker::PhantomData; // Imports needed only for docs @@ -159,13 +160,14 @@ pub const MAX_HMAC_OUTPUT_LEN: usize = 64; #[derive(Clone)] pub struct HKDF< H: Hash + HashAlgParams + Default, + PARAMS: HMACParams, const HASH_STATE_LEN: usize, const HKDF_STATE_LEN: usize, > { // Optional because an HMAC cannot be constructed until a key is provided // to initialize it with. // None must correspond to a state of Uninitialized. - hmac: Option>, + hmac: Option>, entropy: HkdfEntropyTracker, state: HkdfStates, } @@ -243,16 +245,24 @@ impl HkdfEntropyTracker { } } -impl - Default for HKDF +impl< + H: Hash + HashAlgParams + Default, + PARAMS: HMACParams, + const HASH_STATE_LEN: usize, + const HKDF_STATE_LEN: usize, +> Default for HKDF { fn default() -> Self { Self::new() } } -impl - HKDF +impl< + H: Hash + HashAlgParams + Default, + PARAMS: HMACParams, + const HASH_STATE_LEN: usize, + const HKDF_STATE_LEN: usize, +> HKDF { /// Get a new, uninstantiated HKDF object. pub fn new() -> Self { @@ -398,7 +408,7 @@ impl::new(&prk_as_mac_key) + let mut hmac = HMAC::::new(&prk_as_mac_key) .map_err(|_| KeyMaterialError::GenericError("HMAC initialization failed"))?; hmac.do_update(&T[..t_len]); hmac.do_update(info); @@ -417,7 +427,7 @@ impl::new(&prk_as_mac_key)?; + let mut hmac = HMAC::::new(&prk_as_mac_key)?; hmac.do_update(&T[..t_len]); hmac.do_update(info); hmac.do_update(&[i]); @@ -487,7 +497,7 @@ impl::new_allow_weak_key(salt)?); + self.hmac = Some(HMAC::::new_allow_weak_key(salt)?); let additional_entropy = self.entropy.credit_entropy(salt); self.state = HkdfStates::Initialized; @@ -520,7 +530,7 @@ impl = self.hmac.as_mut().unwrap(); + let hmac_ref: &mut HMAC = self.hmac.as_mut().unwrap(); hmac_ref.do_update(ikm.ref_to_bytes()); // self.hmac.as_mut().unwrap().do_update(ikm.ref_to_bytes()); @@ -609,8 +619,12 @@ impl - KDF for HKDF +impl< + H: Hash + HashAlgParams + Default, + PARAMS: HMACParams, + const HASH_STATE_LEN: usize, + const HKDF_STATE_LEN: usize, +> KDF for HKDF { /// This invokes [`HKDF::extract_and_expand_out`] with a zero salt and using the provided key as ikm. /// This provides a fixed-length output, which may be truncated as needed. @@ -736,8 +750,8 @@ impl SuspendableKeyed - for HKDF +impl + SuspendableKeyed for HKDF where H: Hash + HashAlgParams + Default + Suspendable, { @@ -785,7 +799,7 @@ where let hmac = match state[3] { 0 => None, // infallible: the sub-slice is exactly HASH_STATE_LEN bytes by const construction. - 1 => Some(HMAC::::from_suspended( + 1 => Some(HMAC::::from_suspended( state[4..4 + HASH_STATE_LEN].try_into().unwrap(), salt, )?), diff --git a/crypto/hkdf/tests/hkdf_tests.rs b/crypto/hkdf/tests/hkdf_tests.rs index ce911bd7..89b4f217 100644 --- a/crypto/hkdf/tests/hkdf_tests.rs +++ b/crypto/hkdf/tests/hkdf_tests.rs @@ -745,21 +745,22 @@ mod hkdf_tests { // A helper that exercises the full round-trip for one HKDF variant. A concrete `&KeyMaterial128` // works for `do_extract_init` (which wants a `Sized` `&impl KeyMaterialTrait`) and coerces to // `&dyn KeyMaterialTrait` for the serialization APIs. - fn round_trip( + fn round_trip( salt: &KeyMaterial128, part1: &[u8], part2: &[u8], ) where H: Hash + HashAlgParams + Default, - HKDF: Clone + SuspendableKeyed, + P: bouncycastle_hmac::HMACParams, + HKDF: Clone + SuspendableKeyed, { - let hkdf = HKDF::::new(); + let hkdf = HKDF::::new(); // it can be serialized pre-init, which is kinda a no-op, but at least it works. let serialized_state = hkdf.suspend(); assert_eq!(serialized_state.len(), LEN); let mut hkdf = - HKDF::::from_suspended(serialized_state, salt).unwrap(); + HKDF::::from_suspended(serialized_state, salt).unwrap(); hkdf.do_extract_init(salt).unwrap(); hkdf.do_extract_update_bytes(part1).unwrap(); @@ -776,19 +777,25 @@ mod hkdf_tests { // resume (re-supplying the salt), feed the identical remaining IKM, and compare PRKs let mut resumed = - HKDF::::from_suspended(serialized_state, salt).unwrap(); + HKDF::::from_suspended(serialized_state, salt).unwrap(); resumed.do_extract_update_bytes(part2).unwrap(); let prk_resumed = resumed.do_extract_final().unwrap(); assert_eq!(prk.ref_to_bytes(), prk_resumed.ref_to_bytes()); } - round_trip::( - &salt, part1, part2, - ); - round_trip::( - &salt, part1, part2, - ); + round_trip::< + SUSPENDED_SHA256_STATE_LEN, + SUSPENDED_HKDF_SHA256_STATE_LEN, + SHA256, + bouncycastle_sha2::hmac::HMAC_SHA256Params, + >(&salt, part1, part2); + round_trip::< + SUSPENDED_SHA512_STATE_LEN, + SUSPENDED_HKDF_SHA512_STATE_LEN, + SHA512, + bouncycastle_sha2::hmac::HMAC_SHA512Params, + >(&salt, part1, part2); // Test the guard for invalid states // testing just on HKDF_SHA256 diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index 4a54cc18..89b959c6 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -2,8 +2,8 @@ //! taking into account NIST Implementation Guidance in FIPS 140-2 IG A.8 and NIST SP 800-107-r1. //! //! This is a utility crate and is not intended to be used directly. It provides [`HMAC`] -- the -//! construction, generic over any struct that implements [`Hash`] and [`HMACParams`], the extension -//! point through which a hash declares the metadata from which the HMAC instance is built. +//! construction, generic over any struct that implements [`Hash`], paired with an [`HMACParams`] +//! marker type that carries the metadata the resulting instantiation reports about itself. //! The library provides the following concrete instantiations of HMAC: //! //! | Hash family | Instantiations | @@ -11,8 +11,9 @@ //! | SHA-2 | `bouncycastle_sha2::hmac` -- `HMAC_SHA224` .. `HMAC_SHA512_256` | //! | SHA-3 | `bouncycastle_sha3::hmac` -- `HMAC_SHA3_224` .. `HMAC_SHA3_512` | //! -//! Although users are free to implement [`Hash`] and [`HMACParams`] for a a hash function not included with the library, -//! and will then be able to instantiate [`HMAC`] for it as well. +//! Users are free to implement [`Hash`] for a hash function not included with the library and +//! declare their own [`HMACParams`] marker type for it, and will then be able to instantiate +//! [`HMAC`] over it as well. //! //! # Instantiating HMAC over a Hash //! @@ -22,21 +23,20 @@ //! rather than deriving it from the hash's OID. Supplying that metadata is what makes an HMAC a //! first-class algorithm in this library rather than an anonymous `HMAC`. //! -//! There are four steps, of which only the second is mandatory: +//! That metadata is supplied by a *params marker type*. +//! +//! There are four steps to implementing a new HMAC type: //! //! 1. Have a hash type that implements [`Hash`] + [`HashAlgParams`] + [`Default`]. Implementing //! [`Hash`] is documented in `bouncycastle-core`; nothing about it is HMAC-specific. -//! 2. Implement [`HMACParams`] for that hash type, supplying the HMAC's name and OID, plus the key -//! type that [`HMAC::keygen_from_rng`] should return (typically a `KeyMaterial` for an L that -//! matches the size of the underlying hash function). This allows this crate to provide blanket -//! [`Algorithm`], [`AlgorithmOID`] and [`HMAC::keygen_from_rng`] impls. The claimed security -//! strength is not supplied here -- it is read off the hash's own -//! [`Algorithm::MAX_SECURITY_STRENGTH`]. -//! 3. Publish a type alias for the instantiation, passing [`HashAlgParams::BLOCK_LEN`] as the key -//! buffer length. Per RFC 2104 a key no longer than the hash's block is used verbatim, and only -//! longer keys are pre-hashed down to the output length, so the buffer must hold a full block. -//! Reading the length off the hash rather than writing a literal means the two cannot drift apart. -//! 4. Optionally publish the suspended-state length as a constant. [`SuspendableKeyed`] is +//! 2. Declare a params type for the instantiation and give it [`Algorithm`] (the name and claimed +//! strength), [`AlgorithmOID`] (the OID and its DER encoding) and [`HMACParams`] (the key type +//! that [`HMAC::keygen_from_rng`] should return, and the internal key buffer). This crate then +//! provides the blanket [`Algorithm`], [`AlgorithmOID`] and [`HMAC::keygen_from_rng`] impls for +//! any [`HMAC`]. Take the buffer from [`HashAlgParams::BLOCK_LEN`] rather than writing a literal, +//! so it cannot drift away from the hash it has to hold a block of. +//! 3. Optionally, publish a type alias pairing the hash with the params. +//! 4. Optionally, publish the suspended-state length as a convenience constant. [`SuspendableKeyed`] is //! implemented automatically for any hash that implements [`Suspendable`], and HMAC's suspended //! state is exactly the inner hash's -- the key is deliberately excluded -- so the constant is //! just an alias for the hash's own. @@ -46,33 +46,52 @@ //! As an example, the `bouncycastle-sha2` crate follows exactly the recipe above; its entry for SHA-256 reduces to: //! //! ```rust,ignore -//! pub type HMAC_SHA256 = HMAC::BLOCK_LEN }>; +//! /// The parameters for HMAC-SHA256. +//! #[derive(Clone)] +//! pub struct HMAC_SHA256Params; //! -//! impl HMACParams for SHA256 { -//! type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; -//! const HMAC_ALG_NAME: &'static str = "HMAC-SHA256"; -//! /// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } -//! const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 9]; -//! const HMAC_OID_DER: &'static [u8] = +//! impl Algorithm for HMAC_SHA256Params { +//! const ALG_NAME: &'static str = "HMAC-SHA256"; +//! const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +//! } +//! +//! /// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } +//! impl AlgorithmOID for HMAC_SHA256Params { +//! const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 9]; +//! const OID_DER: &'static [u8] = //! &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09]; //! } //! +//! impl HMACParams for HMAC_SHA256Params { +//! type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; +//! type KeyBuf = [u8; ::BLOCK_LEN]; +//! } +//! +//! pub type HMAC_SHA256 = HMAC; +//! //! pub const SUSPENDED_HMAC_SHA256_STATE_LEN: usize = SUSPENDED_SHA256_STATE_LEN; //! ``` //! //! [`HMACParams`] is deliberately **not** sealed, so the same recipe works for a hash function //! defined in any other crate. Simply follow the recipe above! //! +//! Every [`HMAC`] carries a params type; there is no anonymous form. That includes an HMAC used as +//! an internal construction step: HKDF's extract phase is `PRK = HMAC-Hash(salt, IKM)` (RFC 5869 +//! Section 2.2), so the HMAC inside `HKDF_SHA256` really is HMAC-SHA256 and names +//! `HMAC_SHA256Params` accordingly rather than hiding behind a placeholder. +//! //! # Security Considerations //! //! These apply to every instantiation; the hash crates' `hmac` modules repeat the ones that matter //! most in day-to-day use. //! -//! * An HMAC claims the security strength of its underlying hash, and that claim is enforced in two -//! places: [`MAC::new`] checks it against the key's tagged strength, and -//! [`HMAC::keygen_from_rng`] checks it against the RNG's. NIST SP 800-107-r1 Section 5.3.4 gives +//! * The strength an HMAC claims is declared by its params, and is enforced in two places: +//! [`MAC::new`] checks it against the key's tagged strength, and [`HMAC::keygen_from_rng`] +//! checks it against the RNG's. It is not the underlying hash's strength: HMAC does not rest on +//! collision resistance, so the figures are independent. NIST SP 800-107-r1 Section 5.3.4 gives //! the ceiling: the effective strength is `min(strength of K, 2C)` for an internal chaining value -//! of `C` bits. +//! of `C` bits. Declaring a strength the construction cannot support does not make it stronger, +//! it just makes both checks wrong. //! * [`MAC::new_allow_weak_key`] deliberately skips the key-strength check. It exists for protocols //! that call for a weak or all-zero key -- an all-zero HKDF salt, for example -- and should not be //! used to silence an error from [`MAC::new`]. @@ -95,110 +114,61 @@ use bouncycastle_core::errors::{KeyMaterialError, MACError, RNGError, Suspendabl use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType}; use bouncycastle_core::security_strength::SecurityStrength; use bouncycastle_core::traits::{ - Algorithm, AlgorithmOID, Hash, HashAlgParams, MAC, RNG, Suspendable, SuspendableKeyed, + Algorithm, AlgorithmOID, Hash, MAC, RNG, Suspendable, SuspendableKeyed, }; +use bouncycastle_utils::secret::ZeroizablePrimitive; use bouncycastle_utils::{ct, secret::Secret}; use core::fmt::{Debug, Display, Formatter}; +use core::marker::PhantomData; + +/*** Imports needed for docs ***/ +#[allow(unused_imports)] +use bouncycastle_core::traits::HashAlgParams; /*** Parameters ***/ -/// The HMAC-specific parameters for one underlying hash function. -/// -/// [`HMAC`] itself is fully generic: it works with any [`Hash`], including hashes supplied by crates -/// outside this library. What HMAC cannot derive on its own is the *metadata* of the resulting -/// construction -- this trait supplies exactly that metadata, so the blanket [`Algorithm`], [`AlgorithmOID`] and -/// [`HMAC::keygen_from_rng`] can be impl'd generically rather than being written out once -/// per hash. -/// -/// Each hash crate is expected to implement this trait for its own hash types and publish the -/// resulting type alias. For example, `HMAC_SHA256` lives in `bouncycastle_sha2::hmac` and -/// `HMAC_SHA3_256` in `bouncycastle_sha3::hmac`. -/// -/// # Why the metadata comes in through a trait on the hash -/// -/// It is tempting to delete this trait and have each hash crate write the impls directly onto its -/// own alias instead -- `impl Algorithm for HMAC_SHA256` over in `bouncycastle-sha2`. That does not -/// compile. [`Algorithm`] belongs to `bouncycastle-core` and [`HMAC`] belongs to this crate, so both -/// are foreign there, and `HMAC` does not become a local type merely because `SHA256` -/// is: the orphan rule counts `Foreign` as foreign (RFC 2451), and rustc rejects the impl -/// with E0117. Only this crate may write `impl ... for HMAC<..>`, and this crate must not depend on -/// the hash crates -- breaking that cycle is the whole reason HMAC is a utility crate. A blanket -/// impl fed by a trait on the hash is therefore the only available shape, not a stylistic choice. -/// -/// The block length and the generated-key length do not need to be restated here since they are already -/// carried by the hash itself as [`HashAlgParams::BLOCK_LEN`] and [`HashAlgParams::OUTPUT_LEN`]. -/// Neither does the claimed security strength: an HMAC claims the strength of its underlying hash, -/// so the blanket [`Algorithm`] impl reads [`Algorithm::MAX_SECURITY_STRENGTH`] straight off the -/// hash. Restating it here would only create a second copy that could disagree with the one -/// [`MAC::new`] actually enforces. -pub trait HMACParams: Hash + HashAlgParams + Default { - /// The key type produced by [`HMAC::keygen_from_rng`], sized to this hash's output length. +/// The metadata of one concrete HMAC instantiation, supplied as a marker type. +pub trait HMACParams: Algorithm + AlgorithmOID { + /// The type of key that this HMAC instance needs, sized to the underlying hash's output length. /// - /// Implementors should set this to `KeyMaterial<{Self::OUTPUT_LEN}>`. + /// Implementors should set this to `KeyMaterial<{ ::OUTPUT_LEN }>`. /// // todo: once rust stabilizes generic_const_exprs, delete this and return // `KeyMaterial<{Self::OUTPUT_LEN}>` from `keygen_from_rng` instead. type MACKey: KeyMaterialTrait + Default; - /// The name of the HMAC over this hash, as reported by [`Algorithm::ALG_NAME`]. + /// The internal key buffer. /// - /// This is always "HMAC-" followed by the hash's own [`Algorithm::ALG_NAME`] -- which is exactly - /// what [`HMAC`]'s [`Display`] impl formats at runtime -- but a `const` cannot concatenate two - /// `&'static str`s without an external crate, so it is restated here. - const HMAC_ALG_NAME: &'static str; - /// The OID of the HMAC over this hash in component form, as reported by [`AlgorithmOID::OID`]. + /// It must be able to hold a key up to the *block length* of the underlying hash: per RFC 2104 + /// a key no longer than the block is used verbatim, and only longer keys are pre-hashed down to + /// the output length, so the buffer has to fit a whole block. /// - /// Every hash/HMAC pair is assigned its own OID -- RFC 4231 for the SHA-2 family, the NIST - /// Computer Security Objects Register for SHA-3 -- so this cannot be derived from the hash's OID. - const HMAC_OID: &'static [u32]; - /// The DER encoding of [`HMACParams::HMAC_OID`], as reported by [`AlgorithmOID::OID_DER`]. - const HMAC_OID_DER: &'static [u8]; -} - -impl Algorithm for HMAC { - const ALG_NAME: &'static str = HASH::HMAC_ALG_NAME; - // An HMAC claims the security strength of the hash underneath it. Reading it off the hash rather - // than restating it per instantiation keeps the advertised strength identical to the one - // `HMAC::init` enforces against the key, which it takes from `Hash::max_security_strength`. - const MAX_SECURITY_STRENGTH: SecurityStrength = HASH::MAX_SECURITY_STRENGTH; -} - -impl AlgorithmOID for HMAC { - const OID: &'static [u32] = HASH::HMAC_OID; - const OID_DER: &'static [u8] = HASH::HMAC_OID_DER; + /// Implementors should set this to `[u8; { ::BLOCK_LEN }]`. + type KeyBuf: ZeroizablePrimitive + AsRef<[u8]> + AsMut<[u8]>; } -// The internal key buffer must be able to hold a key up to the *block length* of the underlying hash: -// per RFC 2104, a key no longer than the block is used verbatim (only longer keys are pre-hashed down -// to the output length). So the buffer size is a const parameter of the struct, set per hash to its -// block length by the type aliases below. Block lengths (bytes): SHA-224/256 = 64, SHA-384/512 = 128, -// SHA3-224 = 144, SHA3-256 = 136, SHA3-384 = 104, SHA3-512 = 72. -// -// The default is used only when `HMAC` is written without an explicit buffer size; it is the -// largest block length across all supported hashes, so it is always large enough. -const LARGEST_HASHER_BLOCK_LEN: usize = 144; - /// Internal struct for HKDF. /// HMAC implements RFC 2104. /// Can, in theory, be instantiated with hash functions other than the ones provided by this crate (even custom ones). #[derive(Clone)] -pub struct HMAC { +pub struct HMAC { + _phantom_params: PhantomData, hasher: HASH, - // todo: once rust stable merges generic_const_exprs, we can remove this hack and delete the KEY_BUF_LEN param. - // key: [u8; HASH::OUTPUT_LEN]; - key: Secret<[u8; KEY_BUF_LEN]>, + // Sized by the params ([`HMACParams::KeyBuf`]), which take it from the hash's block length, so + // the caller cannot pick a buffer that does not fit the hash. + key: Secret, key_len: Secret, // Doing it this way to avoid needing a vec, so that this can be made no_std friendly. } -impl Debug for HMAC { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "HMAC-{} instance", HASH::ALG_NAME,) +impl Debug for HMAC { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{} instance", PARAMS::ALG_NAME,) } } -impl Display for HMAC { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "HMAC-{} instance", HASH::ALG_NAME,) +impl Display for HMAC { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{} instance", PARAMS::ALG_NAME,) } } @@ -218,13 +188,13 @@ const OPAD_BYTE: u8 = 0x5C; /// = 32 bits / 8 = 4 bytes; pub const MIN_FIPS_DIGEST_LEN: usize = 4; -impl HMAC { +impl HMAC { fn pad_key_into_hasher(&mut self, padding: u8) { // TODO: it would be nice to be able to statically extract the length of HASH and not need a Vec or over-sized array here. // TODO: make this no_std-friendly let mut padded = vec![0u8; self.hasher.block_bitlen() / 8]; - padded[..*self.key_len].copy_from_slice(&self.key[..*self.key_len]); + padded[..*self.key_len].copy_from_slice(&self.key.as_ref()[..*self.key_len]); // XXX: easier way to xor over Vec? for entry in &mut padded { @@ -243,16 +213,16 @@ impl HMAC { fn load_key_material(&mut self, key_bytes: &[u8]) { if key_bytes.len() > self.hasher.block_bitlen() / 8 { // then we have to pre-hash it -- use a new instance of the hasher rather than the internal one - HASH::default().hash_out(key_bytes, &mut self.key[..self.hasher.output_len()]); + HASH::default().hash_out(key_bytes, &mut self.key.as_mut()[..self.hasher.output_len()]); *self.key_len = self.hasher.output_len(); } else { - self.key[..key_bytes.len()].copy_from_slice(key_bytes); + self.key.as_mut()[..key_bytes.len()].copy_from_slice(key_bytes); *self.key_len = key_bytes.len(); } // Just as a sanity-check. assert!( - *self.key_len <= KEY_BUF_LEN, + *self.key_len <= self.key.as_ref().len(), "Fatal error: Key length exceeds HMAC internal buffer length" ); } @@ -279,7 +249,7 @@ impl HMAC { self.pad_key_into_hasher(IPAD_BYTE); // check that the key had enough security level - if !allow_weak_keys && key.security_strength() < HASH::default().max_security_strength() { + if !allow_weak_keys && key.security_strength() < PARAMS::MAX_SECURITY_STRENGTH { Err(KeyMaterialError::SecurityStrength( "HMAC::init(): provided key has a lower security strength than the instantiated HMAC", ))? @@ -323,15 +293,25 @@ impl HMAC { // TODO: This is essentially a "batch mode" where you want to perform many MACs or Verifications with the same key // TODO: against different data. -impl MAC for HMAC { +impl MAC for HMAC { fn new(key: &impl KeyMaterialTrait) -> Result { - let mut hmac = Self { hasher: HASH::default(), key: Secret::new(), key_len: Secret::new() }; + let mut hmac = Self { + _phantom_params: PhantomData, + hasher: HASH::default(), + key: Secret::new(), + key_len: Secret::new(), + }; hmac.init(key, false)?; Ok(hmac) } fn new_allow_weak_key(key: &impl KeyMaterialTrait) -> Result { - let mut hmac = Self { hasher: HASH::default(), key: Secret::new(), key_len: Secret::new() }; + let mut hmac = Self { + _phantom_params: PhantomData, + hasher: HASH::default(), + key: Secret::new(), + key_len: Secret::new(), + }; hmac.init(key, true)?; Ok(hmac) } @@ -384,7 +364,9 @@ impl MAC for HMAC SecurityStrength { - HASH::default().max_security_strength() + // Same source as the key check in `init()`, so the strength this reports and the strength + // that gets enforced cannot drift apart. + PARAMS::MAX_SECURITY_STRENGTH } } @@ -405,9 +387,9 @@ impl MAC for HMAC, -> SuspendableKeyed for HMAC + PARAMS: HMACParams, +> SuspendableKeyed for HMAC { // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait` // rather than a single concrete key type. The key is only used (by reference) to reload the key @@ -432,7 +414,12 @@ impl< // Re-load the key material exactly as `new()` did (pre-hashing an over-length key), but do // NOT re-absorb `K ⊕ ipad` — the deserialized hasher already contains it. The key is only // needed for the outer `K ⊕ opad` step at finalization. - let mut hmac = HMAC { hasher, key: Secret::new(), key_len: Secret::new() }; + let mut hmac = HMAC { + _phantom_params: PhantomData, + hasher, + key: Secret::new(), + key_len: Secret::new(), + }; hmac.load_key_material(key.ref_to_bytes()); Ok(hmac) @@ -441,7 +428,7 @@ impl< /* KeyGen functions */ -impl HMAC { +impl HMAC { /// Generates a key of the appropriate length for this HMAC from the provided RNG, tagged /// [`KeyType::MACKey`] and ready to hand to [`MAC::new`]. /// @@ -451,16 +438,29 @@ impl HMAC { // Dev note: done this way to avoid this crate needing a dependency on the `bouncycastle-rng` crate, // which itself has a dependency on `bouncycastle-sha2` which depends on this hmac crate, // which creates a circular cargo dependency. - pub fn keygen_from_rng(rng: &mut dyn RNG) -> Result { + pub fn keygen_from_rng(rng: &mut dyn RNG) -> Result { // Refuse to generate a key from an RNG that cannot back the strength this HMAC claims; // otherwise the key's tagged security strength would overstate its true entropy. - if rng.security_strength() < HASH::MAX_SECURITY_STRENGTH { + if rng.security_strength() < PARAMS::MAX_SECURITY_STRENGTH { return Err(RNGError::SecurityStrengthInsufficientForAlgorithm); } - let mut key = HASH::MACKey::default(); + let mut key = PARAMS::MACKey::default(); rng.fill_keymaterial_out(&mut key)?; key.set_key_type(KeyType::MACKey)?; Ok(key) } } + +impl Algorithm for HMAC { + const ALG_NAME: &'static str = PARAMS::ALG_NAME; + // An HMAC claims the security strength of the hash underneath it. Reading it off the hash rather + // than restating it per instantiation keeps the advertised strength identical to the one + // `HMAC::init` enforces against the key, which it takes from `P::max_security_strength`. + const MAX_SECURITY_STRENGTH: SecurityStrength = PARAMS::MAX_SECURITY_STRENGTH; +} + +impl AlgorithmOID for HMAC { + const OID: &'static [u32] = PARAMS::OID; + const OID_DER: &'static [u8] = PARAMS::OID_DER; +} diff --git a/crypto/sha2/src/hkdf.rs b/crypto/sha2/src/hkdf.rs index 587826d3..304f2a9a 100644 --- a/crypto/sha2/src/hkdf.rs +++ b/crypto/sha2/src/hkdf.rs @@ -186,18 +186,17 @@ //! //! | Object | Size (bytes) | //! |---------------------------------------------------|--------------| -//! | `HKDF_SHA256` | 296 | -//! | `HKDF_SHA512` | 392 | +//! | `HKDF_SHA256` | 216 | +//! | `HKDF_SHA512` | 376 | //! | Suspended `HKDF_SHA256` state | 122 | //! | Suspended `HKDF_SHA512` state | 218 | //! -//! The object is an `Option` of the inner extract-phase HMAC -- 272 bytes for SHA-256, 368 for +//! The object is an `Option` of the inner extract-phase HMAC -- 192 bytes for SHA-256, 352 for //! SHA-512 -- plus 24 bytes of bookkeeping (the entropy counter, the accumulated security strength -//! and the state-machine tag, with padding). Note that the inner HMAC is written as `HMAC`, which -//! takes the *default* key buffer length: the largest block length across all supported hashes -//! (144 bytes) rather than the 64 or 128 that SHA-256 and SHA-512 actually need. So the inner -//! `HMAC` is 264 bytes where the published [`crate::hmac::HMAC_SHA256`] is 184, and an -//! `HKDF_SHA256` is correspondingly larger than the HMAC it is built on. +//! and the state-machine tag, with padding). The inner HMAC is the published +//! [`crate::hmac::HMAC_SHA256`] rather than an anonymous one, so its key buffer is sized to the +//! hash's block length like any other: an `HKDF_SHA256` is 32 bytes larger than the HMAC it is +//! built on, not 112. //! //! The suspended state is the inner HMAC's suspended state (which is the hash's) plus 14 bytes; the //! salt is deliberately excluded and must be re-supplied on resume. @@ -247,7 +246,17 @@ pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_L /*** Type aliases ***/ /// Public type for HKDF using SHA256. #[allow(non_camel_case_types)] -pub type HKDF_SHA256 = HKDF; +pub type HKDF_SHA256 = HKDF< + SHA256, + crate::hmac::HMAC_SHA256Params, + SUSPENDED_SHA256_STATE_LEN, + SUSPENDED_HKDF_SHA256_STATE_LEN, +>; /// Public type for HKDF using SHA512. #[allow(non_camel_case_types)] -pub type HKDF_SHA512 = HKDF; +pub type HKDF_SHA512 = HKDF< + SHA512, + crate::hmac::HMAC_SHA512Params, + SUSPENDED_SHA512_STATE_LEN, + SUSPENDED_HKDF_SHA512_STATE_LEN, +>; diff --git a/crypto/sha2/src/hmac.rs b/crypto/sha2/src/hmac.rs index 1bc4c5eb..b9ab5e1b 100644 --- a/crypto/sha2/src/hmac.rs +++ b/crypto/sha2/src/hmac.rs @@ -4,14 +4,17 @@ //! Uses [`bouncycastle_hmac`] to provide the HMAC-SHA2 instantiations: [`HMAC_SHA224`], //! [`HMAC_SHA256`], [`HMAC_SHA384`] and [`HMAC_SHA512`]. //! -//! HMAC itself is implemented generically in [`bouncycastle_hmac`]; this module supplies the -//! SHA-2-specific parameters via [`HMACParams`] and publishes the resulting type aliases, so that +//! HMAC itself is implemented generically in [`bouncycastle_hmac`]; this module declares one +//! [`HMACParams`] marker type per instantiation (such as [`HMAC_SHA256Params`]), carrying that +//! HMAC's name, claimed strength, OID and key type, and publishes the type alias pairing each +//! marker with its hash. This mirrors how the hashes themselves are built, where `SHA256` is +//! `SHA256Internal`. The upshot is that //! HMAC over a SHA2 hash is found in this crate, and [`bouncycastle_hmac`] serves as a utility crate //! rather than as part of library's public API. //! -//! The key buffer length of each alias is the underlying hash's block length: per RFC 2104, a key no -//! longer than the block is used verbatim, and only longer keys are pre-hashed down to the output -//! length, so the buffer must be able to hold a full block. It is taken from +//! Each params type sizes the internal key buffer to its hash's block length: per RFC 2104, a key +//! no longer than the block is used verbatim, and only longer keys are pre-hashed down to the +//! output length, so the buffer must be able to hold a full block. It is taken from //! [`HashAlgParams::BLOCK_LEN`] rather than restated as a literal so the two cannot drift apart. //! //! # Usage @@ -247,7 +250,8 @@ use crate::{SHA224, SHA256, SHA384, SHA512, SHA512_224, SHA512_256}; use crate::{SUSPENDED_SHA256_STATE_LEN, SUSPENDED_SHA512_STATE_LEN}; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::traits::HashAlgParams; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::{Algorithm, AlgorithmOID, HashAlgParams}; use bouncycastle_hmac::{HMAC, HMACParams}; /*** Imports needed for docs ***/ @@ -272,79 +276,223 @@ pub const HMAC_SHA512_224_NAME: &str = "HMAC-SHA512/224"; /// pub const HMAC_SHA512_256_NAME: &str = "HMAC-SHA512/256"; -/*** Type aliases ***/ -/// Public type for HMAC using SHA224. +/*** Params types and type aliases ***/ +// TODO: revise the MAX_SECURITY_STRENGTH that each params type below declares. +// +// They currently declare the underlying hash's collision strength (output length / 2): 112 bits for +// HMAC-SHA224, 128 for HMAC-SHA256, and so on. That figure is a leftover from when the strength was +// read off the hash, and NIST SP 800-107 Revision 1 (August 2012) Section 5.3.4 says collision +// resistance is the wrong basis for an HMAC: +// +// "The effective security strength of the HMAC key is the minimum of the security strength of +// K and the value of 2C. That is, security strength = min(security strength of K, 2C)." +// +// where C is "the bit length of the internal hash value that is denoted H in FIPS 180-4" -- the +// chaining value, not the output. Footnote 4 of that section is explicit that collisions are out of +// scope: "the collision attack is not considered in this document. In this Recommendation, the +// strength of the HMAC key is considered to be the amount of work required for an attacker who +// performs a brute-force attack to discover the HMAC key K or the first hash values (Hs) ...". +// +// Recommendation: raise them. Section 5.3.4 gives C = 256 for SHA-256 ("L = C for SHA-1, SHA-256, +// and SHA-512") and C = 512 for SHA-384 and SHA-512/t, so 2C is 512 and 1024 bits respectively -- +// far above any key this library accepts. It does not state C for SHA-224, but C >= L = 224 there, +// so 2C >= 448 and the conclusion is unchanged: the key's own strength is the binding term in every +// SHA-2 case. Since `MACKey` is `KeyMaterial`, a full-length key carries 224, 256, 384 +// and 512 bits for HMAC-SHA224/256/384/512, every one of which exceeds what is declared today, and +// all but the first exceed the top of `SecurityStrength`, so they would cap at `_256bit` (and +// HMAC-SHA224 at `_192bit`). +// +// Do NOT extrapolate this to the HMAC-SHA3 values in bouncycastle-sha3. SP 800-107r1 predates SHA-3 +// and Section 5.3.4 is written in terms of the FIPS 180-4 chaining value, which a sponge +// construction does not have. Those need their own reference (FIPS 202 / SP 800-185) read first. +// +// This is not a mechanical edit. MAX_SECURITY_STRENGTH is enforced, not merely reported, so raising +// it makes `MAC::new` reject keys it accepts today and `keygen_from_rng` reject RNGs it accepts +// today. Raising HMAC-SHA256 to `_256bit`, for example, would make HashDRBG_SHA256 insufficient to +// generate its own key and would invert the existing `keygen_rejects_weak_rng` test. Settle the +// policy question first: does this constant describe the ceiling the construction can reach, or the +// minimum a caller is required to bring to it? Those give different numbers. + +/// The parameters for HMAC-SHA224 -- see [`HMAC_SHA224`]. +#[derive(Clone)] #[allow(non_camel_case_types)] -pub type HMAC_SHA224 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA224 { +pub struct HMAC_SHA224Params; + +impl Algorithm for HMAC_SHA224Params { + const ALG_NAME: &'static str = HMAC_SHA224_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA224: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; +} + +/// Defined in RFC 4231: id-hmacWithSHA224 { digestAlgorithm 8 } +impl AlgorithmOID for HMAC_SHA224Params { + const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 8]; + const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x08]; +} + +impl HMACParams for HMAC_SHA224Params { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA224_NAME; - /// Defined in RFC 4231: id-hmacWithSHA224 { digestAlgorithm 8 } - const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 8]; - const HMAC_OID_DER: &'static [u8] = - &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x08]; + type KeyBuf = [u8; ::BLOCK_LEN]; } -/// Public type for HMAC using SHA256. +/// Public type for HMAC using SHA224. +#[allow(non_camel_case_types)] +pub type HMAC_SHA224 = HMAC; + +/// The parameters for HMAC-SHA256 -- see [`HMAC_SHA256`]. +#[derive(Clone)] #[allow(non_camel_case_types)] -pub type HMAC_SHA256 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA256 { +pub struct HMAC_SHA256Params; + +impl Algorithm for HMAC_SHA256Params { + const ALG_NAME: &'static str = HMAC_SHA256_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA256: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } +impl AlgorithmOID for HMAC_SHA256Params { + const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 9]; + const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09]; +} + +impl HMACParams for HMAC_SHA256Params { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA256_NAME; - /// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } - const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 9]; - const HMAC_OID_DER: &'static [u8] = - &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09]; + type KeyBuf = [u8; ::BLOCK_LEN]; } -/// Public type for HMAC using SHA384. +/// Public type for HMAC using SHA256. #[allow(non_camel_case_types)] -pub type HMAC_SHA384 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA384 { +pub type HMAC_SHA256 = HMAC; + +/// The parameters for HMAC-SHA384 -- see [`HMAC_SHA384`]. +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct HMAC_SHA384Params; + +impl Algorithm for HMAC_SHA384Params { + const ALG_NAME: &'static str = HMAC_SHA384_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA384: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; +} + +/// Defined in RFC 4231: id-hmacWithSHA384 { digestAlgorithm 10 } +impl AlgorithmOID for HMAC_SHA384Params { + const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 10]; + const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0a]; +} + +impl HMACParams for HMAC_SHA384Params { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA384_NAME; - /// Defined in RFC 4231: id-hmacWithSHA384 { digestAlgorithm 10 } - const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 10]; - const HMAC_OID_DER: &'static [u8] = - &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0a]; + type KeyBuf = [u8; ::BLOCK_LEN]; } -/// Public type for HMAC using SHA512. +/// Public type for HMAC using SHA384. #[allow(non_camel_case_types)] -pub type HMAC_SHA512 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA512 { +pub type HMAC_SHA384 = HMAC; + +/// The parameters for HMAC-SHA512 -- see [`HMAC_SHA512`]. +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct HMAC_SHA512Params; + +impl Algorithm for HMAC_SHA512Params { + const ALG_NAME: &'static str = HMAC_SHA512_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA512: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; +} + +/// Defined in RFC 4231: id-hmacWithSHA512 { digestAlgorithm 11 } +impl AlgorithmOID for HMAC_SHA512Params { + const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 11]; + const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0b]; +} + +impl HMACParams for HMAC_SHA512Params { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA512_NAME; - /// Defined in RFC 4231: id-hmacWithSHA512 { digestAlgorithm 11 } - const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 11]; - const HMAC_OID_DER: &'static [u8] = - &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0b]; + type KeyBuf = [u8; ::BLOCK_LEN]; } -/// Public type for HMAC using SHA512/224. +/// Public type for HMAC using SHA512. +#[allow(non_camel_case_types)] +pub type HMAC_SHA512 = HMAC; + +/// The parameters for HMAC-SHA512/224 -- see [`HMAC_SHA512_224`]. +#[derive(Clone)] #[allow(non_camel_case_types)] -pub type HMAC_SHA512_224 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA512_224 { +pub struct HMAC_SHA512_224Params; + +impl Algorithm for HMAC_SHA512_224Params { + const ALG_NAME: &'static str = HMAC_SHA512_224_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA512_224: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; +} + +/// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-224 { digestAlgorithm 12 } +impl AlgorithmOID for HMAC_SHA512_224Params { + const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 12]; + const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0c]; +} + +impl HMACParams for HMAC_SHA512_224Params { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA512_224_NAME; - /// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-224 { digestAlgorithm 12 } - const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 12]; - const HMAC_OID_DER: &'static [u8] = - &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0c]; + type KeyBuf = [u8; ::BLOCK_LEN]; } -/// Public type for HMAC using SHA512/256. +/// Public type for HMAC using SHA512/224. #[allow(non_camel_case_types)] -pub type HMAC_SHA512_256 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA512_256 { +pub type HMAC_SHA512_224 = HMAC; + +/// The parameters for HMAC-SHA512/256 -- see [`HMAC_SHA512_256`]. +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct HMAC_SHA512_256Params; + +impl Algorithm for HMAC_SHA512_256Params { + const ALG_NAME: &'static str = HMAC_SHA512_256_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA512_256: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-256 { digestAlgorithm 13 } +impl AlgorithmOID for HMAC_SHA512_256Params { + const OID: &'static [u32] = &[1, 2, 840, 113549, 2, 13]; + const OID_DER: &'static [u8] = &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0d]; +} + +impl HMACParams for HMAC_SHA512_256Params { type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA512_256_NAME; - /// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-256 { digestAlgorithm 13 } - const HMAC_OID: &'static [u32] = &[1, 2, 840, 113549, 2, 13]; - const HMAC_OID_DER: &'static [u8] = - &[0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0d]; + type KeyBuf = [u8; ::BLOCK_LEN]; } +/// Public type for HMAC using SHA512/256. +#[allow(non_camel_case_types)] +pub type HMAC_SHA512_256 = HMAC; + /*** Serialized-state length constants ***/ // HMAC's suspended state is exactly the inner hasher's state -- the key is deliberately excluded and // must be re-supplied on resume -- so each of these is the underlying hash's own state length. diff --git a/crypto/sha2/tests/hmac_tests.rs b/crypto/sha2/tests/hmac_tests.rs index 510bbdac..e25d62fe 100644 --- a/crypto/sha2/tests/hmac_tests.rs +++ b/crypto/sha2/tests/hmac_tests.rs @@ -6,11 +6,11 @@ mod hmac_sha2_tests { KeyMaterial, KeyMaterial256, KeyMaterial512, KeyMaterialTrait, KeyType, }; use bouncycastle_core::security_strength::SecurityStrength; - use bouncycastle_core::traits::{Algorithm, Hash, MAC}; + use bouncycastle_core::traits::{Algorithm, AlgorithmOID, Hash, MAC}; use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_core_test_framework::mac::TestFrameworkMAC; use bouncycastle_hex as hex; - use bouncycastle_hmac::{HMAC, MIN_FIPS_DIGEST_LEN}; + use bouncycastle_hmac::MIN_FIPS_DIGEST_LEN; use bouncycastle_rng::{HashDRBG_SHA256, HashDRBG_SHA512}; use bouncycastle_sha2::hmac::*; use bouncycastle_sha2::*; @@ -26,7 +26,7 @@ mod hmac_sha2_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - let mut mac = HMAC::::new_allow_weak_key(&zero_length_key).unwrap(); + let mut mac = HMAC_SHA256::new_allow_weak_key(&zero_length_key).unwrap(); mac.do_update("Hi There".as_bytes()); let output = mac.do_final(); assert_eq!(output, b"\xe4\x84\x11\x26\x27\x15\xc8\x37\x0c\xd5\xe7\xbf\x8e\x82\xbe\xf5\x3b\xd5\x37\x12\xd0\x07\xf3\x42\x93\x51\x84\x3b\x77\xc7\xbb\x9b"); @@ -37,7 +37,7 @@ mod hmac_sha2_tests { KeyType::MACKey, ) .unwrap(); - let mut mac = HMAC::::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new(&key).unwrap(); mac.do_update(b"Hi There"); let output = mac.do_final(); assert_eq!(output, b"\x89\x6f\xb1\x12\x8a\xbb\xdf\x19\x68\x32\x10\x7c\xd4\x9d\xf3\x3f\x47\xb4\xb1\x16\x99\x12\xba\x4f\x53\x68\x4b\x22"); @@ -48,7 +48,7 @@ mod hmac_sha2_tests { KeyType::MACKey, ) .unwrap(); - let mac = HMAC::::new(&key).unwrap(); + let mac = HMAC_SHA256::new(&key).unwrap(); // mac.do_update(b"").unwrap(); let output = mac.do_final(); assert_eq!( @@ -67,7 +67,7 @@ mod hmac_sha2_tests { .unwrap(); assert_eq!(short_key.security_strength(), SecurityStrength::_112bit); // key is too short, so it is expected to fail - match HMAC::::new(&short_key) { + match HMAC_SHA256::new(&short_key) { Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) => { /* good */ } _ => panic!( "This should have thrown a KeyMaterialError::SecurityStrength error but it didn't" @@ -75,7 +75,7 @@ mod hmac_sha2_tests { } // It works after allowing weak keys - HMAC::::new_allow_weak_key(&short_key).unwrap(); + HMAC_SHA256::new_allow_weak_key(&short_key).unwrap(); // It works with a long enough key let key = KeyMaterial256::from_bytes_as_type( @@ -83,7 +83,7 @@ mod hmac_sha2_tests { KeyType::MACKey, ) .unwrap(); - HMAC::::new(&key).unwrap(); + HMAC_SHA256::new(&key).unwrap(); } #[test] @@ -152,7 +152,7 @@ mod hmac_sha2_tests { assert_eq!(key.security_strength(), SecurityStrength::_256bit); key.set_security_strength(SecurityStrength::_128bit).unwrap(); // The call should fail, as the key's security strength is set below the required threshold - match HMAC::::new(&key) { + match HMAC_SHA512::new(&key) { Err(MACError::KeyMaterialError(KeyMaterialError::SecurityStrength(_))) => { /* fine */ } _ => { panic!( @@ -161,17 +161,17 @@ mod hmac_sha2_tests { } } // It passes after setting .allow_weak_keys() - let mut hmac = HMAC::::new_allow_weak_key(&key).unwrap(); + let mut hmac = HMAC_SHA512::new_allow_weak_key(&key).unwrap(); hmac.do_update(b"Hi There"); hmac.do_final(); // one-shot APIs still work with a weak key - let out = HMAC::::new_allow_weak_key(&key).unwrap().mac(b"Hi There"); - assert!(HMAC::::new_allow_weak_key(&key).unwrap().verify(b"Hi There", &out)); + let out = HMAC_SHA512::new_allow_weak_key(&key).unwrap().mac(b"Hi There"); + assert!(HMAC_SHA512::new_allow_weak_key(&key).unwrap().verify(b"Hi There", &out)); // likewise with pre-allocated buffers let mut out = [0u8; 64]; - HMAC::::new_allow_weak_key(&key).unwrap().mac_out(b"Hi There", &mut out).unwrap(); - assert!(HMAC::::new_allow_weak_key(&key).unwrap().verify(b"Hi There", &out)); + HMAC_SHA512::new_allow_weak_key(&key).unwrap().mac_out(b"Hi There", &mut out).unwrap(); + assert!(HMAC_SHA512::new_allow_weak_key(&key).unwrap().verify(b"Hi There", &out)); } #[test] @@ -183,15 +183,15 @@ mod hmac_sha2_tests { .unwrap(); // get the known-good output - let out = HMAC::::new(&key).unwrap().mac(b"Hi There"); + let out = HMAC_SHA224::new(&key).unwrap().mac(b"Hi There"); // test output that's the wrong length, should simply return False - let mut mac = HMAC::::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new(&key).unwrap(); mac.do_update(b"Hi There"); assert!(!mac.do_verify_final(&out[..out.len() - 1])); // test output that's the right length but wrong value -- do_verify - let mut mac = HMAC::::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new(&key).unwrap(); mac.do_update(b"Hi There"); assert!(!mac.do_verify_final(&[0x01_u8; 28])); @@ -199,7 +199,7 @@ mod hmac_sha2_tests { assert!(!HMAC_SHA224::new(&key).unwrap().verify(b"Hi There", &[0x01_u8; 28])); // error case: test that it'll refuse to truncate below MIN_FIPS_DIGEST_LEN - let mut mac = HMAC::::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new(&key).unwrap(); mac.do_update(b"Hi There"); let mut out = vec![0u8; MIN_FIPS_DIGEST_LEN - 1]; match mac.do_final_out(&mut out) { @@ -213,7 +213,7 @@ mod hmac_sha2_tests { } // success case: ... but it will truncate to exactly MIN_FIPS_DIGEST_LEN - let mut mac = HMAC::::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new(&key).unwrap(); mac.do_update(b"Hi There"); let mut out = vec![0u8; MIN_FIPS_DIGEST_LEN]; let bytes_written = mac.do_final_out(&mut out).unwrap(); @@ -234,6 +234,18 @@ mod hmac_sha2_tests { assert_eq!(HMAC_SHA512_256_NAME, "HMAC-SHA512/256"); assert_eq!(HMAC_SHA512_224::MAX_SECURITY_STRENGTH, SecurityStrength::_112bit); assert_eq!(HMAC_SHA512_256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + + assert_eq!(HMAC_SHA224::OID, [1, 2, 840, 113549, 2, 8]); + assert_eq!(HMAC_SHA256::OID, [1, 2, 840, 113549, 2, 9]); + assert_eq!(HMAC_SHA384::OID, [1, 2, 840, 113549, 2, 10]); + assert_eq!(HMAC_SHA512::OID, [1, 2, 840, 113549, 2, 11]); + + assert_eq!(HMAC_SHA224::MAX_SECURITY_STRENGTH, SecurityStrength::_112bit); + assert_eq!(HMAC_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(HMAC_SHA384::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); + assert_eq!(HMAC_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + assert_eq!(HMAC_SHA512_224::OID, [1, 2, 840, 113549, 2, 12]); + assert_eq!(HMAC_SHA512_256::OID, [1, 2, 840, 113549, 2, 13]); } #[cfg(test)] @@ -346,7 +358,7 @@ mod hmac_sha2_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - test_framework.test_mac::>( + test_framework.test_mac::( &zero_length_key, b"Hello, world", &hex::decode("57454372e6a8780b11150274d7056c6fbcffef902f9c23f24fbbfee9").unwrap(), @@ -354,7 +366,7 @@ mod hmac_sha2_tests { // RFC4231 Test Case 1 let test_framework = TestFrameworkMAC::new(); - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type( &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), KeyType::MACKey, @@ -365,7 +377,7 @@ mod hmac_sha2_tests { ); // RFC4231 Test Case 2 -- Test with a key shorter than the length of the HMAC output. - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), b"what do ya want for nothing?", &hex::decode("a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44").unwrap(), @@ -373,14 +385,14 @@ mod hmac_sha2_tests { // RFC4231 Test Case 3 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), &hex::decode("7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), &hex::decode("6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a").unwrap(), ); @@ -392,13 +404,13 @@ mod hmac_sha2_tests { ) .unwrap(); let mut out = [0u8; 128 / 8]; - HMAC::::new(&key).unwrap().mac_out(b"Test With Truncation", &mut out).unwrap(); + HMAC_SHA224::new(&key).unwrap().mac_out(b"Test With Truncation", &mut out).unwrap(); assert_eq!(&Vec::from(out), &hex::decode("0e2aea68a90c8d37c988bcdb9fca6fa8").unwrap()); // RFC4231 Test Case 6 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). let key = KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(); - test_framework.test_mac::>( + test_framework.test_mac::( &key, b"Test Using Larger Than Block-Size Key - Hash Key First", &hex::decode("95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e").unwrap(), @@ -406,7 +418,7 @@ mod hmac_sha2_tests { // RFC4231 Test Case 7 -- Test with a key and data that is larger than 128 bytes (= block-size // of SHA-384 and SHA-512) - test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", &hex::decode("3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1").unwrap(), ); @@ -424,7 +436,7 @@ mod hmac_sha2_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - test_framework.test_mac::>( + test_framework.test_mac::( &zero_length_key, b"Hello, world", &hex::decode("c0fa4c55880318c31c1020e7a2cf830c2c695716387795c7a0eb918ba84e4bf0") @@ -433,7 +445,7 @@ mod hmac_sha2_tests { // RFC4231 Test Case 1 let test_framework = TestFrameworkMAC::new(); - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type( &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), KeyType::MACKey, @@ -445,7 +457,7 @@ mod hmac_sha2_tests { ); // RFC4231 Test Case 2 -- Test with a key shorter than the length of the HMAC output. - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), b"what do ya want for nothing?", &hex::decode("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843") @@ -454,14 +466,14 @@ mod hmac_sha2_tests { // RFC4231 Test Case 3 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), &hex::decode("773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), &hex::decode("82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b").unwrap(), ); @@ -473,19 +485,19 @@ mod hmac_sha2_tests { ) .unwrap(); let mut out = [0u8; 128 / 8]; - HMAC::::new(&key).unwrap().mac_out(b"Test With Truncation", &mut out).unwrap(); + HMAC_SHA256::new(&key).unwrap().mac_out(b"Test With Truncation", &mut out).unwrap(); assert_eq!(&Vec::from(out), &hex::decode("a3b6167473100ee06e0c796c2955552b").unwrap()); // RFC4231 Test Case 6 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"Test Using Larger Than Block-Size Key - Hash Key First", &hex::decode("60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54").unwrap(), ); // RFC4231 Test Case 7 -- Test with a key and data that is larger than 128 bytes (= block-size // of SHA-384 and SHA-512) - test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", &hex::decode("9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2").unwrap(), ); @@ -503,7 +515,7 @@ mod hmac_sha2_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - test_framework.test_mac::>( + test_framework.test_mac::( &zero_length_key, b"Hello, world", &hex::decode("fbd41442f749049355175277afbaff610539e5bfa874c9cf86ef867a43a30b09a5eac6578d5c0cb1ceddc95f97598af7").unwrap(), @@ -511,7 +523,7 @@ mod hmac_sha2_tests { // RFC4231 Test Case 1 let test_framework = TestFrameworkMAC::new(); - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type( &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), KeyType::MACKey, @@ -521,7 +533,7 @@ mod hmac_sha2_tests { ); // RFC4231 Test Case 2 -- Test with a key shorter than the length of the HMAC output. - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), b"what do ya want for nothing?", &hex::decode("af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649").unwrap(), @@ -529,14 +541,14 @@ mod hmac_sha2_tests { // RFC4231 Test Case 3 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), &hex::decode("88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), &hex::decode("3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb").unwrap(), ); @@ -549,20 +561,20 @@ mod hmac_sha2_tests { .unwrap(); let mut out = [0u8; 128 / 8]; // Key is shorter than HMAC security strength, so it needs to use new_allow_weak_keys() - let hmac = HMAC::::new_allow_weak_key(&key).unwrap(); + let hmac = HMAC_SHA384::new_allow_weak_key(&key).unwrap(); hmac.mac_out(b"Test With Truncation", &mut out).unwrap(); assert_eq!(&Vec::from(out), &hex::decode("3abf34c3503b2a23a46efc619baef897").unwrap()); // RFC4231 Test Case 6 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"Test Using Larger Than Block-Size Key - Hash Key First", &hex::decode("4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952").unwrap(), ); // RFC4231 Test Case 7 -- Test with a key and data that is larger than 128 bytes (= block-size // of SHA-384 and SHA-512) - test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", &hex::decode("6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e").unwrap(), ); @@ -580,7 +592,7 @@ mod hmac_sha2_tests { assert_eq!(zero_length_key.key_len(), 0); assert_eq!(zero_length_key.key_type(), KeyType::MACKey); - test_framework.test_mac::>( + test_framework.test_mac::( &zero_length_key, b"Hello, world", &hex::decode("e8f7176e01bf9bb883f71f42c143681e86cfafe0b61f3bc0d824e2cde13b5f80199e82d865aebb725461c86a54086aeacac37a86a9f1cf07db567ba5a10f1cc1").unwrap(), @@ -588,7 +600,7 @@ mod hmac_sha2_tests { // RFC4231 Test Case 1 let test_framework = TestFrameworkMAC::new(); - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type( &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), KeyType::MACKey, @@ -599,7 +611,7 @@ mod hmac_sha2_tests { ); // RFC4231 Test Case 2 -- Test with a key shorter than the length of the HMAC output. - test_framework.test_mac::>( + test_framework.test_mac::( &KeyMaterial256::from_bytes_as_type(b"Jefe", KeyType::MACKey).unwrap(), b"what do ya want for nothing?", &hex::decode("164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737").unwrap(), @@ -607,14 +619,14 @@ mod hmac_sha2_tests { // RFC4231 Test Case 3 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd").unwrap(), &hex::decode("fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb").unwrap(), ); // RFC4231 Test Case 4 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial256::from_bytes_as_type(&hex::decode("0102030405060708090a0b0c0d0e0f10111213141516171819").unwrap(), KeyType::MACKey).unwrap(), &hex::decode("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd").unwrap(), &hex::decode("b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd").unwrap(), ); @@ -627,20 +639,20 @@ mod hmac_sha2_tests { .unwrap(); let mut out = [0u8; 128 / 8]; // Key is shorter than HMAC security strength, so need to use new_allow_weak_keys() - let hmac = HMAC::::new_allow_weak_key(&key).unwrap(); + let hmac = HMAC_SHA512::new_allow_weak_key(&key).unwrap(); hmac.mac_out(b"Test With Truncation", &mut out).unwrap(); assert_eq!(&Vec::from(out), &hex::decode("415fad6271580a531d4179bc891d87a6").unwrap()); // RFC4231 Test Case 6 -- Test with a combined length of key and data that is larger than 64 // bytes (= block-size of SHA-224 and SHA-256). - test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"Test Using Larger Than Block-Size Key - Hash Key First", &hex::decode("80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598").unwrap(), ); // RFC4231 Test Case 7 -- Test with a key and data that is larger than 128 bytes (= block-size // of SHA-384 and SHA-512) - test_framework.test_mac::>(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), + test_framework.test_mac::(&KeyMaterial::<131>::from_bytes_as_type(&hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), KeyType::MACKey).unwrap(), b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", &hex::decode("e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58").unwrap(), ); diff --git a/crypto/sha3/src/hmac.rs b/crypto/sha3/src/hmac.rs index 52f14f61..1a994515 100644 --- a/crypto/sha3/src/hmac.rs +++ b/crypto/sha3/src/hmac.rs @@ -4,12 +4,15 @@ //! Uses [`bouncycastle_hmac`] to provide the HMAC-SHA3 instantiations: [`HMAC_SHA3_224`], //! [`HMAC_SHA3_256`], [`HMAC_SHA3_384`] and [`HMAC_SHA3_512`]. //! -//! HMAC itself is implemented generically in [`bouncycastle_hmac`]; this module supplies the -//! SHA-3-specific parameters via [`HMACParams`] and publishes the resulting type aliases, so that +//! HMAC itself is implemented generically in [`bouncycastle_hmac`]; this module declares one +//! [`HMACParams`] marker type per instantiation (such as [`HMAC_SHA3_256Params`]), carrying that +//! HMAC's name, claimed strength, OID and key type, and publishes the type alias pairing each +//! marker with its hash. This mirrors how the hashes themselves are built, where `SHA256` is +//! `SHA256Internal`. The upshot is that //! HMAC over a SHA3 hash is found in this crate, and [`bouncycastle_hmac`] serves as a utility crate //! rather than as part of library's public API. //! -//! The key buffer length of each alias is the underlying hash's block length: per RFC 2104, a key no +//! Each params type sizes the internal key buffer to its hash's block length: per RFC 2104, a key no //! longer than the block is used verbatim, and only longer keys are pre-hashed down to the output //! length, so the buffer must be able to hold a full block. It is taken from //! [`HashAlgParams::BLOCK_LEN`] -- the values FIPS 202 Table 3 ("Input block sizes for HMAC") gives @@ -254,7 +257,8 @@ use crate::SUSPENDED_SHA3_STATE_LEN; use crate::{SHA3_224, SHA3_256, SHA3_384, SHA3_512}; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::traits::HashAlgParams; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::{Algorithm, AlgorithmOID, HashAlgParams}; use bouncycastle_hmac::{HMAC, HMACParams}; /*** Imports needed for docs ***/ @@ -275,55 +279,137 @@ pub const HMAC_SHA3_384_NAME: &str = "HMAC-SHA3-384"; /// pub const HMAC_SHA3_512_NAME: &str = "HMAC-SHA3-512"; -/*** Type aliases ***/ -/// Public type for HMAC using SHA3_224. +/*** Params types and type aliases ***/ +// TODO: the MAX_SECURITY_STRENGTH values below are each hash's collision strength, which NIST +// SP 800-107r1 Section 5.3.4 says is not the right basis for an HMAC. See the TODO above the +// equivalent params types in bouncycastle-sha2 for the analysis. Note that its recommendation +// covers SHA-2 only: SP 800-107r1 predates SHA-3 and reasons about a Merkle-Damgard chaining +// value, which a sponge does not have, so these need FIPS 202 / SP 800-185 read first. + +/// The parameters for HMAC-SHA3_224 -- see [`HMAC_SHA3_224`]. +#[derive(Clone)] #[allow(non_camel_case_types)] -pub type HMAC_SHA3_224 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA3_224 { - type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA3_224_NAME; - /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-224 { hashAlgs 13 } - const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 13]; - const HMAC_OID_DER: &'static [u8] = +pub struct HMAC_SHA3_224Params; + +impl Algorithm for HMAC_SHA3_224Params { + const ALG_NAME: &'static str = HMAC_SHA3_224_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA3_224: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; +} + +/// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-224 { hashAlgs 13 } +impl AlgorithmOID for HMAC_SHA3_224Params { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 13]; + const OID_DER: &'static [u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0d]; } -/// Public type for HMAC using SHA3_256. +impl HMACParams for HMAC_SHA3_224Params { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + type KeyBuf = [u8; ::BLOCK_LEN]; +} + +/// Public type for HMAC using SHA3_224. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_256 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA3_256 { - type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA3_256_NAME; - /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-256 { hashAlgs 14 } - const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 14]; - const HMAC_OID_DER: &'static [u8] = +pub type HMAC_SHA3_224 = HMAC; + +/// The parameters for HMAC-SHA3_256 -- see [`HMAC_SHA3_256`]. +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct HMAC_SHA3_256Params; + +impl Algorithm for HMAC_SHA3_256Params { + const ALG_NAME: &'static str = HMAC_SHA3_256_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA3_256: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-256 { hashAlgs 14 } +impl AlgorithmOID for HMAC_SHA3_256Params { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 14]; + const OID_DER: &'static [u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0e]; } -/// Public type for HMAC using SHA3_384. +impl HMACParams for HMAC_SHA3_256Params { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + type KeyBuf = [u8; ::BLOCK_LEN]; +} + +/// Public type for HMAC using SHA3_256. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_384 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA3_384 { - type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA3_384_NAME; - /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-384 { hashAlgs 15 } - const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 15]; - const HMAC_OID_DER: &'static [u8] = +pub type HMAC_SHA3_256 = HMAC; + +/// The parameters for HMAC-SHA3_384 -- see [`HMAC_SHA3_384`]. +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct HMAC_SHA3_384Params; + +impl Algorithm for HMAC_SHA3_384Params { + const ALG_NAME: &'static str = HMAC_SHA3_384_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA3_384: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; +} + +/// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-384 { hashAlgs 15 } +impl AlgorithmOID for HMAC_SHA3_384Params { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 15]; + const OID_DER: &'static [u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0f]; } -/// Public type for HMAC using SHA3_512. +impl HMACParams for HMAC_SHA3_384Params { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + type KeyBuf = [u8; ::BLOCK_LEN]; +} + +/// Public type for HMAC using SHA3_384. #[allow(non_camel_case_types)] -pub type HMAC_SHA3_512 = HMAC::BLOCK_LEN }>; -impl HMACParams for SHA3_512 { - type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SHA3_512_NAME; - /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-512 { hashAlgs 16 } - const HMAC_OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 16]; - const HMAC_OID_DER: &'static [u8] = +pub type HMAC_SHA3_384 = HMAC; + +/// The parameters for HMAC-SHA3_512 -- see [`HMAC_SHA3_512`]. +#[derive(Clone)] +#[allow(non_camel_case_types)] +pub struct HMAC_SHA3_512Params; + +impl Algorithm for HMAC_SHA3_512Params { + const ALG_NAME: &'static str = HMAC_SHA3_512_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SHA3_512: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; +} + +/// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-512 { hashAlgs 16 } +impl AlgorithmOID for HMAC_SHA3_512Params { + const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 16]; + const OID_DER: &'static [u8] = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x10]; } +impl HMACParams for HMAC_SHA3_512Params { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + type KeyBuf = [u8; ::BLOCK_LEN]; +} + +/// Public type for HMAC using SHA3_512. +#[allow(non_camel_case_types)] +pub type HMAC_SHA3_512 = HMAC; + /*** Serialized-state length constants ***/ // HMAC's suspended state is exactly the inner hasher's state -- the key is deliberately excluded and // must be re-supplied on resume -- so each of these is the underlying hash's own state length. All diff --git a/crypto/sha3/tests/hmac_tests.rs b/crypto/sha3/tests/hmac_tests.rs index 0e777386..9ba2f110 100644 --- a/crypto/sha3/tests/hmac_tests.rs +++ b/crypto/sha3/tests/hmac_tests.rs @@ -1,7 +1,8 @@ #[cfg(test)] mod hmac_sha3_tests { use bouncycastle_core::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialTrait, KeyType}; - use bouncycastle_core::traits::{Algorithm, MAC}; + use bouncycastle_core::security_strength::SecurityStrength; + use bouncycastle_core::traits::{Algorithm, AlgorithmOID, MAC}; use bouncycastle_core_test_framework::DUMMY_SEED; use bouncycastle_rng::HashDRBG_SHA512; use bouncycastle_sha3::hmac::*; @@ -51,6 +52,16 @@ mod hmac_sha3_tests { assert_eq!(HMAC_SHA3_256::ALG_NAME, HMAC_SHA3_256_NAME); assert_eq!(HMAC_SHA3_384::ALG_NAME, HMAC_SHA3_384_NAME); assert_eq!(HMAC_SHA3_512::ALG_NAME, HMAC_SHA3_512_NAME); + + assert_eq!(HMAC_SHA3_224::OID, [2, 16, 840, 1, 101, 3, 4, 2, 13]); + assert_eq!(HMAC_SHA3_256::OID, [2, 16, 840, 1, 101, 3, 4, 2, 14]); + assert_eq!(HMAC_SHA3_384::OID, [2, 16, 840, 1, 101, 3, 4, 2, 15]); + assert_eq!(HMAC_SHA3_512::OID, [2, 16, 840, 1, 101, 3, 4, 2, 16]); + + assert_eq!(HMAC_SHA3_224::MAX_SECURITY_STRENGTH, SecurityStrength::_112bit); + assert_eq!(HMAC_SHA3_256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); + assert_eq!(HMAC_SHA3_384::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); + assert_eq!(HMAC_SHA3_512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); } #[test] diff --git a/crypto/sm3/src/hmac.rs b/crypto/sm3/src/hmac.rs index 3e47b3dc..2e4eb65f 100644 --- a/crypto/sm3/src/hmac.rs +++ b/crypto/sm3/src/hmac.rs @@ -43,7 +43,8 @@ use crate::{SM3, SUSPENDED_SM3_STATE_LEN}; use bouncycastle_core::key_material::KeyMaterial; -use bouncycastle_core::traits::HashAlgParams; +use bouncycastle_core::security_strength::SecurityStrength; +use bouncycastle_core::traits::{Algorithm, AlgorithmOID, HashAlgParams}; use bouncycastle_hmac::{HMAC, HMACParams}; /*** Imports needed for docs ***/ @@ -59,19 +60,38 @@ use bouncycastle_hmac::MIN_FIPS_DIGEST_LEN; /// Algorithm name string for HMAC-SM3, as used by the factories and CLI. pub const HMAC_SM3_NAME: &str = "HMAC-SM3"; -/*** Type aliases ***/ -/// Public type for HMAC using SM3. +/*** Params types and type aliases ***/ +/// The parameters for HMAC-SM3 -- see [`HMAC_SM3`]. +#[derive(Clone)] #[allow(non_camel_case_types)] -pub type HMAC_SM3 = HMAC::BLOCK_LEN }>; -impl HMACParams for SM3 { - type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; - const HMAC_ALG_NAME: &'static str = HMAC_SM3_NAME; - /// Assigned by the Chinese OSCCA (GM/T 0006): hmac-sm3 { sm3 2 } = 1.2.156.10197.1.401.2 - const HMAC_OID: &'static [u32] = &[1, 2, 156, 10197, 1, 401, 2]; - const HMAC_OID_DER: &'static [u8] = +pub struct HMAC_SM3Params; + +impl Algorithm for HMAC_SM3Params { + const ALG_NAME: &'static str = HMAC_SM3_NAME; + // The strength this HMAC claims. Deliberately stated here rather than read off + // SM3: HMAC does not rest on the hash's collision resistance, so in principle the + // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by + // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value + // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; +} + +/// Assigned by the Chinese OSCCA (GM/T 0006): hmac-sm3 { sm3 2 } = 1.2.156.10197.1.401.2 +impl AlgorithmOID for HMAC_SM3Params { + const OID: &'static [u32] = &[1, 2, 156, 10197, 1, 401, 2]; + const OID_DER: &'static [u8] = &[0x06, 0x09, 0x2A, 0x81, 0x1C, 0xCF, 0x55, 0x01, 0x83, 0x11, 0x02]; } +impl HMACParams for HMAC_SM3Params { + type MACKey = KeyMaterial<{ ::OUTPUT_LEN }>; + type KeyBuf = [u8; ::BLOCK_LEN]; +} + +/// Public type for HMAC using SM3. +#[allow(non_camel_case_types)] +pub type HMAC_SM3 = HMAC; + /*** Serialized-state length constants ***/ // HMAC's suspended state is exactly the inner hasher's state -- the key is deliberately excluded and // must be re-supplied on resume -- so this is SM3's own state length. From eb5a04d0275aa4eabf2bf1c80aaa4e0b40e296e9 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sat, 12 Sep 2026 15:48:18 -0500 Subject: [PATCH 5/6] Adjusting HMAC::MAX_SECURITY_STRENGTH to higher values as allowed by SP 800-107r1 --- crypto/factory/tests/mac_factory_tests.rs | 28 ++--- crypto/hmac/src/lib.rs | 41 +++++-- crypto/sha2/src/hmac.rs | 141 +++++++++------------- crypto/sha2/tests/hmac_tests.rs | 80 +++++++----- crypto/sha3/src/hmac.rs | 85 +++++++------ crypto/sha3/tests/hmac_tests.rs | 21 ++-- crypto/sm3/src/hmac.rs | 10 +- 7 files changed, 212 insertions(+), 194 deletions(-) diff --git a/crypto/factory/tests/mac_factory_tests.rs b/crypto/factory/tests/mac_factory_tests.rs index a7121465..fee9fd88 100644 --- a/crypto/factory/tests/mac_factory_tests.rs +++ b/crypto/factory/tests/mac_factory_tests.rs @@ -10,16 +10,19 @@ mod hash_factory_tests { #[test] fn sha2_hash_tests() { - // HMAC-SHA224 - let key = KeyMaterial::<32>::from_bytes_as_type( - &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), + // HMAC-SHA224, RFC 4231 Test Case 6. MACFactory has no weak-key constructor, so this + // needs a vector whose key reaches the strength HMAC-SHA224 claims; Test Case 1's + // 20-byte key does not. + let key = KeyMaterial::<131>::from_bytes_as_type( + &hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + .unwrap(), KeyType::MACKey, ) .unwrap(); let hmac = MACFactory::new("HMAC-SHA224", &key).unwrap(); assert!(hmac.verify( - b"Hi There", - &hex::decode("896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22").unwrap(), + b"Test Using Larger Than Block-Size Key - Hash Key First", + &hex::decode("95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e").unwrap(), )); // HMAC-SHA512/224 -- NIST ACVP HMAC-SHA2-512/224 2.0, tgId 1, tcId 106 (MAC truncated to 160 bits) @@ -145,21 +148,18 @@ mod hash_factory_tests { #[test] fn hmac_sm3_tests() { - // RFC4231 Test Case 1 key/message; expected value from `openssl dgst -sm3 -mac HMAC`, - // confirmed with bc-java's HMac(new SM3Digest()). - let key = KeyMaterial::<32>::from_bytes_as_type( - &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), - KeyType::MACKey, - ) - .unwrap(); + // RFC4231 Test Case 6 key/message, for the same reason as HMAC-SHA224 above; expected + // value from `openssl dgst -sm3 -mac HMAC`, confirmed with bc-java's HMac(new SM3Digest()). + let key = + KeyMaterial::<131>::from_bytes_as_type(&[0xaa; 131], KeyType::MACKey).unwrap(); for name in ["HMAC-SM3", bouncycastle_sm3::hmac::HMAC_SM3_NAME] { let hmac = MACFactory::new(name, &key).unwrap(); assert_eq!(hmac.output_len(), 32); assert!( hmac.verify( - b"Hi There", + b"Test Using Larger Than Block-Size Key - Hash Key First", &hex::decode( - "51b00d1fb49832bfb01c3ce27848e59f871d9ba938dc563b338ca964755cce70" + "b4fd844e13342002f0b2e0690ea7741f1497d993a70494cea601e657bedf67a0" ) .unwrap(), ) diff --git a/crypto/hmac/src/lib.rs b/crypto/hmac/src/lib.rs index 89b959c6..5c1229d9 100644 --- a/crypto/hmac/src/lib.rs +++ b/crypto/hmac/src/lib.rs @@ -85,16 +85,21 @@ //! These apply to every instantiation; the hash crates' `hmac` modules repeat the ones that matter //! most in day-to-day use. //! -//! * The strength an HMAC claims is declared by its params, and is enforced in two places: -//! [`MAC::new`] checks it against the key's tagged strength, and [`HMAC::keygen_from_rng`] -//! checks it against the RNG's. It is not the underlying hash's strength: HMAC does not rest on -//! collision resistance, so the figures are independent. NIST SP 800-107-r1 Section 5.3.4 gives -//! the ceiling: the effective strength is `min(strength of K, 2C)` for an internal chaining value -//! of `C` bits. Declaring a strength the construction cannot support does not make it stronger, -//! it just makes both checks wrong. -//! * [`MAC::new_allow_weak_key`] deliberately skips the key-strength check. It exists for protocols -//! that call for a weak or all-zero key -- an all-zero HKDF salt, for example -- and should not be -//! used to silence an error from [`MAC::new`]. +//! * The strength an HMAC claims is declared by its params, and is the most the instantiation can +//! deliver. It is not the underlying hash's collision strength: NIST SP 800-107-r1 Section 5.3.4 +//! puts it at `min(strength of K, 2C)` for a `C`-bit chaining value, and footnote 4 there rules +//! collision attacks out of scope entirely. Declaring a strength the construction cannot support +//! does not make it stronger, it just makes the two gates below wrong. +//! * Both gates follow from that claim, and both ask the same question: can this input actually +//! back the strength the algorithm advertises? [`MAC::new`] checks the key's tagged strength and +//! [`HMAC::keygen_from_rng`] checks the RNG's, each refusing anything weaker. So HMAC-SHA256 +//! wants a 256-bit key and a 256-bit generator. Truncating the *output* still lowers the strength +//! of a given tag, which is why this is a ceiling rather than a guarantee. +//! * The escape hatch is on the key only. [`MAC::new_allow_weak_key`] skips the key check for +//! protocols that require a weak or all-zero key, such as an all-zero HKDF salt. +//! [`HMAC::keygen_from_rng`] has no equivalent, because a generator cannot be asked to produce +//! entropy it does not have; the key it returned would carry a tag that overstates it. +//! It should not be used merely to silence an error from [`MAC::new`]. //! * Verification via [`MAC::verify`] / [`MAC::do_verify_final`] uses a constant-time comparison. //! Recomputing the MAC and comparing it with `==` leaks how many leading bytes matched. //! * [`MIN_FIPS_DIGEST_LEN`] (4 bytes) is the shortest truncation this crate will produce, per @@ -435,6 +440,22 @@ impl HMAC { /// The key length is the underlying hash's output length ([`HashAlgParams::OUTPUT_LEN`], carried /// as [`HMACParams::MACKey`]); see that associated type for why. /// + /// # Contract + /// + /// The returned key is tagged at this HMAC's [`Algorithm::MAX_SECURITY_STRENGTH`], and that tag + /// has to be backed by real entropy -- A generator cannot produce + /// more entropy than it has -- so this refuses any RNG that cannot supply it: if + /// [`RNG::security_strength`] is below the claimed strength you get + /// [`RNGError::SecurityStrengthInsufficientForAlgorithm`] and no key. + /// + /// In practice this means a 256-bit generator for HMAC-SHA256 and above. `DefaultRNG` is + /// `HashDRBG_SHA512` and qualifies for every instantiation in this library; `HashDRBG_SHA256` + /// offers 128 bits and so is accepted only where the HMAC claims no more than that. + /// + /// There is deliberately no weak-RNG escape hatch, unlike [`MAC::new_allow_weak_key`]. A + /// caller who genuinely wants a key weaker than the algorithm claims can build one themselves + /// and pass it to [`MAC::new_allow_weak_key`], which makes that choice visible at the call site. + /// // Dev note: done this way to avoid this crate needing a dependency on the `bouncycastle-rng` crate, // which itself has a dependency on `bouncycastle-sha2` which depends on this hmac crate, // which creates a circular cargo dependency. diff --git a/crypto/sha2/src/hmac.rs b/crypto/sha2/src/hmac.rs index b9ab5e1b..1077cc35 100644 --- a/crypto/sha2/src/hmac.rs +++ b/crypto/sha2/src/hmac.rs @@ -44,10 +44,10 @@ //! ``` //! use bouncycastle_core::key_material::KeyMaterial256; //! use bouncycastle_core::traits::MAC; -//! use bouncycastle_rng::HashDRBG_SHA256; +//! use bouncycastle_rng::DefaultRNG; //! use bouncycastle_sha2::hmac::HMAC_SHA256; //! -//! let mut rng = HashDRBG_SHA256::new_from_os(); +//! let mut rng = DefaultRNG::new_from_os(); //! let key: KeyMaterial256 = HMAC_SHA256::keygen_from_rng(&mut rng) //! .expect("Will only fail if the system RNG can't start up."); //! @@ -64,7 +64,8 @@ //! use bouncycastle_sha2::hmac::HMAC_SHA256; //! //! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\ +//! \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", //! KeyType::MACKey).unwrap(); //! //! let hmac = HMAC_SHA256::new(&key).expect( @@ -80,10 +81,10 @@ //! ``` //! use bouncycastle_core::key_material::KeyMaterial256; //! use bouncycastle_core::traits::MAC; -//! use bouncycastle_rng::HashDRBG_SHA256; +//! use bouncycastle_rng::DefaultRNG; //! use bouncycastle_sha2::hmac::HMAC_SHA256; //! -//! let mut rng = HashDRBG_SHA256::new_from_os(); +//! let mut rng = DefaultRNG::new_from_os(); //! let key: KeyMaterial256 = HMAC_SHA256::keygen_from_rng(&mut rng) //! .expect("Will only fail if the system RNG can't start up."); //! @@ -98,10 +99,10 @@ //! ``` //! use bouncycastle_core::key_material::KeyMaterial256; //! use bouncycastle_core::traits::MAC; -//! use bouncycastle_rng::HashDRBG_SHA256; +//! use bouncycastle_rng::DefaultRNG; //! use bouncycastle_sha2::hmac::HMAC_SHA256; //! -//! let mut rng = HashDRBG_SHA256::new_from_os(); +//! let mut rng = DefaultRNG::new_from_os(); //! let key: KeyMaterial256 = HMAC_SHA256::keygen_from_rng(&mut rng) //! .expect("Will only fail if the system RNG can't start up."); //! @@ -127,7 +128,8 @@ //! // For this example to work, we are hard-coding both the key and the MAC value that it generates //! // for this data. //! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\ +//! \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", //! KeyType::MACKey).unwrap(); //! //! let data: &[u8] = b"Hello, world!"; @@ -135,8 +137,8 @@ //! // .verify() returns a bool: true if the MAC is valid, false otherwise. //! if HMAC_SHA256::new(&key).unwrap() //! .verify(data, -//! b"\xa2\xd1\x2e\xcf\xfc\x41\xba\xf1\x23\xd6\x3e\x44\xfc\x27\x88\x90 -//! \x47\xcd\x08\xe7\x05\xd7\x0f\xa3\xb8\xaa\x8a\x5c\x18\x7c\x6c\xa9" +//! b"\x76\xd0\x69\x2c\x75\x6f\x89\x94\x96\xf3\x51\x63\x6a\x69\x69\xe5 +//! \x4e\xbf\xb2\x3a\xbb\x09\xfd\x61\x40\x86\x13\x6a\xc9\xab\x26\x77" //! ) //! { //! println!("MAC is valid!"); @@ -156,12 +158,13 @@ //! // For this example to work, we are hard-coding both the key and the MAC value that it generates //! // for this data. //! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\ +//! \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", //! KeyType::MACKey).unwrap(); //! let mut hmac = HMAC_SHA256::new(&key).unwrap(); //! hmac.do_update(b"Hello,"); //! hmac.do_update(b" world!"); -//! if hmac.do_verify_final(b"\xa2\xd1\x2e\xcf\xfc\x41\xba\xf1\x23\xd6\x3e\x44\xfc\x27\x88\x90\x47\xcd\x08\xe7\x05\xd7\x0f\xa3\xb8\xaa\x8a\x5c\x18\x7c\x6c\xa9" +//! if hmac.do_verify_final(b"\x76\xd0\x69\x2c\x75\x6f\x89\x94\x96\xf3\x51\x63\x6a\x69\x69\xe5\x4e\xbf\xb2\x3a\xbb\x09\xfd\x61\x40\x86\x13\x6a\xc9\xab\x26\x77" //! ) //! { //! println!("MAC is valid!"); @@ -192,7 +195,8 @@ //! let msg_part2 = b" jumped over the lazy dog"; //! //! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\ +//! \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", //! KeyType::MACKey).unwrap(); //! //! let mut hmac = HMAC_SHA256::new(&key).unwrap(); @@ -232,10 +236,26 @@ //! //! # Security Considerations //! -//! * The key must carry at least the security strength claimed by the HMAC, and [`MAC::new`] -//! enforces that. [`MAC::new_allow_weak_key`] deliberately skips the check; use it only where a -//! weak or all-zero key is called for by the protocol (an all-zero HKDF salt, for example), not to -//! silence an error. +//! * Each of these HMACs claims the strength NIST SP 800-107r1 Section 5.3.4 gives it, which is +//! `min(strength of K, 2C)` and works out to the key length for the whole SHA-2 family: 224 bits +//! for HMAC-SHA224 and HMAC-SHA512/224 and 256 or more for the rest. `SecurityStrength` has no +//! 224-bit category and tops out at 256, so the declared values are `_192bit` for HMAC-SHA224 and +//! HMAC-SHA512/224 and `_256bit` for HMAC-SHA256, HMAC-SHA384, HMAC-SHA512 and HMAC-SHA512/256. +//! Note these are *not* the underlying hashes' +//! collision strengths, which are half as large; footnote 4 of that section puts collision +//! attacks out of scope for HMAC. +//! * The key must carry at least the strength claimed by the HMAC, and [`MAC::new`] enforces that. +//! A 20-byte key is therefore no longer enough for HMAC-SHA256; a full 32-byte key is. +//! [`MAC::new_allow_weak_key`] deliberately skips the check; use it only where a weak or all-zero +//! key is called for by the protocol (an all-zero HKDF salt, or a fixed test vector such as +//! RFC 4231's 20-byte keys), not to silence an error. +//! * The same rule applies to the generator. [`HMAC::keygen_from_rng`] tags the key it returns at +//! the HMAC's claimed strength, so it refuses any RNG that cannot back that tag: a 256-bit +//! generator is required for HMAC-SHA256 and above, and `bouncycastle_rng::DefaultRNG` is +//! `HashDRBG_SHA512` and qualifies for all of them. `HashDRBG_SHA256` offers 128 bits and is now +//! refused by every HMAC in this module. There is no weak-RNG opt-out, because a generator cannot +//! be asked for entropy it does not have; build the key yourself and use +//! [`MAC::new_allow_weak_key`] if that is genuinely what you want. //! * Verify with [`MAC::verify`] or [`MAC::do_verify_final`] rather than computing the MAC yourself //! and comparing: those use a constant-time comparison, while `==` on the byte slices leaks how //! many leading bytes matched. @@ -277,41 +297,6 @@ pub const HMAC_SHA512_224_NAME: &str = "HMAC-SHA512/224"; pub const HMAC_SHA512_256_NAME: &str = "HMAC-SHA512/256"; /*** Params types and type aliases ***/ -// TODO: revise the MAX_SECURITY_STRENGTH that each params type below declares. -// -// They currently declare the underlying hash's collision strength (output length / 2): 112 bits for -// HMAC-SHA224, 128 for HMAC-SHA256, and so on. That figure is a leftover from when the strength was -// read off the hash, and NIST SP 800-107 Revision 1 (August 2012) Section 5.3.4 says collision -// resistance is the wrong basis for an HMAC: -// -// "The effective security strength of the HMAC key is the minimum of the security strength of -// K and the value of 2C. That is, security strength = min(security strength of K, 2C)." -// -// where C is "the bit length of the internal hash value that is denoted H in FIPS 180-4" -- the -// chaining value, not the output. Footnote 4 of that section is explicit that collisions are out of -// scope: "the collision attack is not considered in this document. In this Recommendation, the -// strength of the HMAC key is considered to be the amount of work required for an attacker who -// performs a brute-force attack to discover the HMAC key K or the first hash values (Hs) ...". -// -// Recommendation: raise them. Section 5.3.4 gives C = 256 for SHA-256 ("L = C for SHA-1, SHA-256, -// and SHA-512") and C = 512 for SHA-384 and SHA-512/t, so 2C is 512 and 1024 bits respectively -- -// far above any key this library accepts. It does not state C for SHA-224, but C >= L = 224 there, -// so 2C >= 448 and the conclusion is unchanged: the key's own strength is the binding term in every -// SHA-2 case. Since `MACKey` is `KeyMaterial`, a full-length key carries 224, 256, 384 -// and 512 bits for HMAC-SHA224/256/384/512, every one of which exceeds what is declared today, and -// all but the first exceed the top of `SecurityStrength`, so they would cap at `_256bit` (and -// HMAC-SHA224 at `_192bit`). -// -// Do NOT extrapolate this to the HMAC-SHA3 values in bouncycastle-sha3. SP 800-107r1 predates SHA-3 -// and Section 5.3.4 is written in terms of the FIPS 180-4 chaining value, which a sponge -// construction does not have. Those need their own reference (FIPS 202 / SP 800-185) read first. -// -// This is not a mechanical edit. MAX_SECURITY_STRENGTH is enforced, not merely reported, so raising -// it makes `MAC::new` reject keys it accepts today and `keygen_from_rng` reject RNGs it accepts -// today. Raising HMAC-SHA256 to `_256bit`, for example, would make HashDRBG_SHA256 insufficient to -// generate its own key and would invert the existing `keygen_rejects_weak_rng` test. Settle the -// policy question first: does this constant describe the ceiling the construction can reach, or the -// minimum a caller is required to bring to it? Those give different numbers. /// The parameters for HMAC-SHA224 -- see [`HMAC_SHA224`]. #[derive(Clone)] @@ -320,12 +305,9 @@ pub struct HMAC_SHA224Params; impl Algorithm for HMAC_SHA224Params { const ALG_NAME: &'static str = HMAC_SHA224_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA224: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; + // SP 800-107r1 s.5.3.4: min(strength of K, 2C). SHA-224 has C = 256, so 2C = 512, + // and the key is OUTPUT_LEN = 224 bits, so the key binds: 224 bits, rounded down to the nearest category. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } /// Defined in RFC 4231: id-hmacWithSHA224 { digestAlgorithm 8 } @@ -350,12 +332,9 @@ pub struct HMAC_SHA256Params; impl Algorithm for HMAC_SHA256Params { const ALG_NAME: &'static str = HMAC_SHA256_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA256: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; + // SP 800-107r1 s.5.3.4: min(strength of K, 2C). SHA-256 has C = 256, so 2C = 512, + // and the key is OUTPUT_LEN = 256 bits, so the key binds: 256 bits. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } /// Defined in RFC 4231: id-hmacWithSHA256 { digestAlgorithm 9 } @@ -380,12 +359,9 @@ pub struct HMAC_SHA384Params; impl Algorithm for HMAC_SHA384Params { const ALG_NAME: &'static str = HMAC_SHA384_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA384: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; + // SP 800-107r1 s.5.3.4: min(strength of K, 2C). SHA-384 has C = 512, so 2C = 1024, + // and the key is OUTPUT_LEN = 384 bits, so the key binds: 384 bits, capped at the top of `SecurityStrength`. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } /// Defined in RFC 4231: id-hmacWithSHA384 { digestAlgorithm 10 } @@ -410,11 +386,8 @@ pub struct HMAC_SHA512Params; impl Algorithm for HMAC_SHA512Params { const ALG_NAME: &'static str = HMAC_SHA512_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA512: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + // SP 800-107r1 s.5.3.4: min(strength of K, 2C). SHA-512 has C = 512, so 2C = 1024, + // and the key is OUTPUT_LEN = 512 bits, so the key binds: 512 bits, capped at the top of `SecurityStrength`. const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } @@ -440,12 +413,9 @@ pub struct HMAC_SHA512_224Params; impl Algorithm for HMAC_SHA512_224Params { const ALG_NAME: &'static str = HMAC_SHA512_224_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA512_224: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; + // SP 800-107r1 s.5.3.4: min(strength of K, 2C). SHA-512/224 has C = 512, so 2C = 1024, + // and the key is OUTPUT_LEN = 224 bits, so the key binds: 224 bits, rounded down to the nearest category. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } /// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-224 { digestAlgorithm 12 } @@ -470,12 +440,9 @@ pub struct HMAC_SHA512_256Params; impl Algorithm for HMAC_SHA512_256Params { const ALG_NAME: &'static str = HMAC_SHA512_256_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA512_256: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; + // SP 800-107r1 s.5.3.4: min(strength of K, 2C). SHA-512/256 has C = 512, so 2C = 1024, + // and the key is OUTPUT_LEN = 256 bits, so the key binds: 256 bits. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } /// Defined in RFC 8018 Appendix B.1.2: id-hmacWithSHA512-256 { digestAlgorithm 13 } diff --git a/crypto/sha2/tests/hmac_tests.rs b/crypto/sha2/tests/hmac_tests.rs index e25d62fe..22916feb 100644 --- a/crypto/sha2/tests/hmac_tests.rs +++ b/crypto/sha2/tests/hmac_tests.rs @@ -37,7 +37,7 @@ mod hmac_sha2_tests { KeyType::MACKey, ) .unwrap(); - let mut mac = HMAC_SHA224::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new_allow_weak_key(&key).unwrap(); mac.do_update(b"Hi There"); let output = mac.do_final(); assert_eq!(output, b"\x89\x6f\xb1\x12\x8a\xbb\xdf\x19\x68\x32\x10\x7c\xd4\x9d\xf3\x3f\x47\xb4\xb1\x16\x99\x12\xba\x4f\x53\x68\x4b\x22"); @@ -48,7 +48,7 @@ mod hmac_sha2_tests { KeyType::MACKey, ) .unwrap(); - let mac = HMAC_SHA256::new(&key).unwrap(); + let mac = HMAC_SHA256::new_allow_weak_key(&key).unwrap(); // mac.do_update(b"").unwrap(); let output = mac.do_final(); assert_eq!( @@ -77,12 +77,15 @@ mod hmac_sha2_tests { // It works after allowing weak keys HMAC_SHA256::new_allow_weak_key(&short_key).unwrap(); - // It works with a long enough key + // It works with a long enough key. HMAC-SHA256 claims 256 bits (SP 800-107r1 s.5.3.4), so + // "long enough" is a full 32-byte key. let key = KeyMaterial256::from_bytes_as_type( - &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(), + &hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") + .unwrap(), KeyType::MACKey, ) .unwrap(); + assert_eq!(key.security_strength(), SecurityStrength::_256bit); HMAC_SHA256::new(&key).unwrap(); } @@ -183,23 +186,25 @@ mod hmac_sha2_tests { .unwrap(); // get the known-good output - let out = HMAC_SHA224::new(&key).unwrap().mac(b"Hi There"); + let out = HMAC_SHA224::new_allow_weak_key(&key).unwrap().mac(b"Hi There"); // test output that's the wrong length, should simply return False - let mut mac = HMAC_SHA224::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new_allow_weak_key(&key).unwrap(); mac.do_update(b"Hi There"); assert!(!mac.do_verify_final(&out[..out.len() - 1])); // test output that's the right length but wrong value -- do_verify - let mut mac = HMAC_SHA224::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new_allow_weak_key(&key).unwrap(); mac.do_update(b"Hi There"); assert!(!mac.do_verify_final(&[0x01_u8; 28])); // test output that's the right length but wrong value -- static verify - assert!(!HMAC_SHA224::new(&key).unwrap().verify(b"Hi There", &[0x01_u8; 28])); + assert!( + !HMAC_SHA224::new_allow_weak_key(&key).unwrap().verify(b"Hi There", &[0x01_u8; 28]) + ); // error case: test that it'll refuse to truncate below MIN_FIPS_DIGEST_LEN - let mut mac = HMAC_SHA224::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new_allow_weak_key(&key).unwrap(); mac.do_update(b"Hi There"); let mut out = vec![0u8; MIN_FIPS_DIGEST_LEN - 1]; match mac.do_final_out(&mut out) { @@ -213,7 +218,7 @@ mod hmac_sha2_tests { } // success case: ... but it will truncate to exactly MIN_FIPS_DIGEST_LEN - let mut mac = HMAC_SHA224::new(&key).unwrap(); + let mut mac = HMAC_SHA224::new_allow_weak_key(&key).unwrap(); mac.do_update(b"Hi There"); let mut out = vec![0u8; MIN_FIPS_DIGEST_LEN]; let bytes_written = mac.do_final_out(&mut out).unwrap(); @@ -232,18 +237,21 @@ mod hmac_sha2_tests { assert_eq!(HMAC_SHA512_256::ALG_NAME, HMAC_SHA512_256_NAME); assert_eq!(HMAC_SHA512_224_NAME, "HMAC-SHA512/224"); assert_eq!(HMAC_SHA512_256_NAME, "HMAC-SHA512/256"); - assert_eq!(HMAC_SHA512_224::MAX_SECURITY_STRENGTH, SecurityStrength::_112bit); - assert_eq!(HMAC_SHA512_256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); assert_eq!(HMAC_SHA224::OID, [1, 2, 840, 113549, 2, 8]); assert_eq!(HMAC_SHA256::OID, [1, 2, 840, 113549, 2, 9]); assert_eq!(HMAC_SHA384::OID, [1, 2, 840, 113549, 2, 10]); assert_eq!(HMAC_SHA512::OID, [1, 2, 840, 113549, 2, 11]); - assert_eq!(HMAC_SHA224::MAX_SECURITY_STRENGTH, SecurityStrength::_112bit); - assert_eq!(HMAC_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); - assert_eq!(HMAC_SHA384::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); - assert_eq!(HMAC_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + // Per SP 800-107r1 s.5.3.4 these are min(strength of K, 2C), which for every SHA-2 HMAC + // resolves to the key: OUTPUT_LEN bits, rounded down to a representable category. + assert_eq!(HMAC_SHA224::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); // 224 bits + assert_eq!(HMAC_SHA256::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // 256 bits + assert_eq!(HMAC_SHA384::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // 384, capped + assert_eq!(HMAC_SHA512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // 512, capped + assert_eq!(HMAC_SHA512_224::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); // 224 bits + assert_eq!(HMAC_SHA512_256::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // 256 bits + assert_eq!(HMAC_SHA512_224::OID, [1, 2, 840, 113549, 2, 12]); assert_eq!(HMAC_SHA512_256::OID, [1, 2, 840, 113549, 2, 13]); } @@ -404,7 +412,10 @@ mod hmac_sha2_tests { ) .unwrap(); let mut out = [0u8; 128 / 8]; - HMAC_SHA224::new(&key).unwrap().mac_out(b"Test With Truncation", &mut out).unwrap(); + HMAC_SHA224::new_allow_weak_key(&key) + .unwrap() + .mac_out(b"Test With Truncation", &mut out) + .unwrap(); assert_eq!(&Vec::from(out), &hex::decode("0e2aea68a90c8d37c988bcdb9fca6fa8").unwrap()); // RFC4231 Test Case 6 -- Test with a combined length of key and data that is larger than 64 @@ -485,7 +496,10 @@ mod hmac_sha2_tests { ) .unwrap(); let mut out = [0u8; 128 / 8]; - HMAC_SHA256::new(&key).unwrap().mac_out(b"Test With Truncation", &mut out).unwrap(); + HMAC_SHA256::new_allow_weak_key(&key) + .unwrap() + .mac_out(b"Test With Truncation", &mut out) + .unwrap(); assert_eq!(&Vec::from(out), &hex::decode("a3b6167473100ee06e0c796c2955552b").unwrap()); // RFC4231 Test Case 6 -- Test with a combined length of key and data that is larger than 64 @@ -731,7 +745,7 @@ mod hmac_sha2_tests { KeyType::MACKey, ) .unwrap(); - let hmac = HMAC_SHA256::new(&key).unwrap(); + let hmac = HMAC_SHA256::new_allow_weak_key(&key).unwrap(); // test fmt let fmt_str = format!("{}", &hmac); @@ -776,20 +790,26 @@ mod hmac_sha2_tests { /// `keygen_from_rng` must refuse an RNG whose security strength is below the strength the HMAC /// claims, otherwise the returned key would be tagged stronger than the entropy behind it. - /// HashDRBG_SHA256 offers 128 bits, which is enough for HMAC-SHA256 but not for HMAC-SHA512. + /// HashDRBG_SHA256 offers 128 bits. Since SP 800-107r1 s.5.3.4 puts every SHA-2 HMAC at 192 + /// bits or more, it is now too weak for all of them, and a 256-bit generator is required. + /// `DefaultRNG` resolves to HashDRBG_SHA512 and so qualifies out of the box. #[test] fn keygen_rejects_weak_rng() { - let mut weak_rng = HashDRBG_SHA256::new_from_os(); - assert!( - matches!( - HMAC_SHA512::keygen_from_rng(&mut weak_rng), - Err(RNGError::SecurityStrengthInsufficientForAlgorithm) - ), - "a 128-bit RNG must not be accepted for a 256-bit HMAC" - ); + for refused in [ + HMAC_SHA224::keygen_from_rng(&mut HashDRBG_SHA256::new_from_os()).err(), + HMAC_SHA256::keygen_from_rng(&mut HashDRBG_SHA256::new_from_os()).err(), + HMAC_SHA384::keygen_from_rng(&mut HashDRBG_SHA256::new_from_os()).err(), + HMAC_SHA512::keygen_from_rng(&mut HashDRBG_SHA256::new_from_os()).err(), + ] { + assert!( + matches!(refused, Some(RNGError::SecurityStrengthInsufficientForAlgorithm)), + "a 128-bit RNG must not be accepted for an HMAC claiming 192 bits or more" + ); + } - let mut ok_rng = HashDRBG_SHA256::new_from_os(); + // A 256-bit generator backs every SHA-2 HMAC in the crate. + let mut ok_rng = HashDRBG_SHA512::new_from_os(); HMAC_SHA256::keygen_from_rng(&mut ok_rng) - .expect("a 128-bit RNG is sufficient for a 128-bit HMAC"); + .expect("a 256-bit RNG is sufficient for a 256-bit HMAC"); } } diff --git a/crypto/sha3/src/hmac.rs b/crypto/sha3/src/hmac.rs index 1a994515..1020c372 100644 --- a/crypto/sha3/src/hmac.rs +++ b/crypto/sha3/src/hmac.rs @@ -68,7 +68,8 @@ //! use bouncycastle_sha3::hmac::HMAC_SHA3_256; //! //! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\ +//! \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", //! KeyType::MACKey).unwrap(); //! //! let hmac = HMAC_SHA3_256::new(&key).expect( @@ -131,7 +132,8 @@ //! // For this example to work, we are hard-coding both the key and the MAC value that it generates //! // for this data. //! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\ +//! \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", //! KeyType::MACKey).unwrap(); //! //! let data: &[u8] = b"Hello, world!"; @@ -139,8 +141,8 @@ //! // .verify() returns a bool: true if the MAC is valid, false otherwise. //! if HMAC_SHA3_256::new(&key).unwrap() //! .verify(data, -//! b"\x9c\x49\x05\x83\xff\xf3\x59\x6a\x59\x01\x4a\x0d\x95\xb4\x64\x00 -//! \x7d\x5b\xb7\x40\xb3\x84\x20\x7a\x3c\x76\x76\xd8\xc9\x93\xda\xd7" +//! b"\x5d\x16\xf1\xc4\xcc\x22\x83\x8a\xd0\x53\xe6\xb6\x9b\xb2\xd1\x5a +//! \x2a\x79\x35\x76\xb0\x80\x7d\xec\x50\x78\xa1\x36\x99\x33\x7d\xfd" //! ) //! { //! println!("MAC is valid!"); @@ -160,12 +162,13 @@ //! // For this example to work, we are hard-coding both the key and the MAC value that it generates //! // for this data. //! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\ +//! \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", //! KeyType::MACKey).unwrap(); //! let mut hmac = HMAC_SHA3_256::new(&key).unwrap(); //! hmac.do_update(b"Hello,"); //! hmac.do_update(b" world!"); -//! if hmac.do_verify_final(b"\x9c\x49\x05\x83\xff\xf3\x59\x6a\x59\x01\x4a\x0d\x95\xb4\x64\x00\x7d\x5b\xb7\x40\xb3\x84\x20\x7a\x3c\x76\x76\xd8\xc9\x93\xda\xd7" +//! if hmac.do_verify_final(b"\x5d\x16\xf1\xc4\xcc\x22\x83\x8a\xd0\x53\xe6\xb6\x9b\xb2\xd1\x5a\x2a\x79\x35\x76\xb0\x80\x7d\xec\x50\x78\xa1\x36\x99\x33\x7d\xfd" //! ) //! { //! println!("MAC is valid!"); @@ -196,7 +199,8 @@ //! let msg_part2 = b" jumped over the lazy dog"; //! //! let key = KeyMaterial256::from_bytes_as_type( -//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", +//! b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\ +//! \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", //! KeyType::MACKey).unwrap(); //! //! let mut hmac = HMAC_SHA3_256::new(&key).unwrap(); @@ -237,9 +241,27 @@ //! //! # Security Considerations //! -//! * The key must carry at least the security strength claimed by the HMAC, and [`MAC::new`] -//! enforces that. [`MAC::new_allow_weak_key`] deliberately skips the check; use it only where a -//! weak or all-zero key is called for by the protocol, not to silence an error. +//! * Each of these HMACs claims the strength NIST SP 800-107r1 Section 5.3.4 gives it, which is +//! `min(strength of K, 2C)` and works out to the key length for the whole family: 224 bits for +//! HMAC-SHA3-224 and 256 or more for the rest. `SecurityStrength` has no 224-bit category and +//! tops out at 256, so the declared values are `_192bit` for HMAC-SHA3-224 and `_256bit` for +//! HMAC-SHA3-256, HMAC-SHA3-384 and HMAC-SHA3-512. Note these are *not* the underlying hashes' +//! collision strengths, which are half as large; footnote 4 of that section puts collision +//! attacks out of scope for HMAC. +//! * That figure is an **extrapolation**. SP 800-107r1 is older than SHA-3 and does not cover it: +//! `C` there is the FIPS 180-4 chaining value, which a sponge does not have. See the note above +//! the params types for the analogue used and why the choice does not change the answer. +//! * The key must carry at least the strength claimed by the HMAC, and [`MAC::new`] enforces that. +//! A 20-byte key is therefore not enough for HMAC-SHA3-256; a full 32-byte key is. +//! [`MAC::new_allow_weak_key`] deliberately skips the check; use it only where a weak or all-zero +//! key is called for by the protocol, or by a fixed test vector, not to silence an error. +//! * The same rule applies to the generator. [`HMAC::keygen_from_rng`] tags the key it returns at +//! the HMAC's claimed strength, so it refuses any RNG that cannot back that tag: a 256-bit +//! generator is required for HMAC-SHA3-256 and above, and `bouncycastle_rng::DefaultRNG` is +//! `HashDRBG_SHA512` and qualifies for all of them. `HashDRBG_SHA256` offers 128 bits and is +//! refused by every HMAC in this module. There is no weak-RNG opt-out, because a generator cannot +//! be asked for entropy it does not have; build the key yourself and use +//! [`MAC::new_allow_weak_key`] if that is genuinely what you want. //! * Verify with [`MAC::verify`] or [`MAC::do_verify_final`] rather than computing the MAC yourself //! and comparing: those use a constant-time comparison, while `==` on the byte slices leaks how //! many leading bytes matched. @@ -280,11 +302,6 @@ pub const HMAC_SHA3_384_NAME: &str = "HMAC-SHA3-384"; pub const HMAC_SHA3_512_NAME: &str = "HMAC-SHA3-512"; /*** Params types and type aliases ***/ -// TODO: the MAX_SECURITY_STRENGTH values below are each hash's collision strength, which NIST -// SP 800-107r1 Section 5.3.4 says is not the right basis for an HMAC. See the TODO above the -// equivalent params types in bouncycastle-sha2 for the analysis. Note that its recommendation -// covers SHA-2 only: SP 800-107r1 predates SHA-3 and reasons about a Merkle-Damgard chaining -// value, which a sponge does not have, so these need FIPS 202 / SP 800-185 read first. /// The parameters for HMAC-SHA3_224 -- see [`HMAC_SHA3_224`]. #[derive(Clone)] @@ -293,12 +310,10 @@ pub struct HMAC_SHA3_224Params; impl Algorithm for HMAC_SHA3_224Params { const ALG_NAME: &'static str = HMAC_SHA3_224_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA3_224: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit; + // SP 800-107r1 s.5.3.4, extrapolated from SHA2 to SHA3: min(strength of K, 2C). + // SHA3-224 has capacity c = 448, so 2c = 896, and the key is OUTPUT_LEN = 224 bits, + // so the key binds: 224 bits, rounded down to the nearest category. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-224 { hashAlgs 13 } @@ -324,12 +339,10 @@ pub struct HMAC_SHA3_256Params; impl Algorithm for HMAC_SHA3_256Params { const ALG_NAME: &'static str = HMAC_SHA3_256_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA3_256: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; + // SP 800-107r1 s.5.3.4, extrapolated from SHA2 to SHA3: min(strength of K, 2C). + // SHA3-256 has capacity c = 512, so 2c = 1024, and the key is OUTPUT_LEN = 256 bits, + // so the key binds: 256 bits. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-256 { hashAlgs 14 } @@ -355,12 +368,10 @@ pub struct HMAC_SHA3_384Params; impl Algorithm for HMAC_SHA3_384Params { const ALG_NAME: &'static str = HMAC_SHA3_384_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA3_384: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; + // SP 800-107r1 s.5.3.4, extrapolated from SHA2 to SHA3: min(strength of K, 2C). + // SHA3-384 has capacity c = 768, so 2c = 1536, and the key is OUTPUT_LEN = 384 bits, + // so the key binds: 384 bits, capped at the top of `SecurityStrength`. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } /// Assigned by NIST in the Computer Security Objects Register: id-hmacWithSHA3-384 { hashAlgs 15 } @@ -386,11 +397,9 @@ pub struct HMAC_SHA3_512Params; impl Algorithm for HMAC_SHA3_512Params { const ALG_NAME: &'static str = HMAC_SHA3_512_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SHA3_512: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. + // SP 800-107r1 s.5.3.4, extrapolated from SHA2 to SHA3: min(strength of K, 2C). + // SHA3-512 has capacity c = 1024, so 2c = 2048, and the key is OUTPUT_LEN = 512 bits, + // so the key binds: 512 bits, capped at the top of `SecurityStrength`. const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } diff --git a/crypto/sha3/tests/hmac_tests.rs b/crypto/sha3/tests/hmac_tests.rs index 9ba2f110..d8522cce 100644 --- a/crypto/sha3/tests/hmac_tests.rs +++ b/crypto/sha3/tests/hmac_tests.rs @@ -29,18 +29,19 @@ mod hmac_sha3_tests { ) .unwrap(); - // fail case: mac value is correct but truncated - let mac = HMAC_SHA3_224::new(&key).unwrap(); + // fail case: mac value is correct but truncated. The 20-byte key is below the strength + // HMAC-SHA3-224 claims, so these opt in to a weak key. + let mac = HMAC_SHA3_224::new_allow_weak_key(&key).unwrap(); let mut mac_val = mac.mac(b"Polly want a cracker?"); - let verifier = HMAC_SHA3_224::new(&key).unwrap(); + let verifier = HMAC_SHA3_224::new_allow_weak_key(&key).unwrap(); assert!(verifier.verify(b"Polly want a cracker?", &mac_val)); // truncation of the mac value is considered a fail - let verifier = HMAC_SHA3_224::new(&key).unwrap(); + let verifier = HMAC_SHA3_224::new_allow_weak_key(&key).unwrap(); assert!(!verifier.verify(b"Polly want a cracker?", &mac_val[..mac_val.len() - 1])); // .. as is some extra bytes at the end - let verifier = HMAC_SHA3_224::new(&key).unwrap(); + let verifier = HMAC_SHA3_224::new_allow_weak_key(&key).unwrap(); mac_val.extend_from_slice(&[0u8; 4]); assert!(!verifier.verify(b"Polly want a cracker?", &mac_val)); } @@ -58,10 +59,12 @@ mod hmac_sha3_tests { assert_eq!(HMAC_SHA3_384::OID, [2, 16, 840, 1, 101, 3, 4, 2, 15]); assert_eq!(HMAC_SHA3_512::OID, [2, 16, 840, 1, 101, 3, 4, 2, 16]); - assert_eq!(HMAC_SHA3_224::MAX_SECURITY_STRENGTH, SecurityStrength::_112bit); - assert_eq!(HMAC_SHA3_256::MAX_SECURITY_STRENGTH, SecurityStrength::_128bit); - assert_eq!(HMAC_SHA3_384::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); - assert_eq!(HMAC_SHA3_512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); + // Per SP 800-107r1 s.5.3.4, extrapolated to SHA-3: min(strength of K, 2C) resolves to the + // key in every case, so these are OUTPUT_LEN rounded down to a representable category. + assert_eq!(HMAC_SHA3_224::MAX_SECURITY_STRENGTH, SecurityStrength::_192bit); // 224 bits + assert_eq!(HMAC_SHA3_256::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // 256 bits + assert_eq!(HMAC_SHA3_384::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // 384, capped + assert_eq!(HMAC_SHA3_512::MAX_SECURITY_STRENGTH, SecurityStrength::_256bit); // 512, capped } #[test] diff --git a/crypto/sm3/src/hmac.rs b/crypto/sm3/src/hmac.rs index 2e4eb65f..3928c7dc 100644 --- a/crypto/sm3/src/hmac.rs +++ b/crypto/sm3/src/hmac.rs @@ -68,12 +68,10 @@ pub struct HMAC_SM3Params; impl Algorithm for HMAC_SM3Params { const ALG_NAME: &'static str = HMAC_SM3_NAME; - // The strength this HMAC claims. Deliberately stated here rather than read off - // SM3: HMAC does not rest on the hash's collision resistance, so in principle the - // two can differ (NIST SP 800-107-r1 Section 5.3.4 bounds HMAC's strength by - // `min(strength of K, 2C)` for a `C`-bit chaining value). This is the value - // `MAC::new` enforces against the key and `keygen_from_rng` against the RNG. - const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; + // SP 800-107r1 s.5.3.4, extrapolated from SHA-2 to SM3, which is likewise Merkle-Damgard: + // min(strength of K, 2C). SM3 has C = 256, so 2C = 512, and the key is OUTPUT_LEN = 256 bits, + // so the key binds: 256 bits. + const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } /// Assigned by the Chinese OSCCA (GM/T 0006): hmac-sm3 { sm3 2 } = 1.2.156.10197.1.401.2 From 08d59d1ccf1ab004e4052cb4231b6eed515ffb77 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Sat, 12 Sep 2026 16:06:02 -0500 Subject: [PATCH 6/6] Added a release note --- alpha_0.1.3_release_notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index 4e085587..d29f91d4 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -21,3 +21,7 @@ existing test used `0xFF`, which masked the second error. * Changed the order of bits when absorbing a final partial byte to match ASN.1 DER BIT_STRING bit ordering. +* Re-arranged the HMAC and HKDF crates so that they are utility crates, and the pub types HMAC_SHA256, HMAC_SHA3_256, + HKDF_SHA256, and so on now live in the `bouncycastle_sha2::hmac`, `bouncycastle_sha3::hmac`, and + `bouncycastle_sha2::hkdf` namespaces. At the same time, adjustments were made to the MAX_SECURITY_STRENGTH of the HMAC + and HKDF algorithms because SP 800-107r1 allows them to be higher than what we had previously.