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