1 // Copyright (c) 2017 Martijn Rijkeboer <mrr@sru-systems.com>
2 //
3 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6 // option. This file may not be copied, modified, or distributed
7 // except according to those terms.
8
9 use crate::context::Context;
10 use crate::decoded::Decoded;
11 use crate::error::Error;
12 use crate::result::Result;
13 use crate::variant::Variant;
14 use crate::version::Version;
15 use base64;
16
17 /// Structure containing the options.
18 struct Options {
19 mem_cost: u32,
20 time_cost: u32,
21 parallelism: u32,
22 }
23
24 /// Gets the base64 encoded length of a byte slice with the specified length.
base64_len(length: u32) -> u3225 pub fn base64_len(length: u32) -> u32 {
26 let olen = (length / 3) << 2;
27 match length % 3 {
28 2 => olen + 3,
29 1 => olen + 2,
30 _ => olen,
31 }
32 }
33
34 /// Attempts to decode the encoded string slice.
decode_string(encoded: &str) -> Result<Decoded>35 pub fn decode_string(encoded: &str) -> Result<Decoded> {
36 let items: Vec<&str> = encoded.split('$').collect();
37 if items.len() == 6 {
38 decode_empty(items[0])?;
39 let variant = decode_variant(items[1])?;
40 let version = decode_version(items[2])?;
41 let options = decode_options(items[3])?;
42 let salt = base64::decode(items[4])?;
43 let hash = base64::decode(items[5])?;
44
45 Ok(Decoded {
46 variant,
47 version,
48 mem_cost: options.mem_cost,
49 time_cost: options.time_cost,
50 parallelism: options.parallelism,
51 salt,
52 hash,
53 })
54 } else if items.len() == 5 {
55 decode_empty(items[0])?;
56 let variant = decode_variant(items[1])?;
57 let options = decode_options(items[2])?;
58 let salt = base64::decode(items[3])?;
59 let hash = base64::decode(items[4])?;
60
61 Ok(Decoded {
62 variant,
63 version: Version::Version10,
64 mem_cost: options.mem_cost,
65 time_cost: options.time_cost,
66 parallelism: options.parallelism,
67 salt,
68 hash,
69 })
70 } else {
71 Err(Error::DecodingFail)
72 }
73 }
74
decode_empty(str: &str) -> Result<()>75 fn decode_empty(str: &str) -> Result<()> {
76 if str == "" {
77 Ok(())
78 } else {
79 Err(Error::DecodingFail)
80 }
81 }
82
decode_options(str: &str) -> Result<Options>83 fn decode_options(str: &str) -> Result<Options> {
84 let items: Vec<&str> = str.split(',').collect();
85 if items.len() == 3 {
86 Ok(Options {
87 mem_cost: decode_option(items[0], "m")?,
88 time_cost: decode_option(items[1], "t")?,
89 parallelism: decode_option(items[2], "p")?,
90 })
91 } else {
92 Err(Error::DecodingFail)
93 }
94 }
95
decode_option(str: &str, name: &str) -> Result<u32>96 fn decode_option(str: &str, name: &str) -> Result<u32> {
97 let items: Vec<&str> = str.split('=').collect();
98 if items.len() == 2 {
99 if items[0] == name {
100 decode_u32(items[1])
101 } else {
102 Err(Error::DecodingFail)
103 }
104 } else {
105 Err(Error::DecodingFail)
106 }
107 }
108
decode_u32(str: &str) -> Result<u32>109 fn decode_u32(str: &str) -> Result<u32> {
110 match str.parse() {
111 Ok(i) => Ok(i),
112 Err(_) => Err(Error::DecodingFail),
113 }
114 }
115
decode_variant(str: &str) -> Result<Variant>116 fn decode_variant(str: &str) -> Result<Variant> {
117 Variant::from_str(str)
118 }
119
decode_version(str: &str) -> Result<Version>120 fn decode_version(str: &str) -> Result<Version> {
121 let items: Vec<&str> = str.split('=').collect();
122 if items.len() == 2 {
123 if items[0] == "v" {
124 Version::from_str(items[1])
125 } else {
126 Err(Error::DecodingFail)
127 }
128 } else {
129 Err(Error::DecodingFail)
130 }
131 }
132
133 /// Encodes the hash and context.
encode_string(context: &Context, hash: &[u8]) -> String134 pub fn encode_string(context: &Context, hash: &[u8]) -> String {
135 format!(
136 "${}$v={}$m={},t={},p={}${}${}",
137 context.config.variant,
138 context.config.version,
139 context.config.mem_cost,
140 context.config.time_cost,
141 context.config.lanes,
142 base64::encode_config(context.salt, base64::STANDARD_NO_PAD),
143 base64::encode_config(hash, base64::STANDARD_NO_PAD),
144 )
145 }
146
147 /// Gets the string length of the specified number.
num_len(number: u32) -> u32148 pub fn num_len(number: u32) -> u32 {
149 let mut len = 1;
150 let mut num = number;
151 while num >= 10 {
152 len += 1;
153 num /= 10;
154 }
155 len
156 }
157
158 #[cfg(test)]
159 mod tests {
160
161 #[cfg(feature = "crossbeam-utils")]
162 use crate::config::Config;
163 #[cfg(feature = "crossbeam-utils")]
164 use crate::context::Context;
165 use crate::decoded::Decoded;
166 #[cfg(feature = "crossbeam-utils")]
167 use crate::encoding::encode_string;
168 use crate::encoding::{base64_len, decode_string, num_len};
169 use crate::error::Error;
170 #[cfg(feature = "crossbeam-utils")]
171 use crate::thread_mode::ThreadMode;
172 use crate::variant::Variant;
173 use crate::version::Version;
174
175 #[test]
base64_len_returns_correct_length()176 fn base64_len_returns_correct_length() {
177 let tests = vec![
178 (1, 2),
179 (2, 3),
180 (3, 4),
181 (4, 6),
182 (5, 7),
183 (6, 8),
184 (7, 10),
185 (8, 11),
186 (9, 12),
187 (10, 14),
188 ];
189 for (len, expected) in tests {
190 let actual = base64_len(len);
191 assert_eq!(actual, expected);
192 }
193 }
194
195 #[test]
decode_string_with_version10_returns_correct_result()196 fn decode_string_with_version10_returns_correct_result() {
197 let encoded = "$argon2i$v=16$m=4096,t=3,p=1\
198 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
199 let expected = Decoded {
200 variant: Variant::Argon2i,
201 version: Version::Version10,
202 mem_cost: 4096,
203 time_cost: 3,
204 parallelism: 1,
205 salt: b"salt1234".to_vec(),
206 hash: b"12345678901234567890123456789012".to_vec(),
207 };
208 let actual = decode_string(encoded).unwrap();
209 assert_eq!(actual, expected);
210 }
211
212 #[test]
decode_string_with_version13_returns_correct_result()213 fn decode_string_with_version13_returns_correct_result() {
214 let encoded = "$argon2i$v=19$m=4096,t=3,p=1\
215 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
216 let expected = Decoded {
217 variant: Variant::Argon2i,
218 version: Version::Version13,
219 mem_cost: 4096,
220 time_cost: 3,
221 parallelism: 1,
222 salt: b"salt1234".to_vec(),
223 hash: b"12345678901234567890123456789012".to_vec(),
224 };
225 let actual = decode_string(encoded).unwrap();
226 assert_eq!(actual, expected);
227 }
228
229 #[test]
decode_string_without_version_returns_correct_result()230 fn decode_string_without_version_returns_correct_result() {
231 let encoded = "$argon2i$m=4096,t=3,p=1\
232 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
233 let expected = Decoded {
234 variant: Variant::Argon2i,
235 version: Version::Version10,
236 mem_cost: 4096,
237 time_cost: 3,
238 parallelism: 1,
239 salt: b"salt1234".to_vec(),
240 hash: b"12345678901234567890123456789012".to_vec(),
241 };
242 let actual = decode_string(encoded).unwrap();
243 assert_eq!(actual, expected);
244 }
245
246 #[test]
decode_string_without_variant_returns_error_result()247 fn decode_string_without_variant_returns_error_result() {
248 let encoded = "$m=4096,t=3,p=1\
249 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
250 let result = decode_string(encoded);
251 assert_eq!(result, Err(Error::DecodingFail));
252 }
253
254 #[test]
decode_string_with_empty_variant_returns_error_result()255 fn decode_string_with_empty_variant_returns_error_result() {
256 let encoded = "$$m=4096,t=3,p=1\
257 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
258 let result = decode_string(encoded);
259 assert_eq!(result, Err(Error::DecodingFail));
260 }
261
262 #[test]
decode_string_with_invalid_variant_returns_error_result()263 fn decode_string_with_invalid_variant_returns_error_result() {
264 let encoded = "$argon$m=4096,t=3,p=1\
265 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
266 let result = decode_string(encoded);
267 assert_eq!(result, Err(Error::DecodingFail));
268 }
269
270 #[test]
decode_string_without_mem_cost_returns_error_result()271 fn decode_string_without_mem_cost_returns_error_result() {
272 let encoded = "$argon2i$t=3,p=1\
273 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
274 let result = decode_string(encoded);
275 assert_eq!(result, Err(Error::DecodingFail));
276 }
277
278 #[test]
decode_string_with_empty_mem_cost_returns_error_result()279 fn decode_string_with_empty_mem_cost_returns_error_result() {
280 let encoded = "$argon2i$m=,t=3,p=1\
281 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
282 let result = decode_string(encoded);
283 assert_eq!(result, Err(Error::DecodingFail));
284 }
285
286 #[test]
decode_string_with_non_numeric_mem_cost_returns_error_result()287 fn decode_string_with_non_numeric_mem_cost_returns_error_result() {
288 let encoded = "$argon2i$m=a,t=3,p=1\
289 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
290 let result = decode_string(encoded);
291 assert_eq!(result, Err(Error::DecodingFail));
292 }
293
294 #[test]
decode_string_without_time_cost_returns_error_result()295 fn decode_string_without_time_cost_returns_error_result() {
296 let encoded = "$argon2i$m=4096,p=1\
297 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
298 let result = decode_string(encoded);
299 assert_eq!(result, Err(Error::DecodingFail));
300 }
301
302 #[test]
decode_string_with_empty_time_cost_returns_error_result()303 fn decode_string_with_empty_time_cost_returns_error_result() {
304 let encoded = "$argon2i$m=4096,t=,p=1\
305 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
306 let result = decode_string(encoded);
307 assert_eq!(result, Err(Error::DecodingFail));
308 }
309
310 #[test]
decode_string_with_non_numeric_time_cost_returns_error_result()311 fn decode_string_with_non_numeric_time_cost_returns_error_result() {
312 let encoded = "$argon2i$m=4096,t=a,p=1\
313 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
314 let result = decode_string(encoded);
315 assert_eq!(result, Err(Error::DecodingFail));
316 }
317
318 #[test]
decode_string_without_parallelism_returns_error_result()319 fn decode_string_without_parallelism_returns_error_result() {
320 let encoded = "$argon2i$m=4096,t=3\
321 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
322 let result = decode_string(encoded);
323 assert_eq!(result, Err(Error::DecodingFail));
324 }
325
326 #[test]
decode_string_with_empty_parallelism_returns_error_result()327 fn decode_string_with_empty_parallelism_returns_error_result() {
328 let encoded = "$argon2i$m=4096,t=3,p=\
329 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
330 let result = decode_string(encoded);
331 assert_eq!(result, Err(Error::DecodingFail));
332 }
333
334 #[test]
decode_string_with_non_numeric_parallelism_returns_error_result()335 fn decode_string_with_non_numeric_parallelism_returns_error_result() {
336 let encoded = "$argon2i$m=4096,t=3,p=a\
337 $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
338 let result = decode_string(encoded);
339 assert_eq!(result, Err(Error::DecodingFail));
340 }
341
342 #[test]
decode_string_without_salt_returns_error_result()343 fn decode_string_without_salt_returns_error_result() {
344 let encoded = "$argon2i$m=4096,t=3,p=1\
345 $MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=";
346 let result = decode_string(encoded);
347 assert_eq!(result, Err(Error::DecodingFail));
348 }
349
350 #[test]
decode_string_without_hash_returns_error_result()351 fn decode_string_without_hash_returns_error_result() {
352 let encoded = "$argon2i$m=4096,t=3,p=a\
353 $c2FsdDEyMzQ=";
354 let result = decode_string(encoded);
355 assert_eq!(result, Err(Error::DecodingFail));
356 }
357
358 #[test]
decode_string_with_empty_hash_returns_error_result()359 fn decode_string_with_empty_hash_returns_error_result() {
360 let encoded = "$argon2i$m=4096,t=3,p=a\
361 $c2FsdDEyMzQ=$";
362 let result = decode_string(encoded);
363 assert_eq!(result, Err(Error::DecodingFail));
364 }
365
366 #[cfg(feature = "crossbeam-utils")]
367 #[test]
encode_string_returns_correct_string()368 fn encode_string_returns_correct_string() {
369 let hash = b"12345678901234567890123456789012".to_vec();
370 let config = Config {
371 ad: &[],
372 hash_length: hash.len() as u32,
373 lanes: 1,
374 mem_cost: 4096,
375 secret: &[],
376 thread_mode: ThreadMode::Parallel,
377 time_cost: 3,
378 variant: Variant::Argon2i,
379 version: Version::Version13,
380 };
381 let pwd = b"password".to_vec();
382 let salt = b"salt1234".to_vec();
383 let context = Context::new(config, &pwd, &salt).unwrap();
384 let expected = "$argon2i$v=19$m=4096,t=3,p=1\
385 $c2FsdDEyMzQ$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI";
386 let actual = encode_string(&context, &hash);
387 assert_eq!(actual, expected);
388 }
389
390 #[test]
num_len_returns_correct_length()391 fn num_len_returns_correct_length() {
392 let tests = vec![
393 (1, 1),
394 (10, 2),
395 (110, 3),
396 (1230, 4),
397 (12340, 5),
398 (123457, 6),
399 ];
400 for (num, expected) in tests {
401 let actual = num_len(num);
402 assert_eq!(actual, expected);
403 }
404 }
405 }
406