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