oxedyne/fe2o3/fe2o3_hash/src/sha256.rs
10.7 KiB, 24 runs
created by r1870400018:16361, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! A self-contained SHA-256 implementation, per FIPS 180-4. |
| 2 | //! |
| 3 | //! This exists because the Web Crypto API offers no SHA3, so a digest agreed between a browser |
| 4 | //! and a Hematite server must be one of the SHA-2 family. |
| 5 | //! |
| 6 | //! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\ |
| 7 | //! Anthropic Claude |
| 8 | |
| 9 | use oxedyne_fe2o3_core::prelude::*; |
| 10 | |
| 11 | // The round constants: the first thirty two bits of the fractional parts of the cube roots of |
| 12 | // the first sixty four primes. |
| 13 | const K: [u32; 64] = [ |
| 14 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, |
| 15 | 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, |
| 16 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, |
| 17 | 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, |
| 18 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, |
| 19 | 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, |
| 20 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, |
| 21 | 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, |
| 22 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, |
| 23 | 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, |
| 24 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, |
| 25 | 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, |
| 26 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, |
| 27 | 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, |
| 28 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, |
| 29 | 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, |
| 30 | ]; |
| 31 | |
| 32 | // The initial hash value: the first thirty two bits of the fractional parts of the square roots |
| 33 | // of the first eight primes. |
| 34 | const H0: [u32; 8] = [ |
| 35 | 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, |
| 36 | 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, |
| 37 | ]; |
| 38 | |
| 39 | const BLOCK_LEN: usize = 64; // bytes |
| 40 | pub const DIGEST_LEN: usize = 32; // bytes |
| 41 | |
| 42 | /// An incremental SHA-256 hasher, which buffers input until a full block is available. |
| 43 | #[derive(Clone, Debug)] |
| 44 | pub struct Sha256 { |
| 45 | state: [u32; 8], // running chaining value |
| 46 | buf: [u8; BLOCK_LEN], // partial block awaiting compression |
| 47 | buflen: usize, // bytes currently held in buf |
| 48 | total: u64, // message length, for the length padding |
| 49 | } |
| 50 | |
| 51 | impl Default for Sha256 { |
| 52 | fn default() -> Self { |
| 53 | Self { |
| 54 | state: H0, |
| 55 | buf: [0u8; BLOCK_LEN], |
| 56 | buflen: 0, |
| 57 | total: 0, |
| 58 | } |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | impl Sha256 { |
| 63 | |
| 64 | /// Creates a hasher primed with the FIPS 180-4 initial hash value. |
| 65 | pub fn new() -> Self { |
| 66 | Self::default() |
| 67 | } |
| 68 | |
| 69 | /// Absorbs a further slice of the message. |
| 70 | pub fn update(&mut self, mut data: &[u8]) { |
| 71 | self.total = self.total.wrapping_add(data.len() as u64); |
| 72 | // Top up any partial block first. |
| 73 | if self.buflen > 0 { |
| 74 | let take = std::cmp::min(BLOCK_LEN - self.buflen, data.len()); |
| 75 | self.buf[self.buflen..self.buflen + take].copy_from_slice(&data[..take]); |
| 76 | self.buflen += take; |
| 77 | data = &data[take..]; |
| 78 | if self.buflen == BLOCK_LEN { |
| 79 | let block = self.buf; |
| 80 | self.compress(&block); |
| 81 | self.buflen = 0; |
| 82 | } |
| 83 | } |
| 84 | // Consume whole blocks directly from the input. |
| 85 | while data.len() >= BLOCK_LEN { |
| 86 | let (block, rest) = data.split_at(BLOCK_LEN); |
| 87 | let mut chunk = [0u8; BLOCK_LEN]; |
| 88 | chunk.copy_from_slice(block); |
| 89 | self.compress(&chunk); |
| 90 | data = rest; |
| 91 | } |
| 92 | // Retain the remainder. |
| 93 | if !data.is_empty() { |
| 94 | self.buf[..data.len()].copy_from_slice(data); |
| 95 | self.buflen = data.len(); |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | /// Applies the padding and returns the final digest. |
| 100 | pub fn finish(mut self) -> [u8; DIGEST_LEN] { |
| 101 | let bitlen = self.total.wrapping_mul(8); |
| 102 | // A single one bit, then zeroes. When fewer than eight bytes remain after the one bit the |
| 103 | // length spills into a further block, which is the case naive implementations get wrong. |
| 104 | let mut pad = [0u8; 2 * BLOCK_LEN]; |
| 105 | pad[0] = 0x80; |
| 106 | let padlen = if self.buflen < 56 { |
| 107 | 56 - self.buflen |
| 108 | } else { |
| 109 | 120 - self.buflen |
| 110 | }; |
| 111 | pad[padlen..padlen + 8].copy_from_slice(&bitlen.to_be_bytes()); |
| 112 | // `total` is deliberately not corrected here, the hasher being consumed. |
| 113 | self.update_unlogged(&pad[..padlen + 8]); |
| 114 | |
| 115 | let mut out = [0u8; DIGEST_LEN]; |
| 116 | for (i, word) in self.state.iter().enumerate() { |
| 117 | out[4 * i..4 * i + 4].copy_from_slice(&word.to_be_bytes()); |
| 118 | } |
| 119 | out |
| 120 | } |
| 121 | |
| 122 | /// Absorbs padding without counting it toward the message length. |
| 123 | fn update_unlogged(&mut self, data: &[u8]) { |
| 124 | let before = self.total; |
| 125 | self.update(data); |
| 126 | self.total = before; |
| 127 | } |
| 128 | |
| 129 | /// Applies the compression function to one sixty four byte block. |
| 130 | fn compress(&mut self, block: &[u8; BLOCK_LEN]) { |
| 131 | let mut w = [0u32; 64]; |
| 132 | for i in 0..16 { |
| 133 | w[i] = u32::from_be_bytes([ |
| 134 | block[4 * i], |
| 135 | block[4 * i + 1], |
| 136 | block[4 * i + 2], |
| 137 | block[4 * i + 3], |
| 138 | ]); |
| 139 | } |
| 140 | for i in 16..64 { |
| 141 | let s0 = w[i - 15].rotate_right(7) |
| 142 | ^ w[i - 15].rotate_right(18) |
| 143 | ^ (w[i - 15] >> 3); |
| 144 | let s1 = w[i - 2].rotate_right(17) |
| 145 | ^ w[i - 2].rotate_right(19) |
| 146 | ^ (w[i - 2] >> 10); |
| 147 | w[i] = w[i - 16] |
| 148 | .wrapping_add(s0) |
| 149 | .wrapping_add(w[i - 7]) |
| 150 | .wrapping_add(s1); |
| 151 | } |
| 152 | |
| 153 | let mut a = self.state[0]; |
| 154 | let mut b = self.state[1]; |
| 155 | let mut c = self.state[2]; |
| 156 | let mut d = self.state[3]; |
| 157 | let mut e = self.state[4]; |
| 158 | let mut f = self.state[5]; |
| 159 | let mut g = self.state[6]; |
| 160 | let mut h = self.state[7]; |
| 161 | |
| 162 | for i in 0..64 { |
| 163 | let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25); |
| 164 | let ch = (e & f) ^ ((!e) & g); |
| 165 | let temp1 = h |
| 166 | .wrapping_add(s1) |
| 167 | .wrapping_add(ch) |
| 168 | .wrapping_add(K[i]) |
| 169 | .wrapping_add(w[i]); |
| 170 | let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22); |
| 171 | let maj = (a & b) ^ (a & c) ^ (b & c); |
| 172 | let temp2 = s0.wrapping_add(maj); |
| 173 | |
| 174 | h = g; |
| 175 | g = f; |
| 176 | f = e; |
| 177 | e = d.wrapping_add(temp1); |
| 178 | d = c; |
| 179 | c = b; |
| 180 | b = a; |
| 181 | a = temp1.wrapping_add(temp2); |
| 182 | } |
| 183 | |
| 184 | self.state[0] = self.state[0].wrapping_add(a); |
| 185 | self.state[1] = self.state[1].wrapping_add(b); |
| 186 | self.state[2] = self.state[2].wrapping_add(c); |
| 187 | self.state[3] = self.state[3].wrapping_add(d); |
| 188 | self.state[4] = self.state[4].wrapping_add(e); |
| 189 | self.state[5] = self.state[5].wrapping_add(f); |
| 190 | self.state[6] = self.state[6].wrapping_add(g); |
| 191 | self.state[7] = self.state[7].wrapping_add(h); |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | pub fn digest(msg: &[u8]) -> [u8; DIGEST_LEN] { |
| 196 | let mut hasher = Sha256::new(); |
| 197 | hasher.update(msg); |
| 198 | hasher.finish() |
| 199 | } |
| 200 | |
| 201 | #[cfg(test)] |
| 202 | mod tests { |
| 203 | use super::*; |
| 204 | |
| 205 | use oxedyne_fe2o3_core::{ |
| 206 | byte::B32, |
| 207 | string::ToHexString, |
| 208 | }; |
| 209 | |
| 210 | /// Renders a digest as lower case hexadecimal, for comparison with the published vectors. |
| 211 | fn hex(d: [u8; DIGEST_LEN]) -> String { |
| 212 | B32(d).to_hex_string() |
| 213 | } |
| 214 | |
| 215 | /// The vectors published with FIPS 180-4 and in the NIST byte oriented test vector set. |
| 216 | #[test] |
| 217 | fn test_sha256_fips_180_4_vectors() -> Outcome<()> { |
| 218 | let vectors: [(&[u8], &str); 3] = [ |
| 219 | ( |
| 220 | b"", |
| 221 | "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", |
| 222 | ), |
| 223 | ( |
| 224 | b"abc", |
| 225 | "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", |
| 226 | ), |
| 227 | ( |
| 228 | b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", |
| 229 | "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", |
| 230 | ), |
| 231 | ]; |
| 232 | for (msg, expected) in vectors { |
| 233 | let d = hex(digest(msg)); |
| 234 | req!(d, expected.to_string(), "SHA-256 of {:?}", msg); |
| 235 | } |
| 236 | Ok(()) |
| 237 | } |
| 238 | |
| 239 | /// The two block vector from FIPS 180-4, which exercises the message schedule across a block |
| 240 | /// boundary. |
| 241 | #[test] |
| 242 | fn test_sha256_multi_block() -> Outcome<()> { |
| 243 | let msg: &[u8] = |
| 244 | b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"; |
| 245 | let d = hex(digest(msg)); |
| 246 | req!(d, "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1".to_string()); |
| 247 | Ok(()) |
| 248 | } |
| 249 | |
| 250 | /// Lengths either side of the padding boundary, where the length field either fits in the |
| 251 | /// final block or forces an extra one. These are the cases that break naive padding. |
| 252 | #[test] |
| 253 | fn test_sha256_padding_boundaries() -> Outcome<()> { |
| 254 | // Digests of 55, 56, 63, 64 and 65 repetitions of 'a'. |
| 255 | let vectors: [(usize, &str); 5] = [ |
| 256 | (55, "9f4390f8d30c2dd92ec9f095b65e2b9ae9b0a925a5258e241c9f1e910f734318"), |
| 257 | (56, "b35439a4ac6f0948b6d6f9e3c6af0f5f590ce20f1bde7090ef7970686ec6738a"), |
| 258 | (63, "7d3e74a05d7db15bce4ad9ec0658ea98e3f06eeecf16b4c6fff2da457ddc2f34"), |
| 259 | (64, "ffe054fe7ae0cb6dc65c3af9b61d5209f439851db43d0ba5997337df154668eb"), |
| 260 | (65, "635361c48bb9eab14198e76ea8ab7f1a41685d6ad62aa9146d301d4f17eb0ae0"), |
| 261 | ]; |
| 262 | for (n, expected) in vectors { |
| 263 | let msg = vec![b'a'; n]; |
| 264 | let d = hex(digest(&msg)); |
| 265 | req!(d, expected.to_string(), "SHA-256 of {} 'a's", n); |
| 266 | } |
| 267 | Ok(()) |
| 268 | } |
| 269 | |
| 270 | /// Streaming in awkwardly sized pieces must agree with hashing in one go, which is what the |
| 271 | /// `Hasher` impl relies on when it absorbs several input slices. |
| 272 | #[test] |
| 273 | fn test_sha256_incremental_matches_oneshot() -> Outcome<()> { |
| 274 | let msg = vec![b'x'; 1000]; |
| 275 | for chunk in [1usize, 7, 31, 63, 64, 65, 127] { |
| 276 | let mut hasher = Sha256::new(); |
| 277 | for piece in msg.chunks(chunk) { |
| 278 | hasher.update(piece); |
| 279 | } |
| 280 | // `req!` renders its arguments again on failure, so the digests are bound first. |
| 281 | let streamed = hex(hasher.finish()); |
| 282 | let oneshot = hex(digest(&msg)); |
| 283 | req!(streamed, oneshot, "chunk size {}", chunk); |
| 284 | } |
| 285 | Ok(()) |
| 286 | } |
| 287 | |
| 288 | /// The one million 'a' vector from FIPS 180-4, ignored by default only for its cost. |
| 289 | #[test] |
| 290 | #[ignore] |
| 291 | fn test_sha256_million_a() -> Outcome<()> { |
| 292 | let mut hasher = Sha256::new(); |
| 293 | let block = [b'a'; 1000]; |
| 294 | for _ in 0..1000 { |
| 295 | hasher.update(&block); |
| 296 | } |
| 297 | let d = hex(hasher.finish()); |
| 298 | req!(d, "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0".to_string()); |
| 299 | Ok(()) |
| 300 | } |
| 301 | } |