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