Skip to main content

dryoc/sha256/
mod.rs

1//! # SHA-256 hash algorithm
2//!
3//! Provides an implementation of the SHA-256 hash algorithm.
4//!
5//! SHA-256 is an unkeyed cryptographic hash function. It turns arbitrary input
6//! bytes into a 32-byte digest. Hashes are useful for fingerprints and
7//! compatibility with protocols that require SHA-256, but they do not
8//! authenticate messages by themselves. Use [`crate::auth`] or [`crate::hmac`]
9//! when a secret key must be involved.
10//!
11//! ## Example
12//!
13//! ```
14//! # #[cfg(feature = "alloc")]
15//! # {
16//! use dryoc::sha256::Sha256;
17//!
18//! let mut state = Sha256::new();
19//! state.update(b"All the world's a stage, ");
20//! state.update(b"and all the men and women merely players.");
21//! let hash = state.finalize_to_vec();
22//! assert_eq!(hash.len(), 32);
23//! # }
24//! ```
25use crate::constants::CRYPTO_HASH_SHA256_BYTES;
26use crate::sha2_impl::sha2_hasher;
27use crate::types::*;
28
29#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
30mod sha256_aarch64;
31
32/// Type alias for SHA256 digest, provided for convenience.
33pub type Digest = StackByteArray<CRYPTO_HASH_SHA256_BYTES>;
34
35const BLOCK_BYTES: usize = 64;
36
37const IV: [u32; 8] = [
38    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
39];
40
41/// Compresses whole blocks into `state`, using the hardware `sha2` extension
42/// when the running CPU has it.
43///
44/// Out of line at opt-level `z` (with the kernels it calls at `z`, `s` and
45/// `2`), which adds no copy: they only get `&mut` to the hasher's own state
46/// or to `compute_into_bytes`' wiped local, and `&` to the blocks.
47#[inline]
48fn compress(state: &mut [u32; 8], blocks: &[[u8; BLOCK_BYTES]]) {
49    #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
50    if let Some(sha2) = crate::aarch64::Sha2::new() {
51        sha256_aarch64::compress(sha2, state, blocks);
52        return;
53    }
54    sha2::block_api::compress256(state, blocks);
55}
56
57sha2_hasher! {
58    /// SHA-256 hasher.
59    ///
60    /// Buffers input into 64-byte blocks and drives the hardware `sha2` compression
61    /// (runtime-detected on AArch64) or the `sha2` crate's
62    /// compression function, which selects hardware SHA-256 instructions at
63    /// runtime where available. The state and any buffered input are wiped when
64    /// the hasher is dropped.
65    pub struct Sha256;
66    algorithm: "SHA-256",
67    word: u32,
68    word_bytes: 4,
69    length: u64,
70    length_bytes: 8,
71    block_bytes: BLOCK_BYTES,
72    digest_bytes: CRYPTO_HASH_SHA256_BYTES,
73    iv: IV,
74    compress: compress,
75}
76
77#[cfg(all(test, feature = "alloc"))]
78mod tests {
79    use sha2::Digest as _;
80
81    use super::*;
82    use crate::test_prelude::*;
83    use crate::utils::test_util::hex;
84
85    /// FIPS 180-2 test vectors, including the 56-byte message whose padding
86    /// needs a second block.
87    #[test]
88    fn test_sha256_known_answers() {
89        assert_eq!(
90            Sha256::compute_to_vec(b""),
91            hex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
92        );
93        assert_eq!(
94            Sha256::compute_to_vec(b"abc"),
95            hex("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
96        );
97        assert_eq!(
98            Sha256::compute_to_vec(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),
99            hex("248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1")
100        );
101    }
102
103    /// Every buffer fill level and padding boundary, absorbed in one call and
104    /// in irregular chunks, matches the `sha2` crate.
105    #[test]
106    fn test_sha256_matches_sha2_for_all_lengths_and_chunkings() {
107        let message: Vec<u8> = (0..600u32).map(|i| (i * 31 % 251) as u8).collect();
108        for len in (0..200).chain([255, 256, 257, 511, 512, 513, 599, 600]) {
109            let expected = sha2::Sha256::digest(&message[..len]).to_vec();
110            assert_eq!(
111                Sha256::compute_to_vec(&message[..len]),
112                expected,
113                "len {len}"
114            );
115
116            let mut hasher = Sha256::new();
117            let mut offset = 0;
118            for chunk in [1usize, 7, 63, 64, 65, 100, 3].iter().cycle() {
119                if offset >= len {
120                    break;
121                }
122                let end = (offset + chunk).min(len);
123                hasher.update(&message[offset..end]);
124                offset = end;
125            }
126            assert_eq!(hasher.finalize_to_vec(), expected, "chunked len {len}");
127        }
128    }
129
130    /// Empty updates at every buffer state and updates that end exactly on
131    /// a block boundary from a partially filled buffer (1 + 63, 63 + 1), a
132    /// whole block from an empty buffer, and finalization from both an empty
133    /// and an almost-full buffer, against the `sha2` crate.
134    #[test]
135    fn test_empty_and_exact_fill_updates_match_sha2() {
136        const B: usize = BLOCK_BYTES;
137        let message: Vec<u8> = (0..3 * B as u32).map(|i| (i * 31 % 251) as u8).collect();
138        for len in [B, B + 1, 2 * B, 2 * B + 1, 3 * B - 1, 3 * B] {
139            let message = &message[..len];
140            let mut cuts: Vec<usize> = [0, 1, B, 2 * B, 3 * B - 1, len]
141                .into_iter()
142                .filter(|&cut| cut <= len)
143                .collect();
144            cuts.dedup();
145            let mut hasher = Sha256::new();
146            hasher.update(b"");
147            for window in cuts.windows(2) {
148                hasher.update(&message[window[0]..window[1]]);
149                hasher.update(b"");
150            }
151            assert_eq!(
152                hasher.finalize_to_vec(),
153                sha2::Sha256::digest(message).to_vec(),
154                "len {len}"
155            );
156        }
157    }
158
159    /// The hardware loop agrees with the portable compression for every block
160    /// count around its loop boundaries, including the empty run.
161    #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
162    #[test]
163    fn test_hw_compress_matches_portable() {
164        let Some(sha2) = crate::aarch64::Sha2::new() else {
165            return;
166        };
167        let blocks: Vec<[u8; 64]> = (0..40u32)
168            .map(|b| {
169                core::array::from_fn(|i| (b * 64 + i as u32).wrapping_mul(2654435761) as u8 >> 1)
170            })
171            .collect();
172        for n in 0..=blocks.len() {
173            let mut expected = IV;
174            let mut actual = IV;
175            sha2::block_api::compress256(&mut expected, &blocks[..n]);
176            sha256_aarch64::compress(sha2, &mut actual, &blocks[..n]);
177            assert_eq!(actual, expected, "{n} blocks");
178        }
179    }
180}