Skip to content

Commit 785dbef

Browse files
dghgitclaude
andcommitted
sha2: SHA512t becomes usable for every t FIPS 180-4 s. 5.3.6 defines a hash for, not just the two approved truncations, so the IV Generation Function regains the one- and two-digit decimal branches that b11f8f6 dropped as unreachable -- writing a fixed three digits gives the "0256" spelling the section forbids, and hence the wrong IV, for every t below 100; t is checked against the section's own rule (positive, below 512, not 384) plus this crate's multiple-of-8 requirement, which BC Java's SHA512tDigest also imposes, and the unapproved truncations carry SHA512tParams::FIPS_APPROVED = false which SHA512Internal::new asserts in an inline const, so reaching one takes new_allow_unapproved_t() the way an encrypting mode takes ENCRYPTION_APPROVED; ALG_NAME, OUTPUT_LEN and MAX_SECURITY_STRENGTH are now derived from t and pinned to their old values for t = 224 and t = 256; new sha512t_tests.rs cross-checks eight truncations spanning all three digit branches against BC Java, and of the file's 259 mutants 180 are caught, 5 die on timeout and the 5 missed are the XOR/OR equivalences already documented at their sites
Assisted-by: Claude:claude-opus-5 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 3912434 commit 785dbef

3 files changed

Lines changed: 588 additions & 54 deletions

File tree

‎crypto/sha2/src/lib.rs‎

