//! SHA-256 digests for export-set identity (no external digest crate). #![allow(clippy::unreadable_literal)] const K: [u32; 64] = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, ]; #[derive(Clone)] pub struct Sha256 { state: [u32; 8], buffer: [u8; 64], buffer_len: usize, bit_len: u64, } impl Default for Sha256 { fn default() -> Self { Self::new() } } impl Sha256 { pub fn new() -> Self { Self { state: [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, ], buffer: [0; 64], buffer_len: 0, bit_len: 0, } } pub fn update(&mut self, data: &[u8]) { let mut offset = 0; if self.buffer_len > 0 { let need = 64 - self.buffer_len; let take = need.min(data.len()); self.buffer[self.buffer_len..self.buffer_len + take].copy_from_slice(&data[..take]); self.buffer_len += take; offset += take; if self.buffer_len == 64 { let block = self.buffer; self.compress(&block); self.bit_len = self.bit_len.saturating_add(512); self.buffer_len = 0; } } while offset + 64 <= data.len() { let mut block = [0u8; 64]; block.copy_from_slice(&data[offset..offset + 64]); self.compress(&block); self.bit_len = self.bit_len.saturating_add(512); offset += 64; } let rest = &data[offset..]; if !rest.is_empty() { self.buffer[..rest.len()].copy_from_slice(rest); self.buffer_len = rest.len(); } } pub fn finalize(mut self) -> [u8; 32] { let total_bits = self .bit_len .saturating_add((self.buffer_len as u64).saturating_mul(8)); self.buffer[self.buffer_len] = 0x80; self.buffer_len += 1; if self.buffer_len > 56 { for b in &mut self.buffer[self.buffer_len..] { *b = 0; } let block = self.buffer; self.compress(&block); self.buffer = [0; 64]; self.buffer_len = 0; } for b in &mut self.buffer[self.buffer_len..56] { *b = 0; } self.buffer[56..64].copy_from_slice(&total_bits.to_be_bytes()); let block = self.buffer; self.compress(&block); let mut out = [0u8; 32]; for (i, word) in self.state.iter().enumerate() { out[i * 4..(i + 1) * 4].copy_from_slice(&word.to_be_bytes()); } out } fn compress(&mut self, block: &[u8; 64]) { let mut w = [0u32; 64]; for (i, chunk) in block.chunks_exact(4).enumerate().take(16) { w[i] = u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]); } for i in 16..64 { let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3); let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10); w[i] = w[i - 16] .wrapping_add(s0) .wrapping_add(w[i - 7]) .wrapping_add(s1); } let mut a = self.state[0]; let mut b = self.state[1]; let mut c = self.state[2]; let mut d = self.state[3]; let mut e = self.state[4]; let mut f = self.state[5]; let mut g = self.state[6]; let mut h = self.state[7]; for i in 0..64 { let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25); let ch = (e & f) ^ ((!e) & g); let temp1 = h .wrapping_add(s1) .wrapping_add(ch) .wrapping_add(K[i]) .wrapping_add(w[i]); let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22); let maj = (a & b) ^ (a & c) ^ (b & c); let temp2 = s0.wrapping_add(maj); h = g; g = f; f = e; e = d.wrapping_add(temp1); d = c; c = b; b = a; a = temp1.wrapping_add(temp2); } self.state[0] = self.state[0].wrapping_add(a); self.state[1] = self.state[1].wrapping_add(b); self.state[2] = self.state[2].wrapping_add(c); self.state[3] = self.state[3].wrapping_add(d); self.state[4] = self.state[4].wrapping_add(e); self.state[5] = self.state[5].wrapping_add(f); self.state[6] = self.state[6].wrapping_add(g); self.state[7] = self.state[7].wrapping_add(h); } } pub fn sha256(data: &[u8]) -> [u8; 32] { let mut h = Sha256::new(); h.update(data); h.finalize() } pub fn sha256_hex(data: &[u8]) -> String { hex(&sha256(data)) } pub fn hex(bytes: &[u8]) -> String { const TABLE: &[u8; 16] = b"0123456789abcdef"; let mut out = String::with_capacity(bytes.len() * 2); for &b in bytes { out.push(TABLE[(b >> 4) as usize] as char); out.push(TABLE[(b & 0xf) as usize] as char); } out } pub fn is_sha256_hex(value: &str) -> bool { value.len() == 64 && value .bytes() .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)) } /// Stream a regular file through SHA-256 with an explicit size ceiling. pub fn sha256_file(path: &std::path::Path, max_bytes: u64) -> Result { use std::io::Read; let meta = std::fs::metadata(path).map_err(|e| e.to_string())?; if !meta.is_file() { return Err("bronbestand is geen regulier bestand".into()); } if meta.len() > max_bytes { return Err(format!("bronbestand groter dan {max_bytes} bytes")); } let mut file = std::fs::File::open(path).map_err(|e| e.to_string())?; let mut hasher = Sha256::new(); let mut buf = [0u8; 64 * 1024]; let mut total = 0u64; loop { let n = file.read(&mut buf).map_err(|e| e.to_string())?; if n == 0 { break; } total = total.saturating_add(n as u64); if total > max_bytes { return Err(format!("bronbestand groter dan {max_bytes} bytes")); } hasher.update(&buf[..n]); } Ok(hex(&hasher.finalize())) } #[cfg(test)] mod tests { use super::*; #[test] fn known_vectors() { assert_eq!( sha256_hex(b""), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ); assert_eq!( sha256_hex(b"abc"), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" ); assert_eq!( sha256_hex(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" ); } #[test] fn streaming_matches_one_shot() { let data: Vec = (0..300).map(|i| i as u8).collect(); let mut h = Sha256::new(); h.update(&data[..100]); h.update(&data[100..200]); h.update(&data[200..]); assert_eq!(hex(&h.finalize()), sha256_hex(&data)); } #[test] fn hex_validator() { assert!(is_sha256_hex(&sha256_hex(b"x"))); assert!(!is_sha256_hex("abc")); assert!(!is_sha256_hex(&"g".repeat(64))); } }