devolutions_crypto/
argon2parameters.rs1use 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#[cfg_attr(feature = "wbindgen", wasm_bindgen(inspectable))]
48#[derive(Clone, TypedBuilder)]
49pub struct Argon2Parameters {
50 #[builder(default=defaults::LENGTH)]
52 pub length: u32,
53 #[builder(default=defaults::LANES)]
55 pub lanes: u32,
56 #[builder(default=defaults::MEMORY)]
58 pub memory: u32,
59 #[builder(default=defaults::ITERATIONS)]
61 pub iterations: u32,
62 #[builder(default=defaults::VARIANT)]
64 variant: Variant,
65 #[builder(default=defaults::VERSION)]
67 version: Version,
68 #[builder(default=defaults::DC_VERSION)]
70 dc_version: u32,
71 #[builder(default)]
73 associated_data: Vec<u8>,
74 #[builder(default)]
76 secret_key: Vec<u8>,
77 #[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
92impl 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 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(¶ms.associated_data).unwrap();
135
136 data.write_u32::<LittleEndian>(params.salt.len() as u32)
137 .unwrap();
138
139 data.write_all(¶ms.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 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 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 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 let hash2 = config.compute(b"Password1").unwrap();
234
235 let hash3 = config.compute(b"Password2").unwrap();
237
238 let mut config = Argon2Parameters::default();
240 config.iterations = 2;
241 config.memory = 32;
242 let hash4 = config.compute(b"Password1").unwrap();
243
244 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}