BlueGit

golden-age paperdoll-renderer

Files

paperdoll-renderer / src / digest.rs

digest.rs

digest.rs 8.0 KiB Download Raw
1//! SHA-256 digests for export-set identity (no external digest crate).
2#![allow(clippy::unreadable_literal)]
3
4const 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)]
16pub struct Sha256 {
17 state: [u32; 8],
18 buffer: [u8; 64],
19 buffer_len: usize,
20 bit_len: u64,
21}
22
23impl Default for Sha256 {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl 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
151pub fn sha256(data: &[u8]) -> [u8; 32] {
152 let mut h = Sha256::new();
153 h.update(data);
154 h.finalize()
155}
156
157pub fn sha256_hex(data: &[u8]) -> String {
158 hex(&sha256(data))
159}
160
161pub 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
171pub 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.
179pub 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)]
207mod 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}