devolutions_crypto/
argon2parameters.rs

1use std::{
2    convert::TryFrom,
3    io::{Cursor, Read, Write},
4};
5
6use argon2::{Config, Variant, Version};
7use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
8use rand_core::{OsRng, RngCore};
9use typed_builder::TypedBuilder;
10
11#[cfg(feature = "wbindgen")]
12use wasm_bindgen::prelude::*;
13
14use super::Error;
15use super::Result;
16
17pub mod defaults {
18    use argon2::Variant;
19    use argon2::Version;
20    use rand_core::{OsRng, RngCore};
21
22    pub const LENGTH: u32 = 32;
23    pub const LANES: u32 = 1;
24    pub const MEMORY: u32 = 4096;
25    pub const ITERATIONS: u32 = 2;
26    pub const VARIANT: Variant = Variant::Argon2id;
27    pub const VERSION: Version = Version::Version13;
28    pub const DC_VERSION: u32 = 1;
29
30    pub fn salt() -> Vec<u8> {
31        let mut salt = vec![0u8; 16];
32        OsRng.fill_bytes(salt.as_mut_slice());
33        salt
34    }
35}
36
37/// Parameters used to derive the password into an Argon2 hash.
38///
39/// It is used to derive a password into a keypair.
40/// You should use the default, although this may be tweakable by the user in some cases.
41/// Once serialized, you can save it along the user information as it is not sensitive data.
42/// If the hash should never be computed in a non-threaded environment,
43///  you can raise the "lanes" value to enable multi-threading.
44///
45/// Note that calling `default()` will also generate a new random salt,
46///  so two calls to `default()` will not generate the same structure.
47#[cfg_attr(feature = "wbindgen", wasm_bindgen(inspectable))]
48#[derive(Clone, TypedBuilder)]
49pub struct Argon2Parameters {
50    /// Length of the desired hash. Should be 32 in most case.
51    #[builder(default=defaults::LENGTH)]
52    pub length: u32,
53    /// Number of parallel jobs to run. Only use if always computed in a multithreaded environment.
54    #[builder(default=defaults::LANES)]
55    pub lanes: u32,
56    /// Memory used by the algorithm in KiB. Higher is better.
57    #[builder(default=defaults::MEMORY)]
58    pub memory: u32,
59    /// Number of iterations(time cost). Higher is better.
60    #[builder(default=defaults::ITERATIONS)]
61    pub iterations: u32,
62    /// The variant to use. You should almost always use Argon2Id.
63    #[builder(default=defaults::VARIANT)]
64    variant: Variant,
65    /// The version of Argon2 to use. Use the latest.
66    #[builder(default=defaults::VERSION)]
67    version: Version,
68    /// Version of this structure in DevolutionsCrypto.
69    #[builder(default=defaults::DC_VERSION)]
70    dc_version: u32,
71    /// Authenticated but not secret data.
72    #[builder(default)]
73    associated_data: Vec<u8>,
74    /// Secret key to sign the hash. Note that this is not serialized.
75    #[builder(default)]
76    secret_key: Vec<u8>,
77    /// A 16-bytes salt to use that is generated automatically. Should not be accessed directly.
78    #[builder(default = defaults::salt())]
79    salt: Vec<u8>,
80}
81
82impl Argon2Parameters {
83    pub fn get_salt_as_slice(&self) -> &[u8] {
84        self.salt.as_slice()
85    }
86
87    pub fn set_salt(&mut self, salt: Vec<u8>) {
88        self.salt = salt;
89    }
90}
91
92/// Implements the default parameters.
93impl Default for Argon2Parameters {
94    fn default() -> Self {
95        let mut salt = vec![0u8; 16];
96        OsRng.fill_bytes(salt.as_mut_slice());
97
98        Argon2Parameters {
99            associated_data: Vec::new(),
100            secret_key: Vec::new(),
101            length: 32,
102            lanes: 1,
103            memory: 4096,
104            iterations: 2,
105            variant: Variant::Argon2id,
106            version: Version::Version13,
107            dc_version: 1,
108            salt,
109        }
110    }
111}
112
113impl From<&Argon2Parameters> for Vec<u8> {
114    fn from(params: &Argon2Parameters) -> Self {
115        // Data is encoded this way:
116        // All the u32 data -> enums(as u8) -> Vectors(length as u32 + vec))
117        // Note that the secret key is not serialized.
118        // Length is calculated this way:
119        // 5 * u32 + 2 * u8(enums) + 3 *u32(lengths) + 2 * vec.len();
120        let mut data = Vec::with_capacity(
121            5 * 4 + 2 + 2 * 4 + params.associated_data.len() + params.salt.len(),
122        );
123        data.write_u32::<LittleEndian>(params.dc_version).unwrap();
124        data.write_u32::<LittleEndian>(params.length).unwrap();
125        data.write_u32::<LittleEndian>(params.lanes).unwrap();
126        data.write_u32::<LittleEndian>(params.memory).unwrap();
127        data.write_u32::<LittleEndian>(params.iterations).unwrap();
128        data.write_u8(params.variant.as_u32() as u8).unwrap();
129        data.write_u8(params.version.as_u32() as u8).unwrap();
130
131        data.write_u32::<LittleEndian>(params.associated_data.len() as u32)
132            .unwrap();
133
134        data.write_all(&params.associated_data).unwrap();
135
136        data.write_u32::<LittleEndian>(params.salt.len() as u32)
137            .unwrap();
138
139        data.write_all(&params.salt).unwrap();
140
141        data
142    }
143}
144
145impl TryFrom<&[u8]> for Argon2Parameters {
146    type Error = Error;
147
148    fn try_from(data: &[u8]) -> Result<Self> {
149        let mut data_cursor = Cursor::new(data);
150        let dc_version = data_cursor.read_u32::<LittleEndian>()?;
151        let length = data_cursor.read_u32::<LittleEndian>()?;
152        let lanes = data_cursor.read_u32::<LittleEndian>()?;
153        let memory = data_cursor.read_u32::<LittleEndian>()?;
154        let iterations = data_cursor.read_u32::<LittleEndian>()?;
155
156        // Check if the versions works
157        let (variant, version) = match (
158            Variant::from_u32(data_cursor.read_u8()? as u32),
159            Version::from_u32(data_cursor.read_u8()? as u32),
160        ) {
161            (Ok(variant), Ok(version)) => (variant, version),
162            _ => return Err(Error::InvalidData),
163        };
164
165        let associated_data_length = data_cursor.read_u32::<LittleEndian>()? as usize;
166        let remaining = data.len() - (data_cursor.position() as usize);
167        if remaining < associated_data_length {
168            return Err(Error::InvalidLength);
169        }
170
171        let mut associated_data = vec![0u8; associated_data_length];
172        data_cursor.read_exact(&mut associated_data)?;
173
174        let salt_length = data_cursor.read_u32::<LittleEndian>()? as usize;
175        let remaining = data.len() - (data_cursor.position() as usize);
176        if remaining < salt_length {
177            return Err(Error::InvalidLength);
178        }
179
180        let mut salt = vec![0u8; salt_length];
181        data_cursor.read_exact(&mut salt)?;
182
183        Ok(Argon2Parameters {
184            associated_data,
185            secret_key: Vec::new(),
186            length,
187            lanes,
188            memory,
189            iterations,
190            variant,
191            version,
192            dc_version,
193            salt,
194        })
195    }
196}
197
198impl Argon2Parameters {
199    /// Compute the Argon2 hash using the password and the parameters.
200    pub fn compute(&self, password: &[u8]) -> Result<Vec<u8>> {
201        let config = Config {
202            ad: &self.associated_data,
203            secret: &self.secret_key,
204            hash_length: self.length,
205            lanes: self.lanes,
206            mem_cost: self.memory,
207            time_cost: self.iterations,
208            variant: self.variant,
209            version: self.version,
210        };
211
212        Ok(argon2::hash_raw(password, &self.salt, &config)?)
213    }
214}
215
216#[test]
217fn test_argon2() {
218    use std::convert::TryInto;
219
220    let mut config = Argon2Parameters::default();
221    config.iterations = 2;
222    config.memory = 32;
223
224    // Computes the first hash
225    let hash1 = config.compute(b"Password1").unwrap();
226    let config_vec: Vec<u8> = (&config).into();
227
228    assert_ne!(config_vec.len(), 0);
229
230    let config: Argon2Parameters = config_vec.as_slice().try_into().unwrap();
231
232    // Compute a 2nd hash with the same params
233    let hash2 = config.compute(b"Password1").unwrap();
234
235    // Same params, different password.
236    let hash3 = config.compute(b"Password2").unwrap();
237
238    // Same Params, Same password, different salt
239    let mut config = Argon2Parameters::default();
240    config.iterations = 2;
241    config.memory = 32;
242    let hash4 = config.compute(b"Password1").unwrap();
243
244    // Test length params.
245    let mut config5 = Argon2Parameters::default();
246    config5.iterations = 2;
247    config5.memory = 32;
248    config5.length = 41;
249    let hash5 = config5.compute(b"Password1").unwrap();
250
251    assert_eq!(hash1.len(), config.length as usize);
252    assert_eq!(hash1, hash2);
253    assert_ne!(hash1, hash3);
254    assert_ne!(hash1, hash4);
255    assert_eq!(hash5.len(), config5.length as usize);
256}