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
use argon2::Argon2;
use base64::{Engine as _, engine::general_purpose};
use cosmian_crypto_core::reexport::tiny_keccak::{Hasher as _, Sha3};
use sha2::{Digest, Sha256};
use zeroize::{Zeroize, ZeroizeOnDrop};
use super::AnoError;
#[derive(PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub enum HashMethod {
/// Represents the SHA2 hash method with an optional salt.
SHA2(Option<Vec<u8>>),
/// Represents the SHA3 hash method with an optional salt.
SHA3(Option<Vec<u8>>),
/// Represents the Argon2 hash method with a mandatory salt.
Argon2(Vec<u8>),
}
impl HashMethod {
/// `HashMethod` constructor for interfaces
///
/// * `method` - The hash method to use. This can be one of the following:
/// SHA2, SHA3 or Argon2
/// * `salt` - An optional salt to use. Required with Argon2
pub fn new(hasher_method: &str, salt: Option<Vec<u8>>) -> Result<Self, AnoError> {
match hasher_method {
"SHA2" => Ok(Self::SHA2(salt)),
"SHA3" => Ok(Self::SHA3(salt)),
"Argon2" => salt.map_or_else(
|| {
Err(AnoError::AnonymizationError(
"Argon2 requires a salt value.".to_owned(),
))
},
|salt| Ok(Self::Argon2(salt)),
),
_ => Err(AnoError::AnonymizationError(
"Not a valid hash method specified.".to_owned(),
)),
}
}
}
#[derive(ZeroizeOnDrop)]
pub struct Hasher {
method: HashMethod, // The selected hash method
}
impl Hasher {
/// Creates a new `Hasher` instance using the specified hash method and an
/// optional salt.
///
/// # Arguments
///
/// * `method` - The hash method to use. This can be one of the following:
/// * `SHA2` Fast and secure, but vulnerable to brute-force attacks.
/// * `SHA3` Secure and resistant to brute-force attacks, but slower than
/// SHA-256 and not as widely supported.
/// * `Argon2` Highly resistant to brute-force attacks, but can be slower
/// than other hash functions and may require more memory.
#[must_use]
pub const fn new(method: HashMethod) -> Self {
Self { method }
}
/// Applies the chosen hash method to the input data.
///
/// # Arguments
///
/// * `data` - input string to be hashed.
///
/// # Returns
///
/// The base64-encoded hash string.
pub fn apply_str(&self, data: &str) -> Result<String, AnoError> {
let bytes = data.as_bytes();
let hashed_bytes = self.apply_bytes(bytes)?;
Ok(general_purpose::STANDARD.encode(hashed_bytes))
}
/// Applies the chosen hash method to the input data.
///
/// # Arguments
///
/// * `data` - input data to be hashed.
///
/// # Returns
///
/// The hash bytes.
pub fn apply_bytes(&self, data: &[u8]) -> Result<[u8; 32], AnoError> {
match &self.method {
HashMethod::SHA2(salt) => {
let mut hasher = Sha256::new();
if let Some(salt_val) = salt.as_deref() {
hasher.update(salt_val);
}
hasher.update(data);
// Convert hash output to a fixed size array
let output = hasher.finalize().into();
Ok(output)
}
HashMethod::SHA3(salt) => {
let mut hasher = Sha3::v256();
let mut output = [0_u8; 32];
if let Some(salt_val) = salt.as_deref() {
hasher.update(salt_val);
}
hasher.update(data);
hasher.finalize(&mut output);
Ok(output)
}
HashMethod::Argon2(salt) => {
let mut output = [0_u8; 32];
Argon2::default()
.hash_password_into(data, salt, &mut output)
.map_err(|e| AnoError::Argon2(e.to_string()))?;
Ok(output)
}
}
}
}