we (web engine): Experimental web browser project to understand the limits of Claude
1//! HMAC: keyed-hash message authentication code (RFC 2104).
2
3use crate::sha2::{Sha256, Sha384, Sha512};
4
5/// Trait abstracting a hash function for use with HMAC.
6pub trait HashFunction {
7 /// Block size in bytes (64 for SHA-256, 128 for SHA-512/SHA-384).
8 const BLOCK_SIZE: usize;
9 /// Output digest size in bytes.
10 const OUTPUT_SIZE: usize;
11
12 fn hash_new() -> Self;
13 fn hash_update(&mut self, data: &[u8]);
14 fn hash_finalize(self) -> Vec<u8>;
15}
16
17impl HashFunction for Sha256 {
18 const BLOCK_SIZE: usize = 64;
19 const OUTPUT_SIZE: usize = 32;
20
21 fn hash_new() -> Self {
22 Sha256::new()
23 }
24
25 fn hash_update(&mut self, data: &[u8]) {
26 Sha256::update(self, data);
27 }
28
29 fn hash_finalize(self) -> Vec<u8> {
30 Sha256::finalize(self).to_vec()
31 }
32}
33
34impl HashFunction for Sha512 {
35 const BLOCK_SIZE: usize = 128;
36 const OUTPUT_SIZE: usize = 64;
37
38 fn hash_new() -> Self {
39 Sha512::new()
40 }
41
42 fn hash_update(&mut self, data: &[u8]) {
43 Sha512::update(self, data);
44 }
45
46 fn hash_finalize(self) -> Vec<u8> {
47 Sha512::finalize(self).to_vec()
48 }
49}
50
51impl HashFunction for Sha384 {
52 const BLOCK_SIZE: usize = 128;
53 const OUTPUT_SIZE: usize = 48;
54
55 fn hash_new() -> Self {
56 Sha384::new()
57 }
58
59 fn hash_update(&mut self, data: &[u8]) {
60 Sha384::update(self, data);
61 }
62
63 fn hash_finalize(self) -> Vec<u8> {
64 Sha384::finalize(self).to_vec()
65 }
66}
67
68/// HMAC hasher, generic over the hash function `H`.
69///
70/// Implements RFC 2104: `HMAC(K, m) = H((K' ⊕ opad) ∥ H((K' ⊕ ipad) ∥ m))`
71pub struct Hmac<H: HashFunction> {
72 /// Inner hash, pre-seeded with `K' ⊕ ipad`.
73 inner: H,
74 /// Pre-computed `K' ⊕ opad` for the outer hash.
75 opad_key: Vec<u8>,
76}
77
78impl<H: HashFunction> Hmac<H> {
79 /// Create a new HMAC instance with the given key.
80 ///
81 /// Keys longer than the hash block size are hashed first.
82 /// Keys shorter than the block size are zero-padded.
83 pub fn new(key: &[u8]) -> Self {
84 // Step 1: Derive the block-sized key K'.
85 let mut key_block = vec![0u8; H::BLOCK_SIZE];
86 if key.len() > H::BLOCK_SIZE {
87 let mut h = H::hash_new();
88 h.hash_update(key);
89 let hashed = h.hash_finalize();
90 key_block[..hashed.len()].copy_from_slice(&hashed);
91 } else {
92 key_block[..key.len()].copy_from_slice(key);
93 }
94
95 // Step 2: Compute ipad and opad keys.
96 let mut ipad_key = vec![0u8; H::BLOCK_SIZE];
97 let mut opad_key = vec![0u8; H::BLOCK_SIZE];
98 for i in 0..H::BLOCK_SIZE {
99 ipad_key[i] = key_block[i] ^ 0x36;
100 opad_key[i] = key_block[i] ^ 0x5c;
101 }
102
103 // Step 3: Start the inner hash with K' ⊕ ipad.
104 let mut inner = H::hash_new();
105 inner.hash_update(&ipad_key);
106
107 Self { inner, opad_key }
108 }
109
110 /// Feed data into the HMAC computation.
111 pub fn update(&mut self, data: &[u8]) {
112 self.inner.hash_update(data);
113 }
114
115 /// Finalize and return the HMAC digest.
116 pub fn finalize(self) -> Vec<u8> {
117 // Complete inner hash: H((K' ⊕ ipad) ∥ message)
118 let inner_hash = self.inner.hash_finalize();
119
120 // Compute outer hash: H((K' ⊕ opad) ∥ inner_hash)
121 let mut outer = H::hash_new();
122 outer.hash_update(&self.opad_key);
123 outer.hash_update(&inner_hash);
124 outer.hash_finalize()
125 }
126}
127
128/// One-shot HMAC-SHA-256.
129pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] {
130 let mut h = Hmac::<Sha256>::new(key);
131 h.update(data);
132 let result = h.finalize();
133 let mut out = [0u8; 32];
134 out.copy_from_slice(&result);
135 out
136}
137
138/// One-shot HMAC-SHA-384.
139pub fn hmac_sha384(key: &[u8], data: &[u8]) -> [u8; 48] {
140 let mut h = Hmac::<Sha384>::new(key);
141 h.update(data);
142 let result = h.finalize();
143 let mut out = [0u8; 48];
144 out.copy_from_slice(&result);
145 out
146}
147
148/// One-shot HMAC-SHA-512.
149pub fn hmac_sha512(key: &[u8], data: &[u8]) -> [u8; 64] {
150 let mut h = Hmac::<Sha512>::new(key);
151 h.update(data);
152 let result = h.finalize();
153 let mut out = [0u8; 64];
154 out.copy_from_slice(&result);
155 out
156}
157
158// ---------------------------------------------------------------------------
159// Tests — RFC 4231 test vectors
160// ---------------------------------------------------------------------------
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 fn hex(bytes: &[u8]) -> String {
167 bytes.iter().map(|b| format!("{b:02x}")).collect()
168 }
169
170 // -----------------------------------------------------------------------
171 // Test Case 1: Key = 0x0b * 20, Data = "Hi There"
172 // -----------------------------------------------------------------------
173
174 #[test]
175 fn rfc4231_case1_sha256() {
176 let key = [0x0bu8; 20];
177 assert_eq!(
178 hex(&hmac_sha256(&key, b"Hi There")),
179 "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
180 );
181 }
182
183 #[test]
184 fn rfc4231_case1_sha384() {
185 let key = [0x0bu8; 20];
186 assert_eq!(
187 hex(&hmac_sha384(&key, b"Hi There")),
188 "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6"
189 );
190 }
191
192 #[test]
193 fn rfc4231_case1_sha512() {
194 let key = [0x0bu8; 20];
195 assert_eq!(
196 hex(&hmac_sha512(&key, b"Hi There")),
197 "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
198 );
199 }
200
201 // -----------------------------------------------------------------------
202 // Test Case 2: Key = "Jefe", Data = "what do ya want for nothing?"
203 // -----------------------------------------------------------------------
204
205 #[test]
206 fn rfc4231_case2_sha256() {
207 assert_eq!(
208 hex(&hmac_sha256(b"Jefe", b"what do ya want for nothing?")),
209 "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
210 );
211 }
212
213 #[test]
214 fn rfc4231_case2_sha384() {
215 assert_eq!(
216 hex(&hmac_sha384(b"Jefe", b"what do ya want for nothing?")),
217 "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649"
218 );
219 }
220
221 #[test]
222 fn rfc4231_case2_sha512() {
223 assert_eq!(
224 hex(&hmac_sha512(b"Jefe", b"what do ya want for nothing?")),
225 "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737"
226 );
227 }
228
229 // -----------------------------------------------------------------------
230 // Test Case 3: Key = 0xaa * 20, Data = 0xdd * 50
231 // -----------------------------------------------------------------------
232
233 #[test]
234 fn rfc4231_case3_sha256() {
235 let key = [0xaau8; 20];
236 let data = [0xddu8; 50];
237 assert_eq!(
238 hex(&hmac_sha256(&key, &data)),
239 "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"
240 );
241 }
242
243 #[test]
244 fn rfc4231_case3_sha384() {
245 let key = [0xaau8; 20];
246 let data = [0xddu8; 50];
247 assert_eq!(
248 hex(&hmac_sha384(&key, &data)),
249 "88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27"
250 );
251 }
252
253 #[test]
254 fn rfc4231_case3_sha512() {
255 let key = [0xaau8; 20];
256 let data = [0xddu8; 50];
257 assert_eq!(
258 hex(&hmac_sha512(&key, &data)),
259 "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb"
260 );
261 }
262
263 // -----------------------------------------------------------------------
264 // Test Case 4: Key = 0x01..0x19 (25 bytes), Data = 0xcd * 50
265 // -----------------------------------------------------------------------
266
267 #[test]
268 fn rfc4231_case4_sha256() {
269 let key: Vec<u8> = (0x01..=0x19).collect();
270 let data = [0xcdu8; 50];
271 assert_eq!(
272 hex(&hmac_sha256(&key, &data)),
273 "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b"
274 );
275 }
276
277 #[test]
278 fn rfc4231_case4_sha384() {
279 let key: Vec<u8> = (0x01..=0x19).collect();
280 let data = [0xcdu8; 50];
281 assert_eq!(
282 hex(&hmac_sha384(&key, &data)),
283 "3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb"
284 );
285 }
286
287 #[test]
288 fn rfc4231_case4_sha512() {
289 let key: Vec<u8> = (0x01..=0x19).collect();
290 let data = [0xcdu8; 50];
291 assert_eq!(
292 hex(&hmac_sha512(&key, &data)),
293 "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd"
294 );
295 }
296
297 // -----------------------------------------------------------------------
298 // Test Case 5: Truncation (Key = 0x0c * 20, Data = "Test With Truncation")
299 // Verify the first 16 bytes of the full HMAC match the truncated vector.
300 // -----------------------------------------------------------------------
301
302 #[test]
303 fn rfc4231_case5_sha256_truncated() {
304 let key = [0x0cu8; 20];
305 let result = hmac_sha256(&key, b"Test With Truncation");
306 assert_eq!(hex(&result[..16]), "a3b6167473100ee06e0c796c2955552b");
307 }
308
309 #[test]
310 fn rfc4231_case5_sha384_truncated() {
311 let key = [0x0cu8; 20];
312 let result = hmac_sha384(&key, b"Test With Truncation");
313 assert_eq!(hex(&result[..16]), "3abf34c3503b2a23a46efc619baef897");
314 }
315
316 #[test]
317 fn rfc4231_case5_sha512_truncated() {
318 let key = [0x0cu8; 20];
319 let result = hmac_sha512(&key, b"Test With Truncation");
320 assert_eq!(hex(&result[..16]), "415fad6271580a531d4179bc891d87a6");
321 }
322
323 // -----------------------------------------------------------------------
324 // Test Case 6: Key longer than block size (0xaa * 131)
325 // Data = "Test Using Larger Than Block-Size Key - Hash Key First"
326 // -----------------------------------------------------------------------
327
328 #[test]
329 fn rfc4231_case6_sha256() {
330 let key = [0xaau8; 131];
331 assert_eq!(
332 hex(&hmac_sha256(
333 &key,
334 b"Test Using Larger Than Block-Size Key - Hash Key First"
335 )),
336 "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"
337 );
338 }
339
340 #[test]
341 fn rfc4231_case6_sha384() {
342 let key = [0xaau8; 131];
343 assert_eq!(
344 hex(&hmac_sha384(&key, b"Test Using Larger Than Block-Size Key - Hash Key First")),
345 "4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952"
346 );
347 }
348
349 #[test]
350 fn rfc4231_case6_sha512() {
351 let key = [0xaau8; 131];
352 assert_eq!(
353 hex(&hmac_sha512(&key, b"Test Using Larger Than Block-Size Key - Hash Key First")),
354 "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598"
355 );
356 }
357
358 // -----------------------------------------------------------------------
359 // Test Case 7: Key longer than block size, data longer than block size
360 // Key = 0xaa * 131
361 // -----------------------------------------------------------------------
362
363 const CASE7_DATA: &[u8] = b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.";
364
365 #[test]
366 fn rfc4231_case7_sha256() {
367 let key = [0xaau8; 131];
368 assert_eq!(
369 hex(&hmac_sha256(&key, CASE7_DATA)),
370 "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"
371 );
372 }
373
374 #[test]
375 fn rfc4231_case7_sha384() {
376 let key = [0xaau8; 131];
377 assert_eq!(
378 hex(&hmac_sha384(&key, CASE7_DATA)),
379 "6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e"
380 );
381 }
382
383 #[test]
384 fn rfc4231_case7_sha512() {
385 let key = [0xaau8; 131];
386 assert_eq!(
387 hex(&hmac_sha512(&key, CASE7_DATA)),
388 "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58"
389 );
390 }
391
392 // -----------------------------------------------------------------------
393 // Streaming API tests
394 // -----------------------------------------------------------------------
395
396 #[test]
397 fn streaming_matches_oneshot_sha256() {
398 let key = b"secret key";
399 let data = b"The quick brown fox jumps over the lazy dog";
400 let expected = hmac_sha256(key, data);
401
402 let mut h = Hmac::<Sha256>::new(key);
403 h.update(&data[..10]);
404 h.update(&data[10..30]);
405 h.update(&data[30..]);
406 let result = h.finalize();
407 assert_eq!(&result[..], &expected[..]);
408 }
409
410 #[test]
411 fn streaming_matches_oneshot_sha512() {
412 let key = b"another secret key";
413 let data = b"Some message to authenticate";
414 let expected = hmac_sha512(key, data);
415
416 let mut h = Hmac::<Sha512>::new(key);
417 h.update(&data[..5]);
418 h.update(&data[5..]);
419 let result = h.finalize();
420 assert_eq!(&result[..], &expected[..]);
421 }
422
423 #[test]
424 fn empty_data() {
425 let key = [0x0bu8; 20];
426 let result = hmac_sha256(&key, b"");
427 // Just verify it doesn't panic and produces 32 bytes.
428 assert_eq!(result.len(), 32);
429 }
430
431 #[test]
432 fn key_exactly_block_size_sha256() {
433 // Key exactly 64 bytes (SHA-256 block size) — no hashing, no padding needed.
434 let key = [0x42u8; 64];
435 let result = hmac_sha256(&key, b"test");
436 assert_eq!(result.len(), 32);
437
438 // Verify streaming matches.
439 let mut h = Hmac::<Sha256>::new(&key);
440 h.update(b"test");
441 assert_eq!(&h.finalize()[..], &result[..]);
442 }
443}