1const K: [u32; 64] = [
25 0x428a_2f98,
26 0x7137_4491,
27 0xb5c0_fbcf,
28 0xe9b5_dba5,
29 0x3956_c25b,
30 0x59f1_11f1,
31 0x923f_82a4,
32 0xab1c_5ed5,
33 0xd807_aa98,
34 0x1283_5b01,
35 0x2431_85be,
36 0x550c_7dc3,
37 0x72be_5d74,
38 0x80de_b1fe,
39 0x9bdc_06a7,
40 0xc19b_f174,
41 0xe49b_69c1,
42 0xefbe_4786,
43 0x0fc1_9dc6,
44 0x240c_a1cc,
45 0x2de9_2c6f,
46 0x4a74_84aa,
47 0x5cb0_a9dc,
48 0x76f9_88da,
49 0x983e_5152,
50 0xa831_c66d,
51 0xb003_27c8,
52 0xbf59_7fc7,
53 0xc6e0_0bf3,
54 0xd5a7_9147,
55 0x06ca_6351,
56 0x1429_2967,
57 0x27b7_0a85,
58 0x2e1b_2138,
59 0x4d2c_6dfc,
60 0x5338_0d13,
61 0x650a_7354,
62 0x766a_0abb,
63 0x81c2_c92e,
64 0x9272_2c85,
65 0xa2bf_e8a1,
66 0xa81a_664b,
67 0xc24b_8b70,
68 0xc76c_51a3,
69 0xd192_e819,
70 0xd699_0624,
71 0xf40e_3585,
72 0x106a_a070,
73 0x19a4_c116,
74 0x1e37_6c08,
75 0x2748_774c,
76 0x34b0_bcb5,
77 0x391c_0cb3,
78 0x4ed8_aa4a,
79 0x5b9c_ca4f,
80 0x682e_6ff3,
81 0x748f_82ee,
82 0x78a5_636f,
83 0x84c8_7814,
84 0x8cc7_0208,
85 0x90be_fffa,
86 0xa450_6ceb,
87 0xbef9_a3f7,
88 0xc671_78f2,
89];
90
91const H0: [u32; 8] = [
94 0x6a09_e667,
95 0xbb67_ae85,
96 0x3c6e_f372,
97 0xa54f_f53a,
98 0x510e_527f,
99 0x9b05_688c,
100 0x1f83_d9ab,
101 0x5be0_cd19,
102];
103
104#[derive(Clone, Debug)]
109pub struct Sha256 {
110 state: [u32; 8],
111 buffer: [u8; 64],
113 buffered: usize,
115 length: u64,
117}
118
119impl Default for Sha256 {
120 fn default() -> Self {
121 Self::new()
122 }
123}
124
125impl Sha256 {
126 #[must_use]
128 pub const fn new() -> Self {
129 Self {
130 state: H0,
131 buffer: [0; 64],
132 buffered: 0,
133 length: 0,
134 }
135 }
136
137 pub fn update(&mut self, mut input: &[u8]) {
139 self.length = self.length.wrapping_add(input.len() as u64);
140
141 if self.buffered > 0 {
142 let want = 64 - self.buffered;
143 let take = want.min(input.len());
144 self.buffer[self.buffered..self.buffered + take].copy_from_slice(&input[..take]);
145 self.buffered += take;
146 input = &input[take..];
147 if self.buffered == 64 {
148 let block = self.buffer;
149 self.compress(&block);
150 self.buffered = 0;
151 } else {
152 return;
153 }
154 }
155
156 let (blocks, tail) = input.as_chunks::<64>();
157 for block in blocks {
158 self.compress(block);
159 }
160
161 self.buffer[..tail.len()].copy_from_slice(tail);
162 self.buffered = tail.len();
163 }
164
165 #[must_use]
167 pub fn finish(mut self) -> [u8; 32] {
168 let bit_length = self.length.wrapping_mul(8);
170 self.update_no_count(&[0x80]);
171 while self.buffered != 56 {
172 self.update_no_count(&[0x00]);
173 }
174 self.update_no_count(&bit_length.to_be_bytes());
175
176 let mut out = [0_u8; 32];
177 for (chunk, word) in out.as_chunks_mut::<4>().0.iter_mut().zip(self.state) {
178 *chunk = word.to_be_bytes();
179 }
180 out
181 }
182
183 fn update_no_count(&mut self, input: &[u8]) {
185 for &byte in input {
186 self.buffer[self.buffered] = byte;
187 self.buffered += 1;
188 if self.buffered == 64 {
189 let block = self.buffer;
190 self.compress(&block);
191 self.buffered = 0;
192 }
193 }
194 }
195
196 fn compress(&mut self, block: &[u8; 64]) {
198 let mut w = [0_u32; 64];
199 for (slot, chunk) in w.iter_mut().zip(block.as_chunks::<4>().0) {
200 *slot = u32::from_be_bytes(*chunk);
201 }
202 for index in 16..64 {
203 let s0 = w[index - 15].rotate_right(7)
204 ^ w[index - 15].rotate_right(18)
205 ^ (w[index - 15] >> 3);
206 let s1 = w[index - 2].rotate_right(17)
207 ^ w[index - 2].rotate_right(19)
208 ^ (w[index - 2] >> 10);
209 w[index] = w[index - 16]
210 .wrapping_add(s0)
211 .wrapping_add(w[index - 7])
212 .wrapping_add(s1);
213 }
214
215 let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = self.state;
216
217 for index in 0..64 {
218 let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
219 let ch = (e & f) ^ ((!e) & g);
220 let temp1 = h
221 .wrapping_add(s1)
222 .wrapping_add(ch)
223 .wrapping_add(K[index])
224 .wrapping_add(w[index]);
225 let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
226 let maj = (a & b) ^ (a & c) ^ (b & c);
227 let temp2 = s0.wrapping_add(maj);
228
229 h = g;
230 g = f;
231 f = e;
232 e = d.wrapping_add(temp1);
233 d = c;
234 c = b;
235 b = a;
236 a = temp1.wrapping_add(temp2);
237 }
238
239 for (slot, value) in self.state.iter_mut().zip([a, b, c, d, e, f, g, h]) {
240 *slot = slot.wrapping_add(value);
241 }
242 }
243}
244
245pub fn hex_digest_file(path: &std::path::Path) -> std::io::Result<String> {
255 use std::io::Read as _;
256
257 let mut file = std::fs::File::open(path)?;
258 let mut hasher = Sha256::new();
259 let mut buffer = vec![0_u8; 1 << 20];
261 loop {
262 match file.read(&mut buffer) {
263 Ok(0) => break,
264 Ok(read) => hasher.update(&buffer[..read]),
265 Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {}
266 Err(error) => return Err(error),
267 }
268 }
269 Ok(to_hex(&hasher.finish()))
270}
271
272#[must_use]
274pub fn hex_digest(bytes: &[u8]) -> String {
275 let mut hasher = Sha256::new();
276 hasher.update(bytes);
277 to_hex(&hasher.finish())
278}
279
280#[must_use]
282pub fn to_hex(digest: &[u8; 32]) -> String {
283 let mut out = String::with_capacity(64);
284 for byte in digest {
285 const HEX: &[u8; 16] = b"0123456789abcdef";
288 out.push(HEX[usize::from(byte >> 4)] as char);
289 out.push(HEX[usize::from(byte & 0x0f)] as char);
290 }
291 out
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 #[test]
304 fn matches_the_published_nist_vectors() {
305 assert_eq!(
306 hex_digest(b""),
307 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
308 );
309 assert_eq!(
310 hex_digest(b"abc"),
311 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
312 );
313 assert_eq!(
314 hex_digest(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),
315 "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
316 );
317 assert_eq!(
318 hex_digest(
319 b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"
320 ),
321 "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1"
322 );
323 let mut hasher = Sha256::new();
325 for _ in 0..1000 {
326 hasher.update(&[b'a'; 1000]);
327 }
328 assert_eq!(
329 to_hex(&hasher.finish()),
330 "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"
331 );
332 }
333
334 #[test]
339 fn streaming_in_any_chunking_matches_one_shot() {
340 let message: Vec<u8> = (0..1000_u32).map(|i| (i % 251) as u8).collect();
341 let expected = hex_digest(&message);
342 for chunk_size in [1_usize, 2, 7, 31, 63, 64, 65, 127, 128, 999, 1000] {
343 let mut hasher = Sha256::new();
344 for chunk in message.chunks(chunk_size) {
345 hasher.update(chunk);
346 }
347 assert_eq!(
348 to_hex(&hasher.finish()),
349 expected,
350 "digest changed with chunk size {chunk_size}"
351 );
352 }
353 }
354
355 #[test]
358 fn file_digest_matches_the_in_memory_digest() {
359 let dir = std::env::temp_dir().join(format!("ftts-sha256-file-{}", std::process::id()));
360 std::fs::create_dir_all(&dir).expect("temp dir");
361 let path = dir.join("digest-input.bin");
362 let message: Vec<u8> = (0..70_000_u32).map(|i| (i % 251) as u8).collect();
364 std::fs::write(&path, &message).expect("write temp file");
365 assert_eq!(
366 hex_digest_file(&path).expect("file digest"),
367 hex_digest(&message)
368 );
369 }
370
371 #[test]
373 fn a_single_bit_flip_changes_the_digest() {
374 let mut message = vec![0_u8; 256];
375 let clean = hex_digest(&message);
376 message[128] ^= 0x01;
377 assert_ne!(hex_digest(&message), clean);
378 }
379}