Skip to main content

arcana/hash/
sha256.rs

1//! SHA-256 hash function (FIPS 180-4).
2//!
3//! 256-bit output, 512-bit (64-byte) blocks, Merkle-Damgård
4//! construction with the standard `Ch` / `Maj` / `Σ₀` / `Σ₁` /
5//! `σ₀` / `σ₁` round mixers.
6//!
7//! # Side-channel posture
8//!
9//! As an *unkeyed* hash, SHA-256 is itself **not SCA-sensitive**;
10//! it has no secret input. The compression function is CT by
11//! construction — fixed-iteration loop, constant rotations / shifts,
12//! no table lookups indexed by secret-derived values.
13//!
14//! As a *keyed* primitive consumed by HMAC, RFC 6979 HMAC-DRBG,
15//! or RSA-PSS / OAEP MGF1, the same compression function carries
16//! a secret into its internal state and becomes vulnerable to
17//! the carry-based DPA result of `belenky2023_cdpa_hmac_sha2`
18//! (TCHES 2023/3) — any arithmetic-addition-based hash leaks the
19//! key in 30 K – 275 K traces. Roadmap item `T2-D` (see
20//! `arcana/doc/sca/countermeasures/hmac.rst`) ships a
21//! `MaskedSha256` variant behind the `sca-protected` feature.
22
23use crate::Hasher;
24
25const H0: [u32; 8] = [
26    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
27];
28
29const K: [u32; 64] = [
30    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98,
31    0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
32    0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8,
33    0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
34    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
35    0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
36    0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
37    0xc67178f2,
38];
39
40/// SHA-256 hasher (FIPS 180-4). 256-bit output, 64-byte blocks.
41///
42/// The workhorse hash of TLS 1.2 / 1.3, JWS, X.509 SHA-2 signatures,
43/// Bitcoin, and almost every other modern protocol that needs a
44/// 128-bit-security hash function.
45#[derive(Clone)]
46pub struct Sha256 {
47    state: [u32; 8],
48    buf: [u8; 64],
49    buf_len: usize,
50    total_len: u64,
51}
52
53fn compress(state: &mut [u32; 8], block: &[u8]) {
54    let mut w = [0u32; 64];
55    for i in 0..16 {
56        w[i] = u32::from_be_bytes([block[4 * i], block[4 * i + 1], block[4 * i + 2], block[4 * i + 3]]);
57    }
58    for i in 16..64 {
59        let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
60        let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
61        w[i] = w[i - 16].wrapping_add(s0).wrapping_add(w[i - 7]).wrapping_add(s1);
62    }
63
64    let mut a = state[0];
65    let mut b = state[1];
66    let mut c = state[2];
67    let mut d = state[3];
68    let mut e = state[4];
69    let mut f = state[5];
70    let mut g = state[6];
71    let mut h = state[7];
72
73    for i in 0..64 {
74        let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
75        let ch = (e & f) ^ ((!e) & g);
76        let temp1 = h
77            .wrapping_add(s1)
78            .wrapping_add(ch)
79            .wrapping_add(K[i])
80            .wrapping_add(w[i]);
81        let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
82        let maj = (a & b) ^ (a & c) ^ (b & c);
83        let temp2 = s0.wrapping_add(maj);
84
85        h = g;
86        g = f;
87        f = e;
88        e = d.wrapping_add(temp1);
89        d = c;
90        c = b;
91        b = a;
92        a = temp1.wrapping_add(temp2);
93    }
94
95    state[0] = state[0].wrapping_add(a);
96    state[1] = state[1].wrapping_add(b);
97    state[2] = state[2].wrapping_add(c);
98    state[3] = state[3].wrapping_add(d);
99    state[4] = state[4].wrapping_add(e);
100    state[5] = state[5].wrapping_add(f);
101    state[6] = state[6].wrapping_add(g);
102    state[7] = state[7].wrapping_add(h);
103}
104
105impl Sha256 {
106    pub(crate) fn new_with_iv(iv: [u32; 8]) -> Self {
107        Self {
108            state: iv,
109            buf: [0u8; 64],
110            buf_len: 0,
111            total_len: 0,
112        }
113    }
114}
115
116impl Hasher for Sha256 {
117    const OUTPUT_LEN: usize = 32;
118    const BLOCK_LEN: usize = 64;
119
120    fn new() -> Self {
121        Self {
122            state: H0,
123            buf: [0u8; 64],
124            buf_len: 0,
125            total_len: 0,
126        }
127    }
128
129    fn update(&mut self, data: &[u8]) {
130        let mut pos = 0;
131        self.total_len += data.len() as u64;
132
133        if self.buf_len > 0 {
134            let need = 64 - self.buf_len;
135            let take = need.min(data.len());
136            self.buf[self.buf_len..self.buf_len + take].copy_from_slice(&data[..take]);
137            self.buf_len += take;
138            pos = take;
139            if self.buf_len == 64 {
140                let block = self.buf;
141                compress(&mut self.state, &block);
142                self.buf_len = 0;
143            }
144        }
145
146        while pos + 64 <= data.len() {
147            compress(&mut self.state, &data[pos..pos + 64]);
148            pos += 64;
149        }
150
151        if pos < data.len() {
152            let remaining = data.len() - pos;
153            self.buf[..remaining].copy_from_slice(&data[pos..]);
154            self.buf_len = remaining;
155        }
156    }
157
158    fn finalize(self) -> Vec<u8> {
159        let mut out = vec![0u8; 32];
160        self.finalize_into(&mut out);
161        out
162    }
163
164    fn finalize_into(mut self, out: &mut [u8]) {
165        let bit_len = self.total_len * 8;
166        let mut pad = [0u8; 72];
167        pad[0] = 0x80;
168        let pad_len = if self.buf_len < 56 {
169            56 - self.buf_len
170        } else {
171            120 - self.buf_len
172        };
173        self.update(&pad[..pad_len]);
174        self.update(&bit_len.to_be_bytes());
175
176        for (i, word) in self.state.iter().enumerate() {
177            let bytes = word.to_be_bytes();
178            let start = i * 4;
179            if start + 4 <= out.len() {
180                out[start..start + 4].copy_from_slice(&bytes);
181            } else if start < out.len() {
182                let end = out.len() - start;
183                out[start..].copy_from_slice(&bytes[..end]);
184            }
185        }
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192    use crate::Hasher;
193
194    #[test]
195    fn test_sha256_empty() {
196        let digest = Sha256::hash(b"");
197        let expected: [u8; 32] = [
198            0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae,
199            0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55,
200        ];
201        assert_eq!(&digest[..], &expected[..]);
202    }
203
204    #[test]
205    fn test_sha256_abc() {
206        let digest = Sha256::hash(b"abc");
207        let expected: [u8; 32] = [
208            0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03,
209            0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad,
210        ];
211        assert_eq!(&digest[..], &expected[..]);
212    }
213}