1use hmac::{Hmac, Mac};
4use rand::Rng;
5use sha2::{Digest, Sha256};
6
7type HmacSha256 = Hmac<Sha256>;
8
9pub fn sha256_hex(value: &str) -> String {
13 let hash = Sha256::digest(value.as_bytes());
14 hex::encode(hash)
15}
16
17pub fn hmac_sha256_hex(key: &str, value: &str) -> String {
21 let mut mac = HmacSha256::new_from_slice(key.as_bytes()).expect("HMAC accepts any key length");
22 mac.update(value.as_bytes());
23 hex::encode(mac.finalize().into_bytes())
24}
25
26pub fn hmac_sha256_verify(key: &str, value: &str, signature_hex: &str) -> Result<(), CryptoError> {
28 let mut mac = HmacSha256::new_from_slice(key.as_bytes())
29 .map_err(|e| CryptoError::InvalidKey(e.to_string()))?;
30 mac.update(value.as_bytes());
31 let expected =
32 hex::decode(signature_hex).map_err(|e| CryptoError::InvalidHex(e.to_string()))?;
33 mac.verify_slice(&expected)
34 .map_err(|_| CryptoError::SignatureMismatch)
35}
36
37pub fn generate_opaque_token(prefix: &str, byte_length: usize) -> String {
41 let mut bytes = vec![0u8; byte_length];
42 rand::rng().fill(&mut bytes[..]);
43 format!("{}{}", prefix, hex::encode(bytes))
44}
45
46pub fn generate_state_nonce(byte_length: usize) -> String {
48 generate_opaque_token("", byte_length)
49}
50
51#[derive(Debug, thiserror::Error)]
53pub enum CryptoError {
54 #[error("invalid key: {0}")]
56 InvalidKey(String),
57
58 #[error("invalid hex: {0}")]
60 InvalidHex(String),
61
62 #[error("signature mismatch")]
64 SignatureMismatch,
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn sha256_hex_deterministic() {
73 let h1 = sha256_hex("hello");
74 let h2 = sha256_hex("hello");
75 assert_eq!(h1, h2);
76 assert_eq!(h1.len(), 64);
77 }
78
79 #[test]
80 fn sha256_hex_different_inputs() {
81 assert_ne!(sha256_hex("hello"), sha256_hex("world"));
82 }
83
84 #[test]
85 fn hmac_roundtrip() {
86 let sig = hmac_sha256_hex("secret-key", "test-message");
87 assert!(hmac_sha256_verify("secret-key", "test-message", &sig).is_ok());
88 }
89
90 #[test]
91 fn hmac_wrong_key_fails() {
92 let sig = hmac_sha256_hex("key1", "message");
93 assert!(hmac_sha256_verify("key2", "message", &sig).is_err());
94 }
95
96 #[test]
97 fn hmac_wrong_message_fails() {
98 let sig = hmac_sha256_hex("key", "msg1");
99 assert!(hmac_sha256_verify("key", "msg2", &sig).is_err());
100 }
101
102 #[test]
103 fn generate_opaque_token_prefix() {
104 let token = generate_opaque_token("tok_", 16);
105 assert!(token.starts_with("tok_"));
106 assert_eq!(token.len(), 4 + 32);
107 }
108
109 #[test]
110 fn generate_state_nonce_length() {
111 let nonce = generate_state_nonce(16);
112 assert_eq!(nonce.len(), 32);
113 }
114
115 #[test]
116 fn hmac_invalid_hex_fails() {
117 assert!(hmac_sha256_verify("key", "msg", "not-hex").is_err());
118 }
119}