Skip to main content

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}