Skip to main content

crypt_io/hash/
sha2_impl.rs

1//! SHA-2 backend (SHA-256 + SHA-512).
2//!
3//! Thin wrapper over the `sha2` crate (`RustCrypto`). The wrapper exposes
4//! one-shot free functions and matching streaming hashers.
5//!
6//! SHA-2 ships for ecosystem interop (TLS, JWT, certificate fingerprints,
7//! Bitcoin, anywhere a spec names SHA-256 or SHA-512). For raw throughput
8//! prefer [`super::blake3`] — it is typically 4–10× faster on modern
9//! hardware.
10
11use sha2::Digest;
12use sha2::{Sha256, Sha512};
13
14use super::{SHA256_OUTPUT_LEN, SHA512_OUTPUT_LEN};
15
16/// Compute a 32-byte SHA-256 digest of `data`.
17///
18/// # Example
19///
20/// ```
21/// # #[cfg(feature = "hash-sha2")] {
22/// use crypt_io::hash;
23/// let d = hash::sha256(b"abc");
24/// assert_eq!(d.len(), 32);
25/// # }
26/// ```
27#[must_use]
28pub fn sha256(data: &[u8]) -> [u8; SHA256_OUTPUT_LEN] {
29    let mut hasher = Sha256::new();
30    hasher.update(data);
31    hasher.finalize().into()
32}
33
34/// Compute a 64-byte SHA-512 digest of `data`.
35///
36/// # Example
37///
38/// ```
39/// # #[cfg(feature = "hash-sha2")] {
40/// use crypt_io::hash;
41/// let d = hash::sha512(b"abc");
42/// assert_eq!(d.len(), 64);
43/// # }
44/// ```
45#[must_use]
46pub fn sha512(data: &[u8]) -> [u8; SHA512_OUTPUT_LEN] {
47    let mut hasher = Sha512::new();
48    hasher.update(data);
49    hasher.finalize().into()
50}
51
52/// Streaming SHA-256 hasher for inputs that don't fit in memory.
53///
54/// # Example
55///
56/// ```
57/// # #[cfg(feature = "hash-sha2")] {
58/// use crypt_io::hash::Sha256Hasher;
59///
60/// let mut h = Sha256Hasher::new();
61/// h.update(b"first ");
62/// h.update(b"second");
63/// let d = h.finalize();
64/// assert_eq!(d.len(), 32);
65/// # }
66/// ```
67#[derive(Debug, Clone, Default)]
68pub struct Sha256Hasher {
69    inner: Sha256,
70}
71
72impl Sha256Hasher {
73    /// Construct a fresh hasher.
74    #[must_use]
75    pub fn new() -> Self {
76        Self {
77            inner: Sha256::new(),
78        }
79    }
80
81    /// Absorb `data` into the running hash. Returns `&mut Self` so calls
82    /// can chain.
83    pub fn update(&mut self, data: &[u8]) -> &mut Self {
84        self.inner.update(data);
85        self
86    }
87
88    /// Finalise the hash and return the 32-byte digest. Consumes the hasher.
89    #[must_use]
90    pub fn finalize(self) -> [u8; SHA256_OUTPUT_LEN] {
91        self.inner.finalize().into()
92    }
93}
94
95/// Streaming SHA-512 hasher for inputs that don't fit in memory.
96///
97/// # Example
98///
99/// ```
100/// # #[cfg(feature = "hash-sha2")] {
101/// use crypt_io::hash::Sha512Hasher;
102///
103/// let mut h = Sha512Hasher::new();
104/// h.update(b"first ");
105/// h.update(b"second");
106/// let d = h.finalize();
107/// assert_eq!(d.len(), 64);
108/// # }
109/// ```
110#[derive(Debug, Clone, Default)]
111pub struct Sha512Hasher {
112    inner: Sha512,
113}
114
115impl Sha512Hasher {
116    /// Construct a fresh hasher.
117    #[must_use]
118    pub fn new() -> Self {
119        Self {
120            inner: Sha512::new(),
121        }
122    }
123
124    /// Absorb `data` into the running hash. Returns `&mut Self` so calls
125    /// can chain.
126    pub fn update(&mut self, data: &[u8]) -> &mut Self {
127        self.inner.update(data);
128        self
129    }
130
131    /// Finalise the hash and return the 64-byte digest. Consumes the hasher.
132    #[must_use]
133    pub fn finalize(self) -> [u8; SHA512_OUTPUT_LEN] {
134        self.inner.finalize().into()
135    }
136}
137
138#[cfg(test)]
139#[allow(clippy::unwrap_used, clippy::expect_used, unused_results)]
140mod tests {
141    use super::*;
142
143    fn hex_to_bytes(s: &str) -> alloc::vec::Vec<u8> {
144        hex::decode(s).expect("valid hex")
145    }
146
147    // --- SHA-256 known-answer tests (FIPS 180-4 Appendix B / RFC 6234). ---
148
149    /// FIPS 180-4 B.1: SHA-256("abc")
150    #[test]
151    fn sha256_kat_abc() {
152        let expected =
153            hex_to_bytes("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
154        assert_eq!(&sha256(b"abc")[..], &expected[..]);
155    }
156
157    /// FIPS 180-4 B.2: SHA-256 over the 56-byte
158    /// "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" input.
159    #[test]
160    fn sha256_kat_two_block() {
161        let input = b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
162        let expected =
163            hex_to_bytes("248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1");
164        assert_eq!(&sha256(input)[..], &expected[..]);
165    }
166
167    /// FIPS 180-4 also covers the empty-input vector.
168    #[test]
169    fn sha256_kat_empty() {
170        let expected =
171            hex_to_bytes("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
172        assert_eq!(&sha256(b"")[..], &expected[..]);
173    }
174
175    // --- SHA-512 known-answer tests (FIPS 180-4 Appendix C). ---
176
177    /// FIPS 180-4 C.1: SHA-512("abc")
178    #[test]
179    fn sha512_kat_abc() {
180        let expected = hex_to_bytes(
181            "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a\
182             2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
183        );
184        assert_eq!(&sha512(b"abc")[..], &expected[..]);
185    }
186
187    /// FIPS 180-4 C.2: SHA-512 over the 112-byte
188    /// "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu".
189    #[test]
190    fn sha512_kat_two_block() {
191        let input = b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu";
192        let expected = hex_to_bytes(
193            "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018\
194             501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909",
195        );
196        assert_eq!(&sha512(input)[..], &expected[..]);
197    }
198
199    #[test]
200    fn sha512_kat_empty() {
201        let expected = hex_to_bytes(
202            "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce\
203             47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
204        );
205        assert_eq!(&sha512(b"")[..], &expected[..]);
206    }
207
208    // --- Streaming-equivalence tests for both algorithms. ---
209
210    #[test]
211    fn sha256_streaming_equals_one_shot() {
212        let data = b"the quick brown fox jumps over the lazy dog";
213        let one_shot = sha256(data);
214        let mut h = Sha256Hasher::new();
215        h.update(&data[..10]);
216        h.update(&data[10..25]);
217        h.update(&data[25..]);
218        assert_eq!(h.finalize(), one_shot);
219    }
220
221    #[test]
222    fn sha512_streaming_equals_one_shot() {
223        let data = b"the quick brown fox jumps over the lazy dog";
224        let one_shot = sha512(data);
225        let mut h = Sha512Hasher::new();
226        h.update(&data[..10]);
227        h.update(&data[10..25]);
228        h.update(&data[25..]);
229        assert_eq!(h.finalize(), one_shot);
230    }
231
232    #[test]
233    fn sha256_streaming_chain_returns_self() {
234        let mut h = Sha256Hasher::new();
235        h.update(b"chain").update(b"-friendly");
236        assert_eq!(h.finalize(), sha256(b"chain-friendly"));
237    }
238
239    #[test]
240    fn sha512_streaming_chain_returns_self() {
241        let mut h = Sha512Hasher::new();
242        h.update(b"chain").update(b"-friendly");
243        assert_eq!(h.finalize(), sha512(b"chain-friendly"));
244    }
245
246    #[test]
247    fn sha256_empty_input_through_streaming() {
248        let h = Sha256Hasher::new();
249        assert_eq!(h.finalize(), sha256(b""));
250    }
251
252    #[test]
253    fn sha512_empty_input_through_streaming() {
254        let h = Sha512Hasher::new();
255        assert_eq!(h.finalize(), sha512(b""));
256    }
257}