Lines changed: 165 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,26 @@
9898
//! | Object | Size (bytes) |
9999
//! |----------------------------------------------------------|--------------|
100100
//! | `SHA224`, `SHA256` | 112 |
101-
//! | `SHA384`, `SHA512`, `SHA512_224`, `SHA512_256` | 208 |
101+
//! | `SHA384`, `SHA512`, `SHA512t<T>` (incl. `SHA512_224`, `SHA512_256`) | 208 |
102102
//! | Suspended `SHA224`/`SHA256` state | 108 |
103-
//! | Suspended `SHA384`/`SHA512`/`SHA512_224`/`SHA512_256` state | 204 |
103+
//! | Suspended `SHA384`/`SHA512`/`SHA512t<T>` state | 204 |
104+
//!
105+
//! `T` does not affect either size: the truncation happens on the way out of `do_final`, so every
106+
//! member of the SHA-512 family carries the same 512-bit chaining value and 1024-bit buffer.
104107
//!
105108
//! # Security Considerations
106109
//!
107110
//! * SHA-224/256/384/512 offer 112/128/192/256 bits of collision resistance respectively;
108-
//! SHA-512/224 and SHA-512/256 offer 112 and 128 bits (SP 800-107r1, Table 1 (§4.2)).
111+
//! SHA-512/224 and SHA-512/256 offer 112 and 128 bits (SP 800-107r1, Table 1 (§4.2)). More
112+
//! generally SHA-512/t offers t/2 bits, which is what [`SHA512t`]'s `MAX_SECURITY_STRENGTH`
113+
//! reports, rounded down to a modelled level.
114+
//! * **Only two SHA-512/t truncations are approved.** [`SHA512t`] is generic over `T`, but FIPS
115+
//! 180-4 s. 5.3.6 approves only t = 224 and t = 256. Any other `T` is a well-defined hash that
116+
//! is nonetheless unapproved, and has to be constructed through
117+
//! [`SHA512Internal::new_allow_unapproved_t`](sha512::SHA512Internal::new_allow_unapproved_t)
118+
//! rather than `new()`; see [`SHA512t`] for the reasoning and the compile-time gate. Small `t`
119+
//! is also simply weak -- SHA-512/8 has a one-byte digest -- and carries a
120+
//! `SecurityStrength::None`.
109121
//! * SHA-2 is a Merkle–Damgård construction and is therefore subject to length-extension:
110122
//! `H(k || m)` is not a secure MAC. Use HMAC (`bouncycastle-hmac`) for keyed hashing.
111123
//! * SHA-224, SHA-384, SHA-512/224 and SHA-512/256 are truncations of SHA-256 or SHA-512 with
@@ -162,9 +174,68 @@ pub type SHA256 = SHA256Internal<SHA256Params>;
162174
pub type SHA384 = SHA512Internal<SHA384Params>;
163175
/// Public type for SHA512.
164176
pub type SHA512 = SHA512Internal<SHA512Params>;
165-
/// Public type for the SHA-512/t truncating family (FIPS 180-4 s. 5.3.6): SHA-512 with a t-specific initial
166-
/// hash value, truncated to `T` bits. Only the NIST-approved truncations `T = 224` and `T = 256`
167-
/// can be instantiated, enforced by the sealing trait `SHA512InitValue`; see [`SHA512_224`] and [`SHA512_256`].
177+
/// Public type for the SHA-512/t truncating family (FIPS 180-4 s. 5.3.6): SHA-512 with a
178+
/// t-specific initial hash value, truncated to `T` bits.
179+
///
180+
/// `T` may be any truncation the standard defines a hash for -- "any positive integer without a
181+
/// leading zero such that t < 512, and t is not 384" -- narrowed here to multiples of 8, since the
182+
/// digest has to be a whole number of bytes. Anything else is a compile error naming the rule it
183+
/// broke. The initial hash value is produced at compile time by the s. 5.3.6 IV Generation
184+
/// Function, so a new `T` costs nothing at runtime and needs no table.
185+
///
186+
/// ```
187+
/// use bouncycastle_core::traits::Hash;
188+
/// use bouncycastle_sha2::{SHA512_256, SHA512t};
189+
///
190+
/// // An approved truncation: the ordinary constructor.
191+
/// let digest = SHA512_256::new().hash(b"abc");
192+
/// assert_eq!(digest.len(), 32);
193+
///
194+
/// // SHA512t<256> *is* SHA512_256.
195+
/// assert_eq!(SHA512t::<256>::new().hash(b"abc"), digest);
196+
/// ```
197+
///
198+
/// # Only `T = 224` and `T = 256` are approved
199+
///
200+
/// FIPS 180-4 s. 5.3.6 approves exactly two truncations, SHA-512/224 and SHA-512/256 ("Other
201+
/// SHA-512/t hash algorithms with different t values may be specified in [SP 800-107] in the
202+
/// future as the need arises"). Every other `T` is a well-defined SHA-512/t but not an approved
203+
/// hash algorithm, so it must not be used where an approved one is required.
204+
///
205+
/// That distinction is enforced rather than merely documented, in the same shape as
206+
/// `ElectronicCodeBook::ENCRYPTION_APPROVED` in the cipher traits: the unapproved truncations carry
207+
/// [`SHA512tParams::FIPS_APPROVED`]` == false`, and
208+
/// [`SHA512Internal::new`](sha512::SHA512Internal::new) checks it in an inline `const`. Building
209+
/// one the ordinary way -- including through `Default`, and so through any generic code that
210+
/// requires it -- is therefore a compile error at the call site, and
211+
/// [`SHA512Internal::new_allow_unapproved_t`](sha512::SHA512Internal::new_allow_unapproved_t) is
212+
/// the way to say you meant it:
213+
///
214+
/// ```
215+
/// use bouncycastle_core::traits::{Algorithm, Hash};
216+
/// use bouncycastle_sha2::{SHA512t, SHA512tParams};
217+
///
218+
/// assert!(!SHA512tParams::<96>::FIPS_APPROVED);
219+
/// let digest = SHA512t::<96>::new_allow_unapproved_t().hash(b"");
220+
/// assert_eq!(digest.len(), 12);
221+
/// assert_eq!(<SHA512t<96> as Algorithm>::ALG_NAME, "SHA512/96");
222+
/// ```
223+
///
224+
/// ```compile_fail
225+
/// use bouncycastle_sha2::SHA512t;
226+
/// // SHA-512/96 is not an approved hash algorithm, so `new()` does not build.
227+
/// let _ = SHA512t::<96>::new();
228+
/// ```
229+
///
230+
/// ```compile_fail
231+
/// use bouncycastle_sha2::SHA512t;
232+
/// // FIPS 180-4 s. 5.3.6: "t is not 384" -- SHA384 is its own algorithm with its own IV.
233+
/// let _ = SHA512t::<384>::new_allow_unapproved_t();
234+
/// ```
235+
///
236+
/// See [`SHA512_224`] and [`SHA512_256`] for the approved pair, which are aliases of this type and
237+
/// are additionally the only truncations with an assigned [`AlgorithmOID`] and a `HashFactory`
238+
/// entry.
168239
pub type SHA512t<const T: usize> = SHA512Internal<SHA512tParams<T>>;
169240
/// Public type for SHA512/224 (FIPS 180-4 s. 6.6).
170241
pub type SHA512_224 = SHA512t<224>;
@@ -189,6 +260,16 @@ trait SHA256InitValue: HashAlgParams {
189260
trait SHA512InitValue: HashAlgParams {
190261
/// The initial hash value H(0), FIPS 180-4 s. 5.3.4 / 5.3.5 / 5.3.6.
191262
const H0: [u64; 8];
263+
264+
/// Whether this parameter set is an approved hash algorithm.
265+
///
266+
/// `true` for SHA-384, SHA-512 and the two approved truncations SHA-512/224 and SHA-512/256;
267+
/// `false` for every other SHA-512/t, which FIPS 180-4 s. 5.3.6 defines but does not approve.
268+
/// [`SHA512Internal::new`] checks this in an inline `const`, so constructing an unapproved
269+
/// truncation the ordinary way is a compile error at the call site and
270+
/// [`SHA512Internal::new_allow_unapproved_t`] is the deliberate way in -- the same shape as
271+
/// `ElectronicCodeBook::ENCRYPTION_APPROVED` in the cipher traits.
272+
const FIPS_APPROVED: bool = true;
192273
}
193274

194275
/// The public hash types expose the same parameters as their `*Params` marker, so the constants
@@ -297,53 +378,104 @@ impl SHA512InitValue for SHA512Params {
297378
/*** SHA-512/t ***/
298379
/// The parameters for SHA-512/t (FIPS 180-4 s. 5.3.6), for a truncation of `T` bits.
299380
///
300-
/// The parameter traits are implemented only for the NIST-approved truncations `T = 224` and
301-
/// `T = 256` ("Other SHA-512/t hash algorithms with different t values may be specified in
302-
/// [SP 800-107] in the future as the need arises"), so any other `T` is a compile-time error.
381+
/// Implemented for every `T` the section defines a hash for, with two restrictions checked when
382+
/// the parameter set is instantiated, so a bad `T` is a compile error rather than a runtime one:
383+
///
384+
/// * FIPS 180-4 s. 5.3.6's own rule, "t is any positive integer without a leading zero such that
385+
/// t < 512, and t is not 384";
386+
/// * this crate's additional requirement that `T` be a multiple of 8, since the digest has to be a
387+
/// whole number of bytes. See [`sha512::sha512t_h0`] for why.
388+
///
389+
/// Only `T = 224` and `T = 256` are *approved* ("Other SHA-512/t hash algorithms with different t
390+
/// values may be specified in [SP 800-107] in the future as the need arises"); the rest are
391+
/// defined but unapproved, and are gated behind
392+
/// [`SHA512Internal::new_allow_unapproved_t`](sha512::SHA512Internal::new_allow_unapproved_t).
303393
#[derive(Clone)]
304394
pub struct SHA512tParams<const T: usize>;
305395

306-
/*** SHA512/224 ***/
307-
impl Algorithm for SHA512tParams<224> {
308-
const ALG_NAME: &'static str = SHA512_224_NAME;
309-
const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit;
396+
impl<const T: usize> SHA512tParams<T> {
397+
/// Whether SHA-512/`T` is an approved hash algorithm: FIPS 180-4 s. 5.3.6 approves only
398+
/// t = 224 and t = 256.
399+
///
400+
/// This is what [`SHA512Internal::new`](sha512::SHA512Internal::new) gates on, so it is also
401+
/// the answer to "does this truncation need
402+
/// [`new_allow_unapproved_t`](sha512::SHA512Internal::new_allow_unapproved_t)?". Public so a
403+
/// caller can make the same check -- `const { assert!(SHA512tParams::<T>::FIPS_APPROVED) }` in
404+
/// generic code -- without reaching into the sealed parameter trait.
405+
pub const FIPS_APPROVED: bool = sha512::t_is_fips_approved(T);
406+
407+
/// `"SHA512/t"` with `T` in decimal, NUL-padded; see [`Self::ALG_NAME_STR`].
408+
const ALG_NAME_BYTES: [u8; sha512::ALG_NAME_BUF_LEN] = sha512::alg_name_bytes(T);
409+
410+
/// The algorithm name, e.g. `"SHA512/224"`. Built at compile time from `T` because a const
411+
/// generic cannot be formatted into a `&'static str` directly.
412+
const ALG_NAME_STR: &'static str = {
413+
let bytes: &'static [u8; sha512::ALG_NAME_BUF_LEN] = &Self::ALG_NAME_BYTES;
414+
let (name, _padding) = bytes.split_at(sha512::alg_name_len(T));
415+
match core::str::from_utf8(name) {
416+
Ok(name) => name,
417+
// unreachable: alg_name_bytes writes only ASCII.
418+
Err(_) => panic!("SHA-512/t algorithm name is not UTF-8"),
419+
}
420+
};
421+
}
422+
423+
impl<const T: usize> Algorithm for SHA512tParams<T> {
424+
const ALG_NAME: &'static str = Self::ALG_NAME_STR;
425+
/// SP 800-107 Rev 1 Table 1: a t-bit digest offers t/2 bits of collision resistance, rounded
426+
/// down to a modelled level. This reproduces the values the two approved truncations carry:
427+
/// 112-bit for SHA-512/224 and 128-bit for SHA-512/256.
428+
const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::from_bits(T / 2);
310429
}
311-
impl HashAlgParams for SHA512tParams<224> {
312-
const OUTPUT_LEN: usize = 28; // FIPS 180-4 s. 6.6 exception 2: truncated to the left-most 224 bits
430+
impl<const T: usize> HashAlgParams for SHA512tParams<T> {
431+
/// FIPS 180-4 s. 6.6 / s. 6.7 exception 2: truncated to the left-most `T` bits. `T` is a
432+
/// multiple of 8 (checked by [`sha512::check_t`]), so this is exact.
433+
const OUTPUT_LEN: usize = T / 8;
313434
const BLOCK_LEN: usize = 128; // FIPS 180-4 Figure 1: block size 1024 bits
314435
}
436+
impl<const T: usize> SHA512InitValue for SHA512tParams<T> {
437+
/// FIPS 180-4 s. 5.3.6: H(0) from the IV Generation Function. For t = 224 and t = 256 this is
438+
/// the value listed in s. 5.3.6.1 / s. 5.3.6.2, pinned against those words by
439+
/// `tests/sha512t_h0_tests.rs`.
440+
const H0: [u64; 8] = sha512t_h0(T);
441+
// Not recursive: inherent associated consts win name resolution, so this is the public
442+
// `SHA512tParams::<T>::FIPS_APPROVED` above, forwarded so the two cannot disagree.
443+
const FIPS_APPROVED: bool = Self::FIPS_APPROVED;
444+
}
445+
446+
// The two approved truncations get everything else from the generic impls above; only their
447+
// object identifiers, which exist for no other t, are specific to them.
448+
449+
/*** SHA512/224 ***/
315450
/// Assigned by NIST in the Computer Security Objects Register: id-sha512-224 { hashAlgs 5 }
316451
impl AlgorithmOID for SHA512_224 {
317452
const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 5];
318453
const OID_DER: &'static [u8] =
319454
&[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x05];
320455
}
321-
impl SHA512InitValue for SHA512tParams<224> {
322-
// FIPS 180-4 s. 6.6 exception 1: H(0) as specified in s. 5.3.6.1 (pinned against the words
323-
// listed there by tests/sha512t_h0_tests.rs).
324-
const H0: [u64; 8] = sha512t_h0(224);
325-
}
326456

327457
/*** SHA512/256 ***/
328-
impl Algorithm for SHA512tParams<256> {
329-
const ALG_NAME: &'static str = SHA512_256_NAME;
330-
const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit;
331-
}
332-
impl HashAlgParams for SHA512tParams<256> {
333-
const OUTPUT_LEN: usize = 32; // FIPS 180-4 s. 6.7 exception 2: truncated to the left-most 256 bits
334-
const BLOCK_LEN: usize = 128; // FIPS 180-4 Figure 1: block size 1024 bits
335-
}
336458
/// Assigned by NIST in the Computer Security Objects Register: id-sha512-256 { hashAlgs 6 }
337459
impl AlgorithmOID for SHA512_256 {
338460
const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 2, 6];
339461
const OID_DER: &'static [u8] =
340462
&[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x06];
341463
}
342-
impl SHA512InitValue for SHA512tParams<256> {
343-
// FIPS 180-4 s. 6.7 exception 1: H(0) as specified in s. 5.3.6.2 (pinned against the words
344-
// listed there by tests/sha512t_h0_tests.rs).
345-
const H0: [u64; 8] = sha512t_h0(256);
346-
}
464+
465+
// The generic name and output length must keep reproducing exactly what the two approved
466+
// truncations had when they were spelled out by hand, and the two approved truncations must stay
467+
// the only approved ones. `cargo mutants` cannot see a const assertion fail, so these are paired
468+
// with the runtime coverage in tests/sha512t_tests.rs rather than replacing it.
469+
const _: () = assert!(matches!(SHA512tParams::<224>::ALG_NAME_STR.as_bytes(), b"SHA512/224"));
470+
const _: () = assert!(matches!(SHA512tParams::<256>::ALG_NAME_STR.as_bytes(), b"SHA512/256"));
471+
const _: () = assert!(matches!(SHA512_224_NAME.as_bytes(), b"SHA512/224"));
472+
const _: () = assert!(matches!(SHA512_256_NAME.as_bytes(), b"SHA512/256"));
473+
const _: () = assert!(SHA512tParams::<224>::OUTPUT_LEN == 28);
474+
const _: () = assert!(SHA512tParams::<256>::OUTPUT_LEN == 32);
475+
const _: () = assert!(SHA512tParams::<224>::FIPS_APPROVED);
476+
const _: () = assert!(SHA512tParams::<256>::FIPS_APPROVED);
477+
const _: () = assert!(!SHA512tParams::<8>::FIPS_APPROVED);
478+
const _: () = assert!(!SHA512tParams::<504>::FIPS_APPROVED);
347479

348480
pub use sha256::SUSPENDED_SHA256_STATE_LEN;
349481
pub use sha512::SUSPENDED_SHA512_STATE_LEN;

0 commit comments

Comments
 (0)