1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
//! HMAC (Hash-based Message Authentication Code) implementation supporting multiple SHA-2 variants.
//!
//! This module provides a flexible HMAC implementation that supports various SHA-2 hash functions
//! for generating and verifying message authentication codes. The implementation is thread-safe
//! and can be used in concurrent contexts.
use crate::results::AppResult;
use chrono::Utc;
use hmac::{Hmac as HHmac, Mac, KeyInit};
use sha2::{Sha224, Sha256, Sha384, Sha512, Sha512_224, Sha512_256};
/// Supported hash functions for HMAC generation and verification.
#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub enum HashFunc {
/// SHA-224 hash function (224-bit output)
Sha224,
#[default]
/// SHA-256 hash function (256-bit output)
Sha256,
/// SHA-384 hash function (384-bit output)
Sha384,
/// SHA-512 hash function (512-bit output)
Sha512,
/// SHA-512/224 hash function (224-bits output using SHA-512 internal state)
Sha512224,
/// SHA-512/256 hash function (256-bits output using SHA-512 internal state)
Sha512256,
}
/// HMAC generator and verifier structure.
///
/// This structure provides methods for generating and verifying HMACs using
/// various SHA-2 hash functions. It is thread-safe and can be cloned.
#[derive(Clone)]
pub struct Hmac {
/// Secret key used for HMAC generation and verification
secret: String,
/// Hash function used for HMAC generation and verification
func: HashFunc,
}
impl Hmac {
/// Creates a new HMAC instance with the specified secret key.
///
/// # Arguments
///
/// * `secret` - The secret key to use for HMAC operations
///
/// # Example
///
/// ```
/// use foxtive::helpers::hmac::{Hmac, HashFunc};
///
/// let hmac = Hmac::new("my_secret_key", HashFunc::Sha256);
/// ```
pub fn new(secret: &str, func: HashFunc) -> Self {
Hmac {
func,
secret: secret.to_string(),
}
}
/// Generates an HMAC for the given value using the specified hash function.
///
/// # Arguments
///
/// * `value` - The message to generate an HMAC for
/// * `fun` - The hash function to use
///
/// # Returns
///
/// Returns a Result containing the hexadecimal string representation of the HMAC
/// or an error if HMAC generation fails.
///
/// # Example
///
/// ```
/// use foxtive::helpers::hmac::{Hmac, HashFunc};
///
/// let hmac = Hmac::new("my_secret_key", HashFunc::Sha256);
/// let value = "message".to_string();
/// let hash = hmac.hash(&value).unwrap();
/// ```
pub fn hash(&self, value: &String) -> AppResult<String> {
match self.func {
HashFunc::Sha224 => {
let mut mac = HHmac::<Sha224>::new_from_slice(self.secret.as_bytes())?;
mac.update(value.as_bytes());
Self::convert_to_string(&mac.finalize().into_bytes())
}
HashFunc::Sha256 => {
let mut mac = HHmac::<Sha256>::new_from_slice(self.secret.as_bytes())?;
mac.update(value.as_bytes());
Self::convert_to_string(&mac.finalize().into_bytes())
}
HashFunc::Sha384 => {
let mut mac = HHmac::<Sha384>::new_from_slice(self.secret.as_bytes())?;
mac.update(value.as_bytes());
Self::convert_to_string(&mac.finalize().into_bytes())
}
HashFunc::Sha512 => {
let mut mac = HHmac::<Sha512>::new_from_slice(self.secret.as_bytes())?;
mac.update(value.as_bytes());
Self::convert_to_string(&mac.finalize().into_bytes())
}
HashFunc::Sha512224 => {
let mut mac = HHmac::<Sha512_224>::new_from_slice(self.secret.as_bytes())?;
mac.update(value.as_bytes());
Self::convert_to_string(&mac.finalize().into_bytes())
}
HashFunc::Sha512256 => {
let mut mac = HHmac::<Sha512_256>::new_from_slice(self.secret.as_bytes())?;
mac.update(value.as_bytes());
Self::convert_to_string(&mac.finalize().into_bytes())
}
}
}
/// Generates a random HMAC using the current timestamp as both the key and value.
///
/// This method uses the default hash function (SHA-256) and the current timestamp
/// to generate a random HMAC. This can be useful for generating unique tokens
/// or identifiers.
///
/// # Returns
///
/// Returns a Result containing the generated random HMAC as a hexadecimal string
/// or an error if generation fails.
///
/// # Example
///
/// ```
/// use foxtive::helpers::hmac::Hmac;
///
/// let random_hmac = Hmac::random().unwrap();
/// ```
pub fn random() -> AppResult<String> {
let timestamp = Utc::now().timestamp_micros().to_string();
// Using the default hash function (Sha256) for random generation
Hmac::new(×tamp, HashFunc::default()).hash(×tamp)
}
/// Verifies an HMAC against a provided value using the specified hash function.
///
/// # Arguments
///
/// * `value` - The original message
/// * `hash` - The HMAC to verify against
/// * `fun` - The hash function to use
///
/// # Returns
///
/// Returns a Result containing a boolean indicating whether the HMAC is valid
/// or an error if verification fails.
///
/// # Example
///
/// ```
/// use foxtive::helpers::hmac::{Hmac, HashFunc};
///
/// let hmac = Hmac::new("my_secret_key", HashFunc::Sha256);
/// let value = "message".to_string();
/// let hash = hmac.hash(&value).unwrap();
///
/// assert!(hmac.verify(&value, &hash).unwrap());
/// ```
pub fn verify(&self, value: &String, hash: &String) -> AppResult<bool> {
let computed = self.hash(value)?;
Ok(hash == &computed)
}
/// Converts a byte slice to its hexadecimal string representation.
///
/// # Arguments
///
/// * `slices` - The byte slice to convert
///
/// # Returns
///
/// Returns a Result containing the hexadecimal string representation
/// or an error if conversion fails.
fn convert_to_string(slices: &[u8]) -> AppResult<String> {
Ok(hex::encode(slices))
}
}
#[cfg(test)]
mod tests {
use super::{HashFunc, Hmac};
#[test]
fn test_hash() {
let hmac = Hmac::new("mysecret", HashFunc::Sha256);
let value = "my message".to_string();
let expected_hmac = "6df7d0cf7d3a52a08acbd7c12a2ab86b15820de24a78bd51e264e257de3316b0";
let generated_hmac = hmac.hash(&value).unwrap();
assert_eq!(
generated_hmac, expected_hmac,
"The generated HMAC does not match the expected value."
);
}
#[test]
fn test_random() {
let random_hmac1 = Hmac::random().unwrap();
let random_hmac2 = Hmac::random().unwrap();
assert_ne!(
random_hmac1, random_hmac2,
"The generated HMACs should be different."
);
assert!(
!random_hmac1.is_empty() && !random_hmac2.is_empty(),
"The generated HMACs should not be empty."
);
}
#[test]
fn test_hmac_valid() {
let hmac = Hmac::new("mysecret", HashFunc::Sha256);
let value = "my message".to_string();
let provided_hmac =
"6df7d0cf7d3a52a08acbd7c12a2ab86b15820de24a78bd51e264e257de3316b0".to_string();
let is_valid = hmac.verify(&value, &provided_hmac).unwrap();
assert!(
is_valid,
"The HMAC verification should succeed, but it failed."
);
}
#[test]
fn test_hmac_invalid() {
let hmac = Hmac::new("mysecret", HashFunc::Sha256);
let value = "my message".to_string();
let provided_hmac = "invalidhmac".to_string();
let is_valid = hmac.verify(&value, &provided_hmac).unwrap();
assert!(
!is_valid,
"The HMAC verification should fail, but it succeeded."
);
}
#[test]
fn test_hash_with_different_values() {
let hmac = Hmac::new("mysecret", HashFunc::Sha256);
let value1 = "message1".to_string();
let value2 = "message2".to_string();
let hmac1 = hmac.hash(&value1).unwrap();
let hmac2 = hmac.hash(&value2).unwrap();
assert_ne!(
hmac1, hmac2,
"HMACs for different values should not be the same."
);
}
#[test]
fn test_hash_with_different_functions() {
let hmac256 = Hmac::new("mysecret", HashFunc::Sha256);
let hmac512 = Hmac::new("mysecret", HashFunc::Sha512);
let value = "my message".to_string();
let sha256_hmac = hmac256.hash(&value).unwrap();
let sha512_hmac = hmac512.hash(&value).unwrap();
assert_ne!(
sha256_hmac, sha512_hmac,
"HMACs with different hash functions should not be the same."
);
}
#[test]
fn test_verify_with_different_functions() {
let hmac = Hmac::new("mysecret", HashFunc::Sha512);
let value = "my message".to_string();
// Generate HMAC with SHA-512
let sha512_hmac = hmac.hash(&value).unwrap();
// Verify should succeed with SHA-512
assert!(
hmac.verify(&value, &sha512_hmac).unwrap(),
"Verification should succeed with matching hash function"
);
// Verify should fail with SHA-256
let hmac = Hmac::new("mysecret", HashFunc::Sha256);
assert!(
!hmac.verify(&value, &sha512_hmac).unwrap(),
"Verification should fail with different hash function"
);
}
}