Skip to main content

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}