Skip to main content

dryoc/classic/
crypto_hash.rs

1//! # SHA-2 and SHA-3 hashing
2//!
3//! Implements libsodium's `crypto_hash_sha256_*`, `crypto_hash_sha512_*`,
4//! `crypto_hash_sha3256_*`, and `crypto_hash_sha3512_*` functions.
5//!
6//! SHA-2 and SHA-3 are unkeyed hash functions. They produce fixed-size digests
7//! that identify input bytes, but they do not prove who created the input. Use
8//! [`crate::classic::crypto_auth`] or the direct HMAC modules when a shared
9//! secret key is required.
10//!
11//! ```
12//! use dryoc::classic::crypto_hash::*;
13//!
14//! let message = b"The empty vessel makes the loudest sound.";
15//!
16//! let mut default_digest: Digest = [0u8; 64];
17//! crypto_hash(&mut default_digest, message);
18//! assert_eq!(default_digest.len(), 64);
19//!
20//! let mut sha256 = Sha256Digest::default();
21//! crypto_hash_sha256(&mut sha256, message);
22//! assert_eq!(sha256.len(), 32);
23//!
24//! let mut sha512: Sha512Digest = [0u8; 64];
25//! crypto_hash_sha512(&mut sha512, message);
26//! assert_eq!(sha512.len(), 64);
27//!
28//! let mut sha3256 = Sha3256Digest::default();
29//! crypto_hash_sha3256(&mut sha3256, message);
30//! assert_eq!(sha3256.len(), 32);
31//!
32//! let mut sha3512: Sha3512Digest = [0u8; 64];
33//! crypto_hash_sha3512(&mut sha3512, message);
34//! assert_eq!(sha3512.len(), 64);
35//! ```
36
37use crate::constants::{
38    CRYPTO_HASH_SHA256_BYTES, CRYPTO_HASH_SHA512_BYTES, CRYPTO_HASH_SHA3256_BYTES,
39    CRYPTO_HASH_SHA3512_BYTES,
40};
41use crate::sha3::{Sha3256, Sha3512};
42use crate::sha256::Sha256;
43use crate::sha512::*;
44
45/// Type alias for SHA512 digest output.
46pub type Digest = Sha512Digest;
47/// Type alias for SHA256 digest output.
48pub type Sha256Digest = [u8; CRYPTO_HASH_SHA256_BYTES];
49/// Type alias for SHA512 digest output.
50pub type Sha512Digest = [u8; CRYPTO_HASH_SHA512_BYTES];
51/// Type alias for SHA3-256 digest output.
52pub type Sha3256Digest = [u8; CRYPTO_HASH_SHA3256_BYTES];
53/// Type alias for SHA3-512 digest output.
54pub type Sha3512Digest = [u8; CRYPTO_HASH_SHA3512_BYTES];
55
56/// Computes a SHA-512 hash from `input` using libsodium's default
57/// `crypto_hash` primitive.
58pub fn crypto_hash(output: &mut Digest, input: &[u8]) {
59    crypto_hash_sha512(output, input);
60}
61
62/// Generates the `*State` struct and `init`/`update`/`final` triple for one
63/// hash function. Attributes on an entry, including doc comments, are applied
64/// to the generated item.
65macro_rules! crypto_hash_state {
66    (
67        $(#[$state_meta:meta])*
68        state: $state:ident($hasher:ty),
69        $(#[$init_meta:meta])*
70        init: $init:ident,
71        $(#[$update_meta:meta])*
72        update: $update:ident,
73        $(#[$final_meta:meta])*
74        final: $final:ident($digest:ty)
75    ) => {
76        $(#[$state_meta])*
77        #[derive(Default)]
78        pub struct $state {
79            pub(super) hasher: $hasher,
80        }
81
82        $(#[$init_meta])*
83        #[must_use]
84        pub fn $init() -> $state {
85            <$state>::default()
86        }
87
88        $(#[$update_meta])*
89        pub fn $update(state: &mut $state, input: &[u8]) {
90            state.hasher.update(input);
91        }
92
93        $(#[$final_meta])*
94        pub fn $final(state: $state, output: &mut $digest) {
95            state.hasher.finalize_into_bytes(output)
96        }
97    };
98}
99
100/// Computes a SHA-256 hash from `input`.
101pub fn crypto_hash_sha256(output: &mut Sha256Digest, input: &[u8]) {
102    Sha256::compute_into_bytes(output, input);
103}
104
105crypto_hash_state! {
106    /// Internal state for SHA-256 functions.
107    state: Sha256State(Sha256),
108    /// Initializes a SHA-256 hasher.
109    init: crypto_hash_sha256_init,
110    /// Updates `state` of SHA-256 hasher with `input`.
111    update: crypto_hash_sha256_update,
112    /// Finalizes `state` of SHA-256, and writes the digest to `output`
113    /// consuming `state`.
114    final: crypto_hash_sha256_final(Sha256Digest)
115}
116
117/// Computes a SHA-512 hash from `input`.
118pub fn crypto_hash_sha512(output: &mut Digest, input: &[u8]) {
119    Sha512::compute_into_bytes(output, input);
120}
121
122crypto_hash_state! {
123    /// Internal state for SHA-512 functions.
124    state: Sha512State(Sha512),
125    /// Initializes a SHA-512 hasher.
126    init: crypto_hash_sha512_init,
127    /// Updates `state` of SHA-512 hasher with `input`.
128    update: crypto_hash_sha512_update,
129    /// Finalizes `state` of SHA-512, and writes the digest to `output`
130    /// consuming `state`.
131    final: crypto_hash_sha512_final(Digest)
132}
133
134/// Computes a SHA3-256 hash from `input`.
135pub fn crypto_hash_sha3256(output: &mut Sha3256Digest, input: &[u8]) {
136    let mut state = crypto_hash_sha3256_init();
137    crypto_hash_sha3256_update(&mut state, input);
138    crypto_hash_sha3256_final(state, output);
139}
140
141crypto_hash_state! {
142    /// Internal state for SHA3-256 functions.
143    state: Sha3256State(Sha3256),
144    /// Initializes a SHA3-256 hasher.
145    init: crypto_hash_sha3256_init,
146    /// Updates `state` of SHA3-256 hasher with `input`.
147    update: crypto_hash_sha3256_update,
148    /// Finalizes `state` of SHA3-256, and writes the digest to `output`
149    /// consuming `state`.
150    final: crypto_hash_sha3256_final(Sha3256Digest)
151}
152
153/// Computes a SHA3-512 hash from `input`.
154pub fn crypto_hash_sha3512(output: &mut Sha3512Digest, input: &[u8]) {
155    let mut state = crypto_hash_sha3512_init();
156    crypto_hash_sha3512_update(&mut state, input);
157    crypto_hash_sha3512_final(state, output);
158}
159
160crypto_hash_state! {
161    /// Internal state for SHA3-512 functions.
162    state: Sha3512State(Sha3512),
163    /// Initializes a SHA3-512 hasher.
164    init: crypto_hash_sha3512_init,
165    /// Updates `state` of SHA3-512 hasher with `input`.
166    update: crypto_hash_sha3512_update,
167    /// Finalizes `state` of SHA3-512, and writes the digest to `output`
168    /// consuming `state`.
169    final: crypto_hash_sha3512_final(Sha3512Digest)
170}
171
172#[cfg(test)]
173mod tests {
174    use sha2::Digest as _;
175
176    use super::*;
177    use crate::sha3::test_vectors::{SHA3_256_RATE, SHA3_512_RATE, sha3_256, sha3_512};
178    use crate::test_prelude::*;
179    use crate::utils::test_util::hex;
180
181    fn pattern(len: usize) -> Vec<u8> {
182        (0..len as u32).map(|i| (i * 31 % 251) as u8).collect()
183    }
184
185    /// Lengths around the padding boundary (the last message length whose
186    /// `0x80` and bit-length field still fit the same block), the block
187    /// boundary and two blocks, for a hash with `block`-byte blocks and a
188    /// `length_field`-byte bit-length field.
189    fn sha2_lengths(block: usize, length_field: usize) -> impl Iterator<Item = usize> {
190        let pad = block - length_field;
191        [
192            0,
193            1,
194            pad - 1,
195            pad,
196            pad + 1,
197            block - 1,
198            block,
199            block + 1,
200            2 * block - 1,
201            2 * block,
202        ]
203        .into_iter()
204    }
205
206    /// Drives a classic `init`/`update`/`final` triple over `message` with
207    /// each chunking: one update, exact `block`-sized updates, and one byte
208    /// per update with an empty update around every byte.
209    fn streamed<S>(
210        message: &[u8],
211        block: usize,
212        init: impl Fn() -> S,
213        update: impl Fn(&mut S, &[u8]),
214        finalize: impl Fn(S) -> Vec<u8>,
215    ) -> [Vec<u8>; 3] {
216        let mut one = init();
217        update(&mut one, message);
218
219        let mut blocks = init();
220        for chunk in message.chunks(block) {
221            update(&mut blocks, chunk);
222        }
223
224        let mut bytes = init();
225        update(&mut bytes, b"");
226        for byte in message {
227            update(&mut bytes, core::slice::from_ref(byte));
228            update(&mut bytes, b"");
229        }
230
231        [finalize(one), finalize(blocks), finalize(bytes)]
232    }
233
234    /// FIPS 180-4 SHA-256 known answers through the one-shot function.
235    #[test]
236    fn test_crypto_hash_sha256() {
237        for (message, expected) in [
238            (
239                &b""[..],
240                "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
241            ),
242            (
243                b"abc",
244                "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
245            ),
246            (
247                b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
248                "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1",
249            ),
250        ] {
251            let mut digest = [0u8; CRYPTO_HASH_SHA256_BYTES];
252            crypto_hash_sha256(&mut digest, message);
253            assert_eq!(digest.to_vec(), hex(expected));
254        }
255    }
256
257    /// `crypto_hash` is FIPS 180-4 SHA-512: the `abc` and two-block example
258    /// digests, one-shot and streamed.
259    #[test]
260    fn test_crypto_hash_is_sha512() {
261        for (message, expected) in [
262            (
263                &b"abc"[..],
264                concat!(
265                    "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a",
266                    "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
267                ),
268            ),
269            (
270                b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno\
271                  ijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
272                concat!(
273                    "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018",
274                    "501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909",
275                ),
276            ),
277        ] {
278            let expected = hex(expected);
279            let mut digest = [0u8; CRYPTO_HASH_SHA512_BYTES];
280            crypto_hash(&mut digest, message);
281            assert_eq!(digest.to_vec(), expected);
282
283            for actual in streamed(
284                message,
285                128,
286                crypto_hash_sha512_init,
287                crypto_hash_sha512_update,
288                |state| {
289                    let mut digest = [0u8; CRYPTO_HASH_SHA512_BYTES];
290                    crypto_hash_sha512_final(state, &mut digest);
291                    digest.to_vec()
292                },
293            ) {
294                assert_eq!(actual, expected);
295            }
296        }
297    }
298
299    /// The SHA-256 classic API at every padding and block boundary, for each
300    /// chunking, against the `sha2` crate.
301    #[test]
302    fn test_crypto_hash_sha256_boundaries_match_sha2() {
303        for len in sha2_lengths(64, 8) {
304            let message = pattern(len);
305            let expected = sha2::Sha256::digest(&message).to_vec();
306            let mut digest = [0u8; CRYPTO_HASH_SHA256_BYTES];
307            crypto_hash_sha256(&mut digest, &message);
308            assert_eq!(digest.to_vec(), expected, "one-shot len {len}");
309            for actual in streamed(
310                &message,
311                64,
312                crypto_hash_sha256_init,
313                crypto_hash_sha256_update,
314                |state| {
315                    let mut digest = [0u8; CRYPTO_HASH_SHA256_BYTES];
316                    crypto_hash_sha256_final(state, &mut digest);
317                    digest.to_vec()
318                },
319            ) {
320                assert_eq!(actual, expected, "streamed len {len}");
321            }
322        }
323    }
324
325    /// The SHA-512 classic API at every padding and block boundary, for each
326    /// chunking, against the `sha2` crate.
327    #[test]
328    fn test_crypto_hash_sha512_boundaries_match_sha2() {
329        for len in sha2_lengths(128, 16) {
330            let message = pattern(len);
331            let expected = sha2::Sha512::digest(&message).to_vec();
332            let mut digest = [0u8; CRYPTO_HASH_SHA512_BYTES];
333            crypto_hash_sha512(&mut digest, &message);
334            assert_eq!(digest.to_vec(), expected, "one-shot len {len}");
335            for actual in streamed(
336                &message,
337                128,
338                crypto_hash_sha512_init,
339                crypto_hash_sha512_update,
340                |state| {
341                    let mut digest = [0u8; CRYPTO_HASH_SHA512_BYTES];
342                    crypto_hash_sha512_final(state, &mut digest);
343                    digest.to_vec()
344                },
345            ) {
346                assert_eq!(actual, expected, "streamed len {len}");
347            }
348        }
349    }
350
351    /// FIPS 202 SHA3-256 answers at the rate boundaries through the one-shot
352    /// function and every `init`/`update`/`final` chunking (rate-sized
353    /// updates end exactly on a permutation).
354    #[test]
355    fn test_crypto_hash_sha3256_known_answers() {
356        for (message, expected) in sha3_256() {
357            let len = message.len();
358            let mut digest = [0u8; CRYPTO_HASH_SHA3256_BYTES];
359            crypto_hash_sha3256(&mut digest, &message);
360            assert_eq!(digest.to_vec(), expected, "one-shot len {len}");
361            if len > 2 * SHA3_256_RATE {
362                // The million-byte message only needs the one-shot check.
363                continue;
364            }
365            for actual in streamed(
366                &message,
367                SHA3_256_RATE,
368                crypto_hash_sha3256_init,
369                crypto_hash_sha3256_update,
370                |state| {
371                    let mut digest = [0u8; CRYPTO_HASH_SHA3256_BYTES];
372                    crypto_hash_sha3256_final(state, &mut digest);
373                    digest.to_vec()
374                },
375            ) {
376                assert_eq!(actual, expected, "streamed len {len}");
377            }
378        }
379    }
380
381    #[test]
382    fn test_crypto_hash_sha3512_known_answers() {
383        for (message, expected) in sha3_512() {
384            let len = message.len();
385            let mut digest = [0u8; CRYPTO_HASH_SHA3512_BYTES];
386            crypto_hash_sha3512(&mut digest, &message);
387            assert_eq!(digest.to_vec(), expected, "one-shot len {len}");
388            if len > 2 * SHA3_512_RATE {
389                continue;
390            }
391            for actual in streamed(
392                &message,
393                SHA3_512_RATE,
394                crypto_hash_sha3512_init,
395                crypto_hash_sha3512_update,
396                |state| {
397                    let mut digest = [0u8; CRYPTO_HASH_SHA3512_BYTES];
398                    crypto_hash_sha3512_final(state, &mut digest);
399                    digest.to_vec()
400                },
401            ) {
402                assert_eq!(actual, expected, "streamed len {len}");
403            }
404        }
405    }
406
407    /// libsodium's `crypto_hash` (SHA-512) at the same boundaries, one-shot
408    /// and streamed with the same cuts on both sides.
409    #[cfg(dryoc_native_tests)]
410    #[test]
411    fn test_crypto_hash_sha512_matches_libsodium() {
412        use crate::native_test_util::{self as sodium, HashSha512State};
413
414        for len in sha2_lengths(128, 16) {
415            let message = pattern(len);
416            let expected = sodium::crypto_hash_sha512(&message);
417            let mut digest = [0u8; CRYPTO_HASH_SHA512_BYTES];
418            crypto_hash(&mut digest, &message);
419            assert_eq!(digest, expected, "one-shot len {len}");
420
421            let mut theirs = HashSha512State::new();
422            let mut ours = crypto_hash_sha512_init();
423            for chunk in message.chunks(127) {
424                theirs.update(chunk);
425                crypto_hash_sha512_update(&mut ours, chunk);
426                theirs.update(b"");
427                crypto_hash_sha512_update(&mut ours, b"");
428            }
429            crypto_hash_sha512_final(ours, &mut digest);
430            assert_eq!(digest, theirs.finalize(), "streamed len {len}");
431        }
432    }
433
434    /// Message lengths around the SHA-3 padding and permutation boundaries
435    /// for a sponge with `rate`-byte blocks: `rate - 1` is the last length
436    /// whose domain byte and final padding bit share a byte.
437    #[cfg(dryoc_native_tests)]
438    fn sha3_lengths(rate: usize) -> impl Iterator<Item = usize> {
439        [
440            0,
441            1,
442            rate - 2,
443            rate - 1,
444            rate,
445            rate + 1,
446            2 * rate - 1,
447            2 * rate,
448            2 * rate + 1,
449            3 * rate + 17,
450        ]
451        .into_iter()
452    }
453
454    /// libsodium's `crypto_hash_sha3256` and `crypto_hash_sha3512` at the
455    /// rate boundaries, one-shot and streamed with the same cuts on both
456    /// sides (single bytes, an odd size, and exactly one rate).
457    #[cfg(dryoc_native_tests)]
458    #[test]
459    fn test_crypto_hash_sha3_matches_libsodium() {
460        use crate::native_test_util::{self as sodium, HashSha3256State, HashSha3512State};
461
462        macro_rules! check {
463            (
464                $rate:expr,
465                $bytes:expr,
466                $oneshot:ident,
467                $init:ident,
468                $update:ident,
469                $final:ident,
470                $theirs:ident
471            ) => {
472                for len in sha3_lengths($rate) {
473                    let message = pattern(len);
474                    let expected = sodium::$oneshot(&message);
475                    let mut digest = [0u8; $bytes];
476                    $oneshot(&mut digest, &message);
477                    assert_eq!(digest, expected, "one-shot len {len}");
478
479                    for chunk_len in [1, 67, $rate] {
480                        let mut theirs = $theirs::new();
481                        let mut ours = $init();
482                        for chunk in message.chunks(chunk_len) {
483                            theirs.update(chunk);
484                            $update(&mut ours, chunk);
485                            theirs.update(b"");
486                            $update(&mut ours, b"");
487                        }
488                        $final(ours, &mut digest);
489                        assert_eq!(
490                            digest,
491                            theirs.finalize(),
492                            "streamed len {len}, chunks of {chunk_len}"
493                        );
494                    }
495                }
496            };
497        }
498
499        check!(
500            SHA3_256_RATE,
501            CRYPTO_HASH_SHA3256_BYTES,
502            crypto_hash_sha3256,
503            crypto_hash_sha3256_init,
504            crypto_hash_sha3256_update,
505            crypto_hash_sha3256_final,
506            HashSha3256State
507        );
508        check!(
509            SHA3_512_RATE,
510            CRYPTO_HASH_SHA3512_BYTES,
511            crypto_hash_sha3512,
512            crypto_hash_sha3512_init,
513            crypto_hash_sha3512_update,
514            crypto_hash_sha3512_final,
515            HashSha3512State
516        );
517    }
518}