| 1 | //! SHA-256 digests for export-set identity (no external digest crate). |
| 2 | #![allow(clippy::unreadable_literal)] |
| 3 | |
| 4 | const K: [u32; 64] = [ |
| 5 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, |
| 6 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, |
| 7 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, |
| 8 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, |
| 9 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, |
| 10 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, |
| 11 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, |
| 12 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, |
| 13 | ]; |
| 14 | |
| 15 | #[derive(Clone)] |
| 16 | pub struct Sha256 { |
| 17 | state: [u32; 8], |
| 18 | buffer: [u8; 64], |
| 19 | buffer_len: usize, |
| 20 | bit_len: u64, |
| 21 | } |
| 22 | |
| 23 | impl Default for Sha256 { |
| 24 | fn default() -> Self { |
| 25 | Self::new() |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | impl Sha256 { |
| 30 | pub fn new() -> Self { |
| 31 | Self { |
| 32 | state: [ |
| 33 | 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, |
| 34 | 0x5be0cd19, |
| 35 | ], |
| 36 | buffer: [0; 64], |
| 37 | buffer_len: 0, |
| 38 | bit_len: 0, |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | pub fn update(&mut self, data: &[u8]) { |
| 43 | let mut offset = 0; |
| 44 | if self.buffer_len > 0 { |
| 45 | let need = 64 - self.buffer_len; |
| 46 | let take = need.min(data.len()); |
| 47 | self.buffer[self.buffer_len..self.buffer_len + take].copy_from_slice(&data[..take]); |
| 48 | self.buffer_len += take; |
| 49 | offset += take; |
| 50 | if self.buffer_len == 64 { |
| 51 | let block = self.buffer; |
| 52 | self.compress(&block); |
| 53 | self.bit_len = self.bit_len.saturating_add(512); |
| 54 | self.buffer_len = 0; |
| 55 | } |
| 56 | } |
| 57 | while offset + 64 <= data.len() { |
| 58 | let mut block = [0u8; 64]; |
| 59 | block.copy_from_slice(&data[offset..offset + 64]); |
| 60 | self.compress(&block); |
| 61 | self.bit_len = self.bit_len.saturating_add(512); |
| 62 | offset += 64; |
| 63 | } |
| 64 | let rest = &data[offset..]; |
| 65 | if !rest.is_empty() { |
| 66 | self.buffer[..rest.len()].copy_from_slice(rest); |
| 67 | self.buffer_len = rest.len(); |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | pub fn finalize(mut self) -> [u8; 32] { |
| 72 | let total_bits = self |
| 73 | .bit_len |
| 74 | .saturating_add((self.buffer_len as u64).saturating_mul(8)); |
| 75 | self.buffer[self.buffer_len] = 0x80; |
| 76 | self.buffer_len += 1; |
| 77 | if self.buffer_len > 56 { |
| 78 | for b in &mut self.buffer[self.buffer_len..] { |
| 79 | *b = 0; |
| 80 | } |
| 81 | let block = self.buffer; |
| 82 | self.compress(&block); |
| 83 | self.buffer = [0; 64]; |
| 84 | self.buffer_len = 0; |
| 85 | } |
| 86 | for b in &mut self.buffer[self.buffer_len..56] { |
| 87 | *b = 0; |
| 88 | } |
| 89 | self.buffer[56..64].copy_from_slice(&total_bits.to_be_bytes()); |
| 90 | let block = self.buffer; |
| 91 | self.compress(&block); |
| 92 | let mut out = [0u8; 32]; |
| 93 | for (i, word) in self.state.iter().enumerate() { |
| 94 | out[i * 4..(i + 1) * 4].copy_from_slice(&word.to_be_bytes()); |
| 95 | } |
| 96 | out |
| 97 | } |
| 98 | |
| 99 | fn compress(&mut self, block: &[u8; 64]) { |
| 100 | let mut w = [0u32; 64]; |
| 101 | for (i, chunk) in block.chunks_exact(4).enumerate().take(16) { |
| 102 | w[i] = u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]); |
| 103 | } |
| 104 | for i in 16..64 { |
| 105 | let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3); |
| 106 | let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10); |
| 107 | w[i] = w[i - 16] |
| 108 | .wrapping_add(s0) |
| 109 | .wrapping_add(w[i - 7]) |
| 110 | .wrapping_add(s1); |
| 111 | } |
| 112 | let mut a = self.state[0]; |
| 113 | let mut b = self.state[1]; |
| 114 | let mut c = self.state[2]; |
| 115 | let mut d = self.state[3]; |
| 116 | let mut e = self.state[4]; |
| 117 | let mut f = self.state[5]; |
| 118 | let mut g = self.state[6]; |
| 119 | let mut h = self.state[7]; |
| 120 | for i in 0..64 { |
| 121 | let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25); |
| 122 | let ch = (e & f) ^ ((!e) & g); |
| 123 | let temp1 = h |
| 124 | .wrapping_add(s1) |
| 125 | .wrapping_add(ch) |
| 126 | .wrapping_add(K[i]) |
| 127 | .wrapping_add(w[i]); |
| 128 | let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22); |
| 129 | let maj = (a & b) ^ (a & c) ^ (b & c); |
| 130 | let temp2 = s0.wrapping_add(maj); |
| 131 | h = g; |
| 132 | g = f; |
| 133 | f = e; |
| 134 | e = d.wrapping_add(temp1); |
| 135 | d = c; |
| 136 | c = b; |
| 137 | b = a; |
| 138 | a = temp1.wrapping_add(temp2); |
| 139 | } |
| 140 | self.state[0] = self.state[0].wrapping_add(a); |
| 141 | self.state[1] = self.state[1].wrapping_add(b); |
| 142 | self.state[2] = self.state[2].wrapping_add(c); |
| 143 | self.state[3] = self.state[3].wrapping_add(d); |
| 144 | self.state[4] = self.state[4].wrapping_add(e); |
| 145 | self.state[5] = self.state[5].wrapping_add(f); |
| 146 | self.state[6] = self.state[6].wrapping_add(g); |
| 147 | self.state[7] = self.state[7].wrapping_add(h); |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | pub fn sha256(data: &[u8]) -> [u8; 32] { |
| 152 | let mut h = Sha256::new(); |
| 153 | h.update(data); |
| 154 | h.finalize() |
| 155 | } |
| 156 | |
| 157 | pub fn sha256_hex(data: &[u8]) -> String { |
| 158 | hex(&sha256(data)) |
| 159 | } |
| 160 | |
| 161 | pub fn hex(bytes: &[u8]) -> String { |
| 162 | const TABLE: &[u8; 16] = b"0123456789abcdef"; |
| 163 | let mut out = String::with_capacity(bytes.len() * 2); |
| 164 | for &b in bytes { |
| 165 | out.push(TABLE[(b >> 4) as usize] as char); |
| 166 | out.push(TABLE[(b & 0xf) as usize] as char); |
| 167 | } |
| 168 | out |
| 169 | } |
| 170 | |
| 171 | pub fn is_sha256_hex(value: &str) -> bool { |
| 172 | value.len() == 64 |
| 173 | && value |
| 174 | .bytes() |
| 175 | .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)) |
| 176 | } |
| 177 | |
| 178 | /// Stream a regular file through SHA-256 with an explicit size ceiling. |
| 179 | pub fn sha256_file(path: &std::path::Path, max_bytes: u64) -> Result<String, String> { |
| 180 | use std::io::Read; |
| 181 | let meta = std::fs::metadata(path).map_err(|e| e.to_string())?; |
| 182 | if !meta.is_file() { |
| 183 | return Err("bronbestand is geen regulier bestand".into()); |
| 184 | } |
| 185 | if meta.len() > max_bytes { |
| 186 | return Err(format!("bronbestand groter dan {max_bytes} bytes")); |
| 187 | } |
| 188 | let mut file = std::fs::File::open(path).map_err(|e| e.to_string())?; |
| 189 | let mut hasher = Sha256::new(); |
| 190 | let mut buf = [0u8; 64 * 1024]; |
| 191 | let mut total = 0u64; |
| 192 | loop { |
| 193 | let n = file.read(&mut buf).map_err(|e| e.to_string())?; |
| 194 | if n == 0 { |
| 195 | break; |
| 196 | } |
| 197 | total = total.saturating_add(n as u64); |
| 198 | if total > max_bytes { |
| 199 | return Err(format!("bronbestand groter dan {max_bytes} bytes")); |
| 200 | } |
| 201 | hasher.update(&buf[..n]); |
| 202 | } |
| 203 | Ok(hex(&hasher.finalize())) |
| 204 | } |
| 205 | |
| 206 | #[cfg(test)] |
| 207 | mod tests { |
| 208 | use super::*; |
| 209 | |
| 210 | #[test] |
| 211 | fn known_vectors() { |
| 212 | assert_eq!( |
| 213 | sha256_hex(b""), |
| 214 | "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" |
| 215 | ); |
| 216 | assert_eq!( |
| 217 | sha256_hex(b"abc"), |
| 218 | "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" |
| 219 | ); |
| 220 | assert_eq!( |
| 221 | sha256_hex(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), |
| 222 | "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" |
| 223 | ); |
| 224 | } |
| 225 | |
| 226 | #[test] |
| 227 | fn streaming_matches_one_shot() { |
| 228 | let data: Vec<u8> = (0..300).map(|i| i as u8).collect(); |
| 229 | let mut h = Sha256::new(); |
| 230 | h.update(&data[..100]); |
| 231 | h.update(&data[100..200]); |
| 232 | h.update(&data[200..]); |
| 233 | assert_eq!(hex(&h.finalize()), sha256_hex(&data)); |
| 234 | } |
| 235 | |
| 236 | #[test] |
| 237 | fn hex_validator() { |
| 238 | assert!(is_sha256_hex(&sha256_hex(b"x"))); |
| 239 | assert!(!is_sha256_hex("abc")); |
| 240 | assert!(!is_sha256_hex(&"g".repeat(64))); |
| 241 | } |
| 242 | } |