argon2_rs/lib.rs
1pub mod error;
2use error::*;
3
4use argon2_sys::{ARGON2_DEFAULT_FLAGS, argon2_context, argon2_ctx};
5
6#[cfg(feature = "zeroize")]
7use zeroize::Zeroize;
8
9pub const RECOMMENDED_HASH_LENGTH: u64 = 64;
10
11/// Argon2 primitive type: variants of the algorithm.
12#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Default, Ord)]
13#[repr(u32)]
14pub enum Algorithm {
15 /// Optimizes against GPU cracking attacks but vulnerable to side-channels.
16 ///
17 /// Accesses the memory array in a password dependent order, reducing the
18 /// possibility of time–memory tradeoff (TMTO) attacks.
19 Argon2d = 0,
20
21 /// Optimized to resist side-channel attacks.
22 ///
23 /// Accesses the memory array in a password independent order, increasing the
24 /// possibility of time-memory tradeoff (TMTO) attacks.
25 Argon2i = 1,
26
27 /// Hybrid that mixes Argon2i and Argon2d passes (*default*).
28 ///
29 /// Uses the Argon2i approach for the first half pass over memory and
30 /// Argon2d approach for subsequent passes. This effectively places it in
31 /// the "middle" between the other two: it doesn't provide as good
32 /// TMTO/GPU cracking resistance as Argon2d, nor as good of side-channel
33 /// resistance as Argon2i, but overall provides the most well-rounded
34 /// approach to both classes of attacks.
35 #[default]
36 Argon2id = 2,
37}
38
39/// Version of the algorithm.
40#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
41#[repr(u32)]
42pub enum Version {
43 /// Version 16 (0x10 in hex)
44 ///
45 /// Performs overwrite internally
46 V0x10 = 0x10,
47
48 /// Version 19 (0x13 in hex, default)
49 ///
50 /// Performs XOR internally
51 #[default]
52 V0x13 = 0x13,
53}
54
55/// Argon2 instance
56///
57/// # Parameters
58///
59/// - `m_cost` - The memory cost in kibibytes
60/// - `t_cost` - Iteration cost
61/// - `p_cost` - Parallelization
62/// - `hash_length` - The length of the hash in bytes
63/// - `algorithm` - The algorithm to use
64/// - `version` - The version of the algorithm to use
65///
66/// By default it will use the `Argon2id` with a `64 byte` hash length (maximum).
67///
68/// It is not recomended to change these specific values, they are fine for most use cases.
69///
70/// Generally speaking you don't want to mess with the `t_cost` and `p_cost` parameters a lot.
71///
72/// ## About the `m_cost`, `t_cost` and `p_cost` parameters
73///
74/// ### `m_cost`
75///
76/// You should mostly adjust the `m_cost` if you really want to increase the security of the hash since this is
77/// the major bottleneck for GPUs and ASICs.
78///
79/// Anything from `1024_000` and beyond is considered very secure, if you are paranoid you should increase it
80/// to the max physical RAM of the machine this hash will be computed on.
81///
82/// ### `t_cost`
83/// For most use cases a good value is between `8` and `30`.
84///
85/// Increasing the `t_cost` will increase the time it takes to compute the hash linearly.
86///
87/// For example if the hash takes 10 seconds to compute with `t_cost` set to `8` and you increase it to `16` it will take roughly twice the time.
88///
89/// ### `p_cost`
90///
91/// For max security the `p_cost` should be set to `1`.
92///
93/// Increasing the `p_cost` will decrease the time it takes to compute the hash linearly.
94///
95/// For example if the hash takes 10 seconds to compute with `p_cost` set to `1` and you increase it to `2` it will take roughly half the time.
96///
97/// Keep in mind increasing the `p_cost` beyond the machine's physical cores will not increase the speed of the hash computation
98/// but in case of a brute-force attack the attacker will be able to use more cores to compute the hash and thus giving him leverage.
99///
100/// ## Presets
101///
102/// There are some presets for the `Argon2` struct that you can use.
103///
104/// - `Argon2::very_fast()`
105/// - `Argon2::fast()`
106/// - `Argon2::balanced()`
107/// - `Argon2::slow()`
108/// - `Argon2::very_slow()`
109#[derive(Default, Clone, Debug, PartialEq, Eq)]
110pub struct Argon2 {
111 pub m_cost: u32,
112 pub t_cost: u32,
113 pub p_cost: u32,
114 pub hash_length: u64,
115 /// By default we use the Argon2id
116 pub algorithm: Algorithm,
117 /// By default we use the version 0x13
118 pub version: Version,
119}
120
121impl Argon2 {
122 /// Create a new Argon2 instance with the given parameters.
123 ///
124 /// By default it will use the `Argon2id` with a `64 byte` hash length.
125 ///
126 /// ## Arguments
127 ///
128 /// - `m_cost` - The memory cost in kibibytes
129 /// - `t_cost` - Iteration cost
130 /// - `p_cost` - Parallelization
131 pub fn new(m_cost: u32, t_cost: u32, p_cost: u32) -> Self {
132 Self {
133 m_cost,
134 t_cost,
135 p_cost,
136 hash_length: RECOMMENDED_HASH_LENGTH,
137 ..Default::default()
138 }
139 }
140
141 pub fn with_algorithm(mut self, algorithm: Algorithm) -> Self {
142 self.algorithm = algorithm;
143 self
144 }
145
146 pub fn with_version(mut self, version: Version) -> Self {
147 self.version = version;
148 self
149 }
150
151 pub fn with_hash_length(mut self, hash_length: u64) -> Self {
152 self.hash_length = hash_length;
153 self
154 }
155
156 /// Hashes the given password
157 ///
158 /// ## Arguments
159 ///
160 /// - `password` - The password to hash
161 /// - `salt` - The salt to use for hashing
162 ///
163 ///
164 /// ## Returns
165 ///
166 /// The hash of the password in its raw byte form
167 pub fn hash_password(&self, password: &str, mut salt: Vec<u8>) -> Result<Vec<u8>, Argon2Error> {
168 let mut hash_buffer = vec![0u8; self.hash_length as usize];
169
170 let mut context = argon2_context {
171 out: hash_buffer.as_mut_ptr(),
172 outlen: self.hash_length as u32,
173 pwd: password.as_bytes().as_ptr() as *mut u8,
174 pwdlen: password.len() as u32,
175 salt: salt.as_mut_ptr(),
176 saltlen: salt.len() as u32,
177 secret: std::ptr::null_mut(),
178 secretlen: 0,
179 ad: std::ptr::null_mut(),
180 adlen: 0,
181 t_cost: self.t_cost,
182 m_cost: self.m_cost,
183 lanes: self.p_cost,
184 threads: self.p_cost,
185 version: self.version as u32,
186 allocate_cbk: None,
187 free_cbk: None,
188 flags: ARGON2_DEFAULT_FLAGS,
189 };
190
191 let code = unsafe { argon2_ctx(&mut context, self.algorithm as u32) };
192
193 #[cfg(feature = "zeroize")]
194 salt.zeroize();
195
196 if code != 0 {
197 return Err(map_argon2_error(code));
198 }
199
200 Ok(hash_buffer)
201 }
202
203 /// Encodes the Argon2 configuration into a byte vector using little-endian byte order.
204 pub fn encode(&self) -> Vec<u8> {
205 let mut buf = Vec::with_capacity(28);
206 let version = self.version as u32;
207 buf.extend_from_slice(&self.m_cost.to_le_bytes());
208 buf.extend_from_slice(&self.t_cost.to_le_bytes());
209 buf.extend_from_slice(&self.p_cost.to_le_bytes());
210 buf.extend_from_slice(&self.hash_length.to_le_bytes());
211 buf.extend_from_slice(&(self.algorithm as u32).to_le_bytes());
212 buf.extend_from_slice(&version.to_le_bytes());
213 buf
214 }
215
216 /// Decodes the Argon2 configuration from a byte slice using little-endian byte order.
217 ///
218 /// # Errors
219 ///
220 /// Returns `Error::Argon2(Argon2Error::DecodingFail)` if the data is too short or contains invalid enum values.
221 pub fn decode(data: &[u8]) -> Result<Self, Argon2Error> {
222 if data.len() < 28 {
223 return Err(Argon2Error::DecodingFail);
224 }
225
226 let m_cost = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
227 let t_cost = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
228 let p_cost = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
229 let hash_length = u64::from_le_bytes([
230 data[12], data[13], data[14], data[15], data[16], data[17], data[18], data[19],
231 ]);
232 let alg_u32 = u32::from_le_bytes([data[20], data[21], data[22], data[23]]);
233 let version_u32 = u32::from_le_bytes([data[24], data[25], data[26], data[27]]);
234
235 let algorithm = match alg_u32 {
236 0 => Algorithm::Argon2d,
237 1 => Algorithm::Argon2i,
238 2 => Algorithm::Argon2id,
239 _ => return Err(Argon2Error::DecodingFail),
240 };
241
242 let version = match version_u32 {
243 0x10 => Version::V0x10,
244 0x13 => Version::V0x13,
245 _ => return Err(Argon2Error::DecodingFail),
246 };
247
248 Ok(Self {
249 m_cost,
250 t_cost,
251 p_cost,
252 hash_length,
253 algorithm,
254 version,
255 })
256 }
257}
258
259// Argon2 Presets
260impl Argon2 {
261 pub fn very_fast() -> Self {
262 Self {
263 m_cost: 128_000,
264 t_cost: 8,
265 p_cost: 1,
266 hash_length: RECOMMENDED_HASH_LENGTH,
267 ..Default::default()
268 }
269 }
270
271 pub fn fast() -> Self {
272 Self {
273 m_cost: 256_000,
274 t_cost: 16,
275 p_cost: 1,
276 hash_length: RECOMMENDED_HASH_LENGTH,
277 ..Default::default()
278 }
279 }
280
281 pub fn balanced() -> Self {
282 Self {
283 m_cost: 1024_000,
284 t_cost: 8,
285 p_cost: 1,
286 hash_length: RECOMMENDED_HASH_LENGTH,
287 ..Default::default()
288 }
289 }
290
291 pub fn slow() -> Self {
292 Self {
293 m_cost: 2048_000,
294 t_cost: 8,
295 p_cost: 1,
296 hash_length: RECOMMENDED_HASH_LENGTH,
297 ..Default::default()
298 }
299 }
300
301 pub fn very_slow() -> Self {
302 Self {
303 m_cost: 3072_000,
304 t_cost: 8,
305 p_cost: 1,
306 hash_length: RECOMMENDED_HASH_LENGTH,
307 ..Default::default()
308 }
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315
316 #[test]
317 fn test_argon2() -> Result<(), Argon2Error> {
318 let argon2 = Argon2::very_fast();
319 let salt = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
320 let hash = argon2.hash_password("password", salt)?;
321 assert_eq!(hash.len(), 64);
322
323 Ok(())
324 }
325
326 #[test]
327 fn test_encode_decode() -> Result<(), Argon2Error> {
328 let argon2 = Argon2::balanced();
329 let encoded = argon2.encode();
330 assert_eq!(encoded.len(), 28);
331 let decoded = Argon2::decode(&encoded)?;
332 assert_eq!(argon2, decoded);
333
334 Ok(())
335 }
336}