Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_hash/src/sha256.rs

10.7 KiB, 24 runs

created by r1870400018:16361, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! A self-contained SHA-256 implementation, per FIPS 180-4.
2//!
3//! This exists because the Web Crypto API offers no SHA3, so a digest agreed between a browser
4//! and a Hematite server must be one of the SHA-2 family.
5//!
6//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
7//! Anthropic Claude
8
9use oxedyne_fe2o3_core::prelude::*;
10
11// The round constants: the first thirty two bits of the fractional parts of the cube roots of
12// the first sixty four primes.
13const K: [u32; 64] = [
14 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
15 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
16 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
17 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
18 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
19 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
20 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
21 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
22 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
23 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
24 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
25 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
26 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
27 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
28 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
29 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
30];
31
32// The initial hash value: the first thirty two bits of the fractional parts of the square roots
33// of the first eight primes.
34const H0: [u32; 8] = [
35 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
36 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
37];
38
39const BLOCK_LEN: usize = 64; // bytes
40pub const DIGEST_LEN: usize = 32; // bytes
41
42/// An incremental SHA-256 hasher, which buffers input until a full block is available.
43#[derive(Clone, Debug)]
44pub struct Sha256 {
45 state: [u32; 8], // running chaining value
46 buf: [u8; BLOCK_LEN], // partial block awaiting compression
47 buflen: usize, // bytes currently held in buf
48 total: u64, // message length, for the length padding
49}
50
51impl Default for Sha256 {
52 fn default() -> Self {
53 Self {
54 state: H0,
55 buf: [0u8; BLOCK_LEN],
56 buflen: 0,
57 total: 0,
58 }
59 }
60}
61
62impl Sha256 {
63
64 /// Creates a hasher primed with the FIPS 180-4 initial hash value.
65 pub fn new() -> Self {
66 Self::default()
67 }
68
69 /// Absorbs a further slice of the message.
70 pub fn update(&mut self, mut data: &[u8]) {
71 self.total = self.total.wrapping_add(data.len() as u64);
72 // Top up any partial block first.
73 if self.buflen > 0 {
74 let take = std::cmp::min(BLOCK_LEN - self.buflen, data.len());
75 self.buf[self.buflen..self.buflen + take].copy_from_slice(&data[..take]);
76 self.buflen += take;
77 data = &data[take..];
78 if self.buflen == BLOCK_LEN {
79 let block = self.buf;
80 self.compress(&block);
81 self.buflen = 0;
82 }
83 }
84 // Consume whole blocks directly from the input.
85 while data.len() >= BLOCK_LEN {
86 let (block, rest) = data.split_at(BLOCK_LEN);
87 let mut chunk = [0u8; BLOCK_LEN];
88 chunk.copy_from_slice(block);
89 self.compress(&chunk);
90 data = rest;
91 }
92 // Retain the remainder.
93 if !data.is_empty() {
94 self.buf[..data.len()].copy_from_slice(data);
95 self.buflen = data.len();
96 }
97 }
98
99 /// Applies the padding and returns the final digest.
100 pub fn finish(mut self) -> [u8; DIGEST_LEN] {
101 let bitlen = self.total.wrapping_mul(8);
102 // A single one bit, then zeroes. When fewer than eight bytes remain after the one bit the
103 // length spills into a further block, which is the case naive implementations get wrong.
104 let mut pad = [0u8; 2 * BLOCK_LEN];
105 pad[0] = 0x80;
106 let padlen = if self.buflen < 56 {
107 56 - self.buflen
108 } else {
109 120 - self.buflen
110 };
111 pad[padlen..padlen + 8].copy_from_slice(&bitlen.to_be_bytes());
112 // `total` is deliberately not corrected here, the hasher being consumed.
113 self.update_unlogged(&pad[..padlen + 8]);
114
115 let mut out = [0u8; DIGEST_LEN];
116 for (i, word) in self.state.iter().enumerate() {
117 out[4 * i..4 * i + 4].copy_from_slice(&word.to_be_bytes());
118 }
119 out
120 }
121
122 /// Absorbs padding without counting it toward the message length.
123 fn update_unlogged(&mut self, data: &[u8]) {
124 let before = self.total;
125 self.update(data);
126 self.total = before;
127 }
128
129 /// Applies the compression function to one sixty four byte block.
130 fn compress(&mut self, block: &[u8; BLOCK_LEN]) {
131 let mut w = [0u32; 64];
132 for i in 0..16 {
133 w[i] = u32::from_be_bytes([
134 block[4 * i],
135 block[4 * i + 1],
136 block[4 * i + 2],
137 block[4 * i + 3],
138 ]);
139 }
140 for i in 16..64 {
141 let s0 = w[i - 15].rotate_right(7)
142 ^ w[i - 15].rotate_right(18)
143 ^ (w[i - 15] >> 3);
144 let s1 = w[i - 2].rotate_right(17)
145 ^ w[i - 2].rotate_right(19)
146 ^ (w[i - 2] >> 10);
147 w[i] = w[i - 16]
148 .wrapping_add(s0)
149 .wrapping_add(w[i - 7])
150 .wrapping_add(s1);
151 }
152
153 let mut a = self.state[0];
154 let mut b = self.state[1];
155 let mut c = self.state[2];
156 let mut d = self.state[3];
157 let mut e = self.state[4];
158 let mut f = self.state[5];
159 let mut g = self.state[6];
160 let mut h = self.state[7];
161
162 for i in 0..64 {
163 let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
164 let ch = (e & f) ^ ((!e) & g);
165 let temp1 = h
166 .wrapping_add(s1)
167 .wrapping_add(ch)
168 .wrapping_add(K[i])
169 .wrapping_add(w[i]);
170 let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
171 let maj = (a & b) ^ (a & c) ^ (b & c);
172 let temp2 = s0.wrapping_add(maj);
173
174 h = g;
175 g = f;
176 f = e;
177 e = d.wrapping_add(temp1);
178 d = c;
179 c = b;
180 b = a;
181 a = temp1.wrapping_add(temp2);
182 }
183
184 self.state[0] = self.state[0].wrapping_add(a);
185 self.state[1] = self.state[1].wrapping_add(b);
186 self.state[2] = self.state[2].wrapping_add(c);
187 self.state[3] = self.state[3].wrapping_add(d);
188 self.state[4] = self.state[4].wrapping_add(e);
189 self.state[5] = self.state[5].wrapping_add(f);
190 self.state[6] = self.state[6].wrapping_add(g);
191 self.state[7] = self.state[7].wrapping_add(h);
192 }
193}
194
195pub fn digest(msg: &[u8]) -> [u8; DIGEST_LEN] {
196 let mut hasher = Sha256::new();
197 hasher.update(msg);
198 hasher.finish()
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 use oxedyne_fe2o3_core::{
206 byte::B32,
207 string::ToHexString,
208 };
209
210 /// Renders a digest as lower case hexadecimal, for comparison with the published vectors.
211 fn hex(d: [u8; DIGEST_LEN]) -> String {
212 B32(d).to_hex_string()
213 }
214
215 /// The vectors published with FIPS 180-4 and in the NIST byte oriented test vector set.
216 #[test]
217 fn test_sha256_fips_180_4_vectors() -> Outcome<()> {
218 let vectors: [(&[u8], &str); 3] = [
219 (
220 b"",
221 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
222 ),
223 (
224 b"abc",
225 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
226 ),
227 (
228 b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
229 "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1",
230 ),
231 ];
232 for (msg, expected) in vectors {
233 let d = hex(digest(msg));
234 req!(d, expected.to_string(), "SHA-256 of {:?}", msg);
235 }
236 Ok(())
237 }
238
239 /// The two block vector from FIPS 180-4, which exercises the message schedule across a block
240 /// boundary.
241 #[test]
242 fn test_sha256_multi_block() -> Outcome<()> {
243 let msg: &[u8] =
244 b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu";
245 let d = hex(digest(msg));
246 req!(d, "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1".to_string());
247 Ok(())
248 }
249
250 /// Lengths either side of the padding boundary, where the length field either fits in the
251 /// final block or forces an extra one. These are the cases that break naive padding.
252 #[test]
253 fn test_sha256_padding_boundaries() -> Outcome<()> {
254 // Digests of 55, 56, 63, 64 and 65 repetitions of 'a'.
255 let vectors: [(usize, &str); 5] = [
256 (55, "9f4390f8d30c2dd92ec9f095b65e2b9ae9b0a925a5258e241c9f1e910f734318"),
257 (56, "b35439a4ac6f0948b6d6f9e3c6af0f5f590ce20f1bde7090ef7970686ec6738a"),
258 (63, "7d3e74a05d7db15bce4ad9ec0658ea98e3f06eeecf16b4c6fff2da457ddc2f34"),
259 (64, "ffe054fe7ae0cb6dc65c3af9b61d5209f439851db43d0ba5997337df154668eb"),
260 (65, "635361c48bb9eab14198e76ea8ab7f1a41685d6ad62aa9146d301d4f17eb0ae0"),
261 ];
262 for (n, expected) in vectors {
263 let msg = vec![b'a'; n];
264 let d = hex(digest(&msg));
265 req!(d, expected.to_string(), "SHA-256 of {} 'a's", n);
266 }
267 Ok(())
268 }
269
270 /// Streaming in awkwardly sized pieces must agree with hashing in one go, which is what the
271 /// `Hasher` impl relies on when it absorbs several input slices.
272 #[test]
273 fn test_sha256_incremental_matches_oneshot() -> Outcome<()> {
274 let msg = vec![b'x'; 1000];
275 for chunk in [1usize, 7, 31, 63, 64, 65, 127] {
276 let mut hasher = Sha256::new();
277 for piece in msg.chunks(chunk) {
278 hasher.update(piece);
279 }
280 // `req!` renders its arguments again on failure, so the digests are bound first.
281 let streamed = hex(hasher.finish());
282 let oneshot = hex(digest(&msg));
283 req!(streamed, oneshot, "chunk size {}", chunk);
284 }
285 Ok(())
286 }
287
288 /// The one million 'a' vector from FIPS 180-4, ignored by default only for its cost.
289 #[test]
290 #[ignore]
291 fn test_sha256_million_a() -> Outcome<()> {
292 let mut hasher = Sha256::new();
293 let block = [b'a'; 1000];
294 for _ in 0..1000 {
295 hasher.update(&block);
296 }
297 let d = hex(hasher.finish());
298 req!(d, "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0".to_string());
299 Ok(())
300 }
301}