Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_net/src/hmac.rs

3.8 KiB, 20 runs

created by r1870400018:13293, 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//! HMAC-SHA256 keyed message authentication.
2//!
3//! A thin wrapper over `ring::hmac` (the constant-time MAC already in
4//! Hematite's dependency tree via this crate) exposing the two
5//! operations downstream callers need: compute a tag, and verify a
6//! tag in constant time. Webhook signature verification (e.g. Stripe's
7//! `Stripe-Signature` scheme) is the motivating caller.
8//!
9//! Constant-time verification matters: comparing a computed tag against
10//! an attacker-supplied one with an ordinary byte comparison leaks the
11//! length of the matching prefix through timing, so verification must
12//! use `ring::hmac::verify`, never `==`.
13//!
14//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
15//! Anthropic Claude
16
17use oxedyne_fe2o3_core::prelude::*;
18
19use ring::hmac as ring_hmac;
20
21
22/// A key of any length is legal, `ring` doing the RFC 2104 padding, and the
23/// operation cannot fail, so there is no `Outcome` wrapper.
24pub fn hmac_sha256(key: &[u8], msg: &[u8]) -> [u8; 32] {
25 let k = ring_hmac::Key::new(ring_hmac::HMAC_SHA256, key);
26 let tag = ring_hmac::sign(&k, msg);
27 // The SHA-256 tag is always 32 bytes; copy it into a fixed array.
28 let mut out = [0u8; 32];
29 out.copy_from_slice(tag.as_ref());
30 out
31}
32
33/// Is `tag` the HMAC-SHA256 of `msg` under `key`?
34///
35/// The comparison is `ring`'s constant-time one, so no timing side channel
36/// reveals how much of the tag was correct. A tag of the wrong length fails to
37/// verify rather than panicking.
38pub fn verify_hmac_sha256(key: &[u8], msg: &[u8], tag: &[u8]) -> bool {
39 let k = ring_hmac::Key::new(ring_hmac::HMAC_SHA256, key);
40 ring_hmac::verify(&k, msg, tag).is_ok()
41}
42
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 /// Decode a lowercase hex string into bytes for test vectors.
49 fn from_hex(s: &str) -> Vec<u8> {
50 let bytes = s.as_bytes();
51 let mut out = Vec::with_capacity(bytes.len() / 2);
52 let val = |c: u8| -> u8 {
53 match c {
54 b'0'..=b'9' => c - b'0',
55 b'a'..=b'f' => c - b'a' + 10,
56 b'A'..=b'F' => c - b'A' + 10,
57 _ => 0,
58 }
59 };
60 let mut i = 0;
61 while i + 1 < bytes.len() {
62 out.push((val(bytes[i]) << 4) | val(bytes[i + 1]));
63 i += 2;
64 }
65 out
66 }
67
68 /// RFC 4231 test case 2: a known HMAC-SHA256 vector.
69 ///
70 /// Key = "Jefe", Data = "what do ya want for nothing?",
71 /// expected tag =
72 /// 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843.
73 #[test]
74 fn test_rfc4231_case2() {
75 let key = b"Jefe";
76 let msg = b"what do ya want for nothing?";
77 let expected = from_hex(
78 "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843");
79
80 let tag = hmac_sha256(key, msg);
81 assert_eq!(&tag[..], &expected[..], "HMAC-SHA256 tag mismatch");
82
83 // Constant-time verification of the same vector.
84 assert!(verify_hmac_sha256(key, msg, &expected),
85 "verify should accept the correct tag");
86
87 // A tampered tag must be rejected.
88 let mut bad = expected.clone();
89 bad[0] ^= 0x01;
90 assert!(!verify_hmac_sha256(key, msg, &bad),
91 "verify should reject a tampered tag");
92
93 // A tag of the wrong length must be rejected, not panic.
94 assert!(!verify_hmac_sha256(key, msg, &expected[..16]),
95 "verify should reject a short tag");
96 }
97
98 /// RFC 4231 test case 1: 20-byte 0x0b key, Data = "Hi There".
99 #[test]
100 fn test_rfc4231_case1() {
101 let key = [0x0bu8; 20];
102 let msg = b"Hi There";
103 let expected = from_hex(
104 "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
105 let tag = hmac_sha256(&key, msg);
106 assert_eq!(&tag[..], &expected[..], "HMAC-SHA256 case 1 mismatch");
107 }
108}