argon2_rs/lib.rs
1pub mod error;
2use error::*;
3
4use argon2_sys::{ARGON2_DEFAULT_FLAGS, ARGON2_OUTPUT_TOO_SHORT, argon2_context, argon2_ctx};
5
6#[cfg(feature = "zeroize")]
7use zeroize::Zeroize;
8
9pub const RECOMMENDED_HASH_LENGTH: u32 = 64;
10
11/// Minimum hash length Argon2 accepts, in bytes (the C library's `ARGON2_MIN_OUTLEN`).
12const MIN_HASH_LENGTH: u32 = 4;
13
14/// Argon2 primitive type: variants of the algorithm.
15#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Default, Ord)]
16#[repr(u32)]
17pub enum Algorithm {
18 /// Optimizes against GPU cracking attacks but vulnerable to side-channels.
19 ///
20 /// Accesses the memory array in a password dependent order, reducing the
21 /// possibility of time–memory tradeoff (TMTO) attacks.
22 Argon2d = 0,
23
24 /// Optimized to resist side-channel attacks.
25 ///
26 /// Accesses the memory array in a password independent order, increasing the
27 /// possibility of time-memory tradeoff (TMTO) attacks.
28 Argon2i = 1,
29
30 /// Hybrid that mixes Argon2i and Argon2d passes (*default*).
31 ///
32 /// Uses the Argon2i approach for the first half pass over memory and
33 /// Argon2d approach for subsequent passes. This effectively places it in
34 /// the "middle" between the other two: it doesn't provide as good
35 /// TMTO/GPU cracking resistance as Argon2d, nor as good of side-channel
36 /// resistance as Argon2i, but overall provides the most well-rounded
37 /// approach to both classes of attacks.
38 #[default]
39 Argon2id = 2,
40}
41
42/// Version of the algorithm.
43#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
44#[repr(u32)]
45pub enum Version {
46 /// Version 16 (0x10 in hex)
47 ///
48 /// Performs overwrite internally
49 V0x10 = 0x10,
50
51 /// Version 19 (0x13 in hex, default)
52 ///
53 /// Performs XOR internally
54 #[default]
55 V0x13 = 0x13,
56}
57
58/// Argon2 instance
59///
60/// # Parameters
61///
62/// - `m_cost` - The memory cost in kibibytes
63/// - `t_cost` - Iteration cost
64/// - `p_cost` - Parallelization
65/// - `hash_length` - The length of the hash in bytes
66/// - `algorithm` - The algorithm to use
67/// - `version` - The version of the algorithm to use
68///
69/// By default it will use `Argon2id`, version `0x13` and a `64 byte` hash length.
70///
71/// It is not recommended to change these specific values, they are fine for most use cases.
72///
73/// Generally speaking you don't want to mess with the `t_cost` and `p_cost` parameters a lot.
74///
75/// ## About the `m_cost`, `t_cost` and `p_cost` parameters
76///
77/// ### `m_cost`
78///
79/// You should mostly adjust the `m_cost` if you really want to increase the security of the hash since this is
80/// the major bottleneck for GPUs and ASICs.
81///
82/// Anything from `1024_000` and beyond is considered very secure, if you are paranoid you should increase it
83/// to the max physical RAM of the machine this hash will be computed on.
84///
85/// ### `t_cost`
86/// For most use cases a good value is between `8` and `30`.
87///
88/// Increasing the `t_cost` will increase the time it takes to compute the hash linearly.
89///
90/// 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.
91///
92/// ### `p_cost`
93///
94/// The degree of parallelism (number of lanes and threads). It does **not** change the total
95/// amount of work — it only decides how many lanes that work is spread over, so it shortens the
96/// wall-clock time only when the machine has spare cores to run those lanes on.
97///
98/// For example if the hash takes 10 seconds to compute with `p_cost` set to `1` and you increase it to `2`
99/// it will take roughly half the time, provided a second core is free; on a single-core machine it will not get faster.
100///
101/// Keep in mind increasing the `p_cost` beyond the machine's physical cores will not increase the speed of the hash computation
102/// 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.
103/// For that reason `p_cost` is kept at `1`.
104///
105/// ## Presets
106///
107/// There are some presets for the `Argon2` struct that you can use.
108///
109/// - `Argon2::very_fast()`
110/// - `Argon2::fast()`
111/// - `Argon2::balanced()`
112/// - `Argon2::slow()`
113/// - `Argon2::very_slow()`
114#[derive(Clone, Debug, PartialEq, Eq)]
115pub struct Argon2 {
116 pub m_cost: u32,
117 pub t_cost: u32,
118 pub p_cost: u32,
119 /// The length of the hash in bytes.
120 ///
121 /// Must be at least `4` (`ARGON2_MIN_OUTLEN`) the C library accepts anything up to
122 /// `u32::MAX` (`ARGON2_MAX_OUTLEN`), so the type itself is the upper bound.
123 pub hash_length: u32,
124 /// By default we use the Argon2id
125 pub algorithm: Algorithm,
126 /// By default we use the version 0x13
127 pub version: Version,
128}
129
130impl Default for Argon2 {
131 /// The [`Argon2::very_fast`] preset: `Argon2id`, version `0x13`, a `64 byte` hash length and
132 /// a `128_000` KiB (`128 MiB`) memory cost.
133 ///
134 /// A configuration with every cost set to `0` can never hash anything, so `Default` is a set
135 /// of parameters that actually works, kept cheap enough to use without tuning.
136 ///
137 /// This must stay a struct literal: every preset below (and [`Argon2::new`]) fills its
138 /// remaining fields with `..Default::default()`, so delegating to a preset here would
139 /// recurse forever.
140 fn default() -> Self {
141 Self {
142 m_cost: 128_000,
143 t_cost: 8,
144 p_cost: 1,
145 hash_length: RECOMMENDED_HASH_LENGTH,
146 algorithm: Algorithm::Argon2id,
147 version: Version::V0x13,
148 }
149 }
150}
151
152impl Argon2 {
153 /// Create a new Argon2 instance with the given parameters.
154 ///
155 /// By default it will use the `Argon2id` with a `64 byte` hash length.
156 ///
157 /// ## Arguments
158 ///
159 /// - `m_cost` - The memory cost in kibibytes
160 /// - `t_cost` - Iteration cost
161 /// - `p_cost` - Parallelization
162 pub fn new(m_cost: u32, t_cost: u32, p_cost: u32) -> Self {
163 Self {
164 m_cost,
165 t_cost,
166 p_cost,
167 ..Default::default()
168 }
169 }
170
171 pub fn with_algorithm(mut self, algorithm: Algorithm) -> Self {
172 self.algorithm = algorithm;
173 self
174 }
175
176 pub fn with_version(mut self, version: Version) -> Self {
177 self.version = version;
178 self
179 }
180
181 /// Sets the hash length in bytes.
182 ///
183 /// Values below `4` (`ARGON2_MIN_OUTLEN`) are rejected by [`Argon2::hash_password`] with
184 /// [`Argon2Error::OutputTooShort`].
185 pub fn with_hash_length(mut self, hash_length: u32) -> Self {
186 self.hash_length = hash_length;
187 self
188 }
189
190 /// Hashes the given password
191 ///
192 /// ## Arguments
193 ///
194 /// - `password` - The password to hash
195 /// - `salt` - The salt to use for hashing
196 ///
197 ///
198 /// ## Returns
199 ///
200 /// The hash of the password in its raw byte form
201 pub fn hash_password(&self, password: &str, mut salt: Vec<u8>) -> Result<Vec<u8>, Argon2Error> {
202 let mut hash_buffer = vec![0u8; self.hash_length as usize];
203
204 // Argon2 rejects any output shorter than `ARGON2_MIN_OUTLEN`, so return that error
205 // without crossing the FFI boundary. The `u32` type already keeps the upper bound at
206 // `ARGON2_MAX_OUTLEN`, and a rejected (too short) length cannot make this allocation
207 // large, so the buffer never precedes validation in a harmful way.
208 let code = if self.hash_length < MIN_HASH_LENGTH {
209 ARGON2_OUTPUT_TOO_SHORT
210 } else {
211 let mut context = argon2_context {
212 out: hash_buffer.as_mut_ptr(),
213 outlen: self.hash_length,
214 pwd: password.as_bytes().as_ptr() as *mut u8,
215 pwdlen: password.len() as u32,
216 salt: salt.as_mut_ptr(),
217 saltlen: salt.len() as u32,
218 secret: std::ptr::null_mut(),
219 secretlen: 0,
220 ad: std::ptr::null_mut(),
221 adlen: 0,
222 t_cost: self.t_cost,
223 m_cost: self.m_cost,
224 lanes: self.p_cost,
225 threads: self.p_cost,
226 version: self.version as u32,
227 allocate_cbk: None,
228 free_cbk: None,
229 flags: ARGON2_DEFAULT_FLAGS,
230 };
231
232 // SAFETY: `context` is fully initialised above and every pointer in it stays valid
233 // for the duration of the call: `out` points at `hash_buffer`, which is allocated to
234 // exactly `outlen` bytes; `salt` owns `saltlen` initialised bytes; and `pwd`/`pwdlen`
235 // borrow the live `password` string. `ARGON2_DEFAULT_FLAGS` does not set a wipe flag,
236 // so the C library only reads `pwd` and `salt` and writes at most `outlen` bytes
237 // through `out`.
238 unsafe { argon2_ctx(&mut context, self.algorithm as u32) }
239 };
240
241 #[cfg(feature = "zeroize")]
242 salt.zeroize();
243
244 if code != 0 {
245 return Err(map_argon2_error(code));
246 }
247
248 Ok(hash_buffer)
249 }
250
251 /// Encodes the Argon2 configuration into a byte vector using little-endian byte order.
252 ///
253 /// `hash_length` is written as an 8-byte field, so the encoded form is still 28 bytes long.
254 pub fn encode(&self) -> Vec<u8> {
255 let mut buf = Vec::with_capacity(28);
256 let version = self.version as u32;
257 buf.extend_from_slice(&self.m_cost.to_le_bytes());
258 buf.extend_from_slice(&self.t_cost.to_le_bytes());
259 buf.extend_from_slice(&self.p_cost.to_le_bytes());
260 buf.extend_from_slice(&(self.hash_length as u64).to_le_bytes());
261 buf.extend_from_slice(&(self.algorithm as u32).to_le_bytes());
262 buf.extend_from_slice(&version.to_le_bytes());
263 buf
264 }
265
266 /// Decodes the Argon2 configuration from a byte slice using little-endian byte order.
267 ///
268 /// # Errors
269 ///
270 /// Returns `Error::Argon2(Argon2Error::DecodingFail)` if the data is too short, contains
271 /// invalid enum values, or holds a `hash_length` that does not fit in a `u32`.
272 pub fn decode(data: &[u8]) -> Result<Self, Argon2Error> {
273 if data.len() < 28 {
274 return Err(Argon2Error::DecodingFail);
275 }
276
277 let m_cost = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
278 let t_cost = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
279 let p_cost = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
280 let hash_length_u64 = u64::from_le_bytes([
281 data[12], data[13], data[14], data[15], data[16], data[17], data[18], data[19],
282 ]);
283 let alg_u32 = u32::from_le_bytes([data[20], data[21], data[22], data[23]]);
284 let version_u32 = u32::from_le_bytes([data[24], data[25], data[26], data[27]]);
285
286 let hash_length = u32::try_from(hash_length_u64).map_err(|_| Argon2Error::DecodingFail)?;
287
288 let algorithm = match alg_u32 {
289 0 => Algorithm::Argon2d,
290 1 => Algorithm::Argon2i,
291 2 => Algorithm::Argon2id,
292 _ => return Err(Argon2Error::DecodingFail),
293 };
294
295 let version = match version_u32 {
296 0x10 => Version::V0x10,
297 0x13 => Version::V0x13,
298 _ => return Err(Argon2Error::DecodingFail),
299 };
300
301 Ok(Self {
302 m_cost,
303 t_cost,
304 p_cost,
305 hash_length,
306 algorithm,
307 version,
308 })
309 }
310}
311
312// Argon2 Presets
313impl Argon2 {
314 /// The [`Default`] configuration.
315 pub fn very_fast() -> Self {
316 Self::default()
317 }
318
319 pub fn fast() -> Self {
320 Self {
321 m_cost: 256_000,
322 t_cost: 16,
323 hash_length: RECOMMENDED_HASH_LENGTH,
324 p_cost: 1,
325 ..Default::default()
326 }
327 }
328
329 pub fn balanced() -> Self {
330 Self {
331 m_cost: 1_024_000,
332 t_cost: 8,
333 hash_length: RECOMMENDED_HASH_LENGTH,
334 p_cost: 1,
335 ..Default::default()
336 }
337 }
338
339 pub fn slow() -> Self {
340 Self {
341 m_cost: 2_048_000,
342 t_cost: 8,
343 hash_length: RECOMMENDED_HASH_LENGTH,
344 p_cost: 1,
345 ..Default::default()
346 }
347 }
348
349 pub fn very_slow() -> Self {
350 Self {
351 m_cost: 3_072_000,
352 t_cost: 8,
353 hash_length: RECOMMENDED_HASH_LENGTH,
354 p_cost: 1,
355 ..Default::default()
356 }
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 const SALT: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
365
366 #[test]
367 fn test_argon2() -> Result<(), Argon2Error> {
368 let argon2 = Argon2::very_fast();
369 let hash = argon2.hash_password("password", SALT.to_vec())?;
370 assert_eq!(hash.len(), 64);
371
372 Ok(())
373 }
374
375 #[test]
376 fn test_encode_decode() -> Result<(), Argon2Error> {
377 let argon2 = Argon2::balanced();
378 let encoded = argon2.encode();
379 assert_eq!(encoded.len(), 28);
380 let decoded = Argon2::decode(&encoded)?;
381 assert_eq!(argon2, decoded);
382
383 Ok(())
384 }
385
386 #[test]
387 fn test_default_is_a_usable_configuration() {
388 // `Default` used to leave every cost at zero, which can never hash anything.
389 let expected = Argon2 {
390 m_cost: 128_000,
391 t_cost: 8,
392 p_cost: 1,
393 hash_length: RECOMMENDED_HASH_LENGTH,
394 algorithm: Algorithm::Argon2id,
395 version: Version::V0x13,
396 };
397 assert_eq!(Argon2::default(), expected);
398 }
399
400 #[test]
401 fn test_presets_do_not_recurse() {
402 // Regression guard: `Default` must stay a struct literal. If it delegates to a preset
403 // that fills its fields from `..Default::default()`, every preset recurses forever.
404 for preset in [
405 Argon2::very_fast(),
406 Argon2::fast(),
407 Argon2::balanced(),
408 Argon2::slow(),
409 Argon2::very_slow(),
410 Argon2::new(64, 3, 1),
411 ] {
412 assert_eq!(preset.algorithm, Algorithm::Argon2id);
413 assert_eq!(preset.version, Version::V0x13);
414 assert_eq!(preset.hash_length, RECOMMENDED_HASH_LENGTH);
415 }
416 }
417
418 #[test]
419 fn test_hash_length_bounds() {
420 // Every length below `ARGON2_MIN_OUTLEN` is rejected before entering the C library.
421 for bad in 0..MIN_HASH_LENGTH {
422 let result = Argon2::new(64, 3, 1)
423 .with_hash_length(bad)
424 .hash_password("password", SALT.to_vec());
425 assert_eq!(result, Err(Argon2Error::OutputTooShort));
426 }
427
428 // The minimum valid length hashes and yields exactly that many bytes.
429 let hash = Argon2::new(64, 3, 1)
430 .with_hash_length(MIN_HASH_LENGTH)
431 .hash_password("password", SALT.to_vec())
432 .expect("minimum hash length must hash");
433 assert_eq!(hash.len(), MIN_HASH_LENGTH as usize);
434 }
435
436 #[test]
437 fn test_encode_decode_keeps_the_28_byte_wire_format() {
438 let argon2 = Argon2::default().with_hash_length(96);
439 let encoded = argon2.encode();
440 assert_eq!(encoded.len(), 28);
441 assert_eq!(Argon2::decode(&encoded), Ok(argon2));
442 }
443
444 #[test]
445 fn test_decode_rejects_hash_length_above_u32() {
446 let mut encoded = Argon2::balanced().encode();
447 encoded[12..20].copy_from_slice(&0x1_0000_0000u64.to_le_bytes());
448 assert_eq!(Argon2::decode(&encoded), Err(Argon2Error::DecodingFail));
449 }
450}