argon2_rust/encoding.rs
1//! Base64 (standard alphabet, **no** `=` padding) and the PHC hash string.
2//!
3//! Format: `$argon2<T>[$v=<num>]$m=<num>,t=<num>,p=<num>$<b64 salt>$<b64 hash>`.
4//!
5//! Ported line by line from `phc-winner-argon2/src/encoding.c`
6//! (`b64_byte_to_char`, `b64_char_to_byte`, `to_base64`, `from_base64`,
7//! `decode_decimal`, `encode_string`, `decode_string`, `b64len`, `numlen`) plus
8//! `argon2_encodedlen` from `src/argon2.c`.
9//!
10//! Details the C reference gets subtly right, and this port does too:
11//!
12//! * The `$v=` field is **optional**. When absent the version defaults to
13//! `0x10`, not `0x13`.
14//! * After decoding, `threads = lanes`.
15//! * `"argon2i"` is a prefix of `"argon2id"`. The C matches the type string and
16//! then relies on the *next* character failing to parse (`$` is expected), so
17//! `"$argon2id$..."` is rejected when the caller asked for `Argon2i`.
18//! Reproduced here rather than special-cased.
19//! * Decoding stops at the first non-base64 character, and then **rejects** if
20//! `acc_len > 4` or any buffered low bits are non-zero.
21//! * `b64_char_to_byte` returns `0xFF` for an invalid character; a computed
22//! value of 0 is only valid for `'A'`.
23//! * `decode_decimal` rejects an empty run of digits and rejects non-minimal
24//! encodings (a leading `'0'` followed by more digits).
25//! * [`decode_string`] finishes by running the full `validate_inputs()`, and
26//! only then requires the string to be fully consumed. The order matters: a
27//! string with both a zero-length salt *and* trailing junk reports
28//! [`Error::SaltTooShort`], exactly like the C.
29//!
30//! The character classification is branch-free, as in the C: the `EQ`/`GT`/
31//! `GE`/`LT`/`LE` macros become the [`eq`]/[`gt`]/[`ge`]/[`lt`]/[`le`] helpers
32//! below, which return `0x00` for false and `0xFF` for true without branching.
33//! It matters because these run over salt and tag material.
34//!
35//! # Known divergences from the C reference
36//!
37//! * **Unrepresentable versions.** `validate_inputs()` never looks at
38//! `ctx->version` (`core.c:388`), so the C accepts `$v=99`. It then applies
39//! the `0x13` *fill rule* to anything that is not `0x10` (`ref.c:181`) — but
40//! the raw value is also hashed into H0 (`core.c:561`
41//! `store32(&value, context->version)`), so the tag is version-specific and
42//! `$v=99` is a self-consistent, verifiable C record rather than an alias for
43//! `$v=19`. Measured against `libargon2.a`: `version=99` yields tag
44//! `2d4d864d…` where `version=19` yields `c1628832…`. [`Version`] is a closed
45//! enum, so [`decode_string`] reports [`Error::DecodingFail`] instead. This
46//! is the one divergence reachable from a conforming ASCII PHC string, and
47//! only from a producer emitting a version other than 16 or 19. The check is
48//! deliberately the *last* thing it does, so every other error code still
49//! matches the C.
50//! * **Embedded NULs.** The C stops at the first `'\0'`; a Rust `&str` has no
51//! terminator, so the whole slice must be consumed. This is strictly
52//! stricter: it only rejects strings the C would have accepted by truncating.
53//! * **Raw and non-ASCII bytes.** The C decoder takes an arbitrary `char *`
54//! byte string, while [`decode_string`] takes `&str`, so malformed UTF-8
55//! cannot reach this API. The C also has a signed-`char` bug in
56//! `b64_char_to_byte(*src)`: on Apple, x86 Linux and MSVC every byte
57//! `>= 0x80` sign-extends to a negative `int`, and `EQ`, which its own
58//! comment says is only valid "over values in the 0..255 range", then reports
59//! a spurious match against both `'+'` and `'/'`. Every such byte therefore
60//! decodes as 63 instead of being rejected. Measured on
61//! `aarch64-apple-darwin` against
62//! `libargon2.a`: `"$argon2i$v=19$m=65536,t=2,p=1$é9tZXNhbHQ$<tag>"` and
63//! `"…$//9tZXNhbHQ$<tag>"` both decode, to the same salt `ff ff 6d 65 …`.
64//! Replacing the first `/` with any single byte `0x80..=0xff`, including
65//! malformed UTF-8, also verifies the same record on that target.
66//! Where `char` is unsigned (aarch64 Linux) the same byte is rejected. This
67//! port always rejects, which matches the unsigned-`char` platforms and the
68//! evident intent, and is unreachable for any string the encoder produced.
69//! See `b64_char_to_byte_rejects_everything_else` below.
70
71use alloc::string::String;
72use alloc::vec::Vec;
73
74use crate::base64::Base64Backend;
75use crate::error::Error;
76use crate::params::{Algorithm, Params, Version, validate_inputs};
77
78// ---------------------------------------------------------------------------
79// Constant-time classification (encoding.c lines 74-78)
80// ---------------------------------------------------------------------------
81//
82// #define EQ(x, y) ((((0U - ((unsigned)(x) ^ (unsigned)(y))) >> 8) & 0xFF) ^ 0xFF)
83// #define GT(x, y) ((((unsigned)(y) - (unsigned)(x)) >> 8) & 0xFF)
84// #define GE(x, y) (GT(y, x) ^ 0xFF)
85// #define LT(x, y) GT(y, x)
86// #define LE(x, y) GE(y, x)
87//
88// Defined over 0..=255, returning 0x00 for false and 0xFF for true. The C
89// evaluates them in `unsigned` with wrapping arithmetic, hence `wrapping_sub`.
90
91/// `EQ(x, y)`: `0xFF` when `x == y`, else `0x00`.
92#[inline]
93const fn eq(x: u32, y: u32) -> u32 {
94 ((0u32.wrapping_sub(x ^ y) >> 8) & 0xFF) ^ 0xFF
95}
96
97/// `GT(x, y)`: `0xFF` when `x > y`, else `0x00`.
98#[inline]
99const fn gt(x: u32, y: u32) -> u32 {
100 (y.wrapping_sub(x) >> 8) & 0xFF
101}
102
103/// `GE(x, y)`: `0xFF` when `x >= y`, else `0x00`.
104#[inline]
105const fn ge(x: u32, y: u32) -> u32 {
106 gt(y, x) ^ 0xFF
107}
108
109/// `LT(x, y)`: `0xFF` when `x < y`, else `0x00`.
110#[inline]
111const fn lt(x: u32, y: u32) -> u32 {
112 gt(y, x)
113}
114
115/// `LE(x, y)`: `0xFF` when `x <= y`, else `0x00`.
116#[inline]
117const fn le(x: u32, y: u32) -> u32 {
118 ge(y, x)
119}
120
121/// `b64_byte_to_char(x)`: map `0..64` to the standard base64 alphabet.
122///
123/// Branch-free, like the C. `x` is always masked to 6 bits by the caller.
124#[inline]
125const fn b64_byte_to_char(x: u32) -> u8 {
126 // `'a' - 26` and `'0' - 52` are evaluated in the C as `int`; the second one
127 // is negative (-4), which `wrapping_add` reproduces.
128 let a_off = b'A' as u32;
129 let lower_off = (b'a' as u32).wrapping_sub(26);
130 let digit_off = (b'0' as u32).wrapping_sub(52);
131
132 let c = (lt(x, 26) & x.wrapping_add(a_off))
133 | (ge(x, 26) & lt(x, 52) & x.wrapping_add(lower_off))
134 | (ge(x, 52) & lt(x, 62) & x.wrapping_add(digit_off))
135 | (eq(x, 62) & b'+' as u32)
136 | (eq(x, 63) & b'/' as u32);
137 c as u8
138}
139
140/// `b64_char_to_byte(c)`: map a base64 character to its 6-bit value.
141///
142/// Returns `0xFF` for anything that is not a base64 character. Note the final
143/// fixup: a computed value of 0 is only accepted for `'A'`, so every invalid
144/// character (which also computes 0) is turned into `0xFF`.
145///
146/// `c` is always a `u8` widened to `u32`, i.e. `0..=255`, which is the range
147/// the `EQ`/`GE`/`LE` macros are defined over. The C instead passes a `char`,
148/// which sign-extends on most targets and makes bytes `>= 0x80` decode as 63
149/// rather than being rejected — see the module docs.
150#[inline]
151const fn b64_char_to_byte(c: u32) -> u32 {
152 let a_off = b'A' as u32;
153 let lower_off = (b'a' as u32).wrapping_sub(26);
154 let digit_off = (b'0' as u32).wrapping_sub(52);
155
156 let x = (ge(c, b'A' as u32) & le(c, b'Z' as u32) & c.wrapping_sub(a_off))
157 | (ge(c, b'a' as u32) & le(c, b'z' as u32) & c.wrapping_sub(lower_off))
158 | (ge(c, b'0' as u32) & le(c, b'9' as u32) & c.wrapping_sub(digit_off))
159 | (eq(c, b'+' as u32) & 62)
160 | (eq(c, b'/' as u32) & 63);
161
162 x | (eq(x, 0) & (eq(c, b'A' as u32) ^ 0xFF))
163}
164
165// ---------------------------------------------------------------------------
166// Lengths
167// ---------------------------------------------------------------------------
168
169/// `b64len(len)`: length of the unpadded base64 encoding of `len` bytes.
170///
171/// ```text
172/// olen = (len / 3) << 2;
173/// switch (len % 3) { case 2: olen++; /* fall through */ case 1: olen += 2; }
174/// ```
175///
176/// # 32-bit targets
177///
178/// On a target where `usize` is 32 bits, a `len` near `u32::MAX` makes
179/// `(len / 3) << 2` discard its top bits, so the answer wraps. That is not a
180/// defect to fix: `encoding.c:441` computes `((size_t)len / 3) << 2` and wraps
181/// identically where `size_t` is 32 bits, and this function exists to match the
182/// C. It cannot panic (`<<` discards bits rather than trapping, and the sums in
183/// [`encoded_len`] stay inside `usize` even after a wrap), and it is not on the
184/// allocation path — [`encode_string_alloc`] sizes its buffer with
185/// `encoded_len_usize`, which takes real slice lengths and cannot wrap.
186#[must_use]
187pub const fn b64_len(len: u32) -> usize {
188 b64_len_usize(len as usize)
189}
190
191/// [`b64_len`] over a `usize`, for slices.
192///
193/// Cannot overflow **when fed a slice length**, which is every caller: a slice
194/// is at most `isize::MAX` bytes, and `(isize::MAX / 3) * 4 < usize::MAX` on
195/// both 32- and 64-bit targets. The `u32` entry point above has no such bound —
196/// see its note.
197#[inline]
198const fn b64_len_usize(len: usize) -> usize {
199 let mut olen = (len / 3) << 2;
200 // The C's `case 2` falls through into `case 1`, so it adds 1 + 2.
201 match len % 3 {
202 2 => olen += 3,
203 1 => olen += 2,
204 _ => {}
205 }
206 olen
207}
208
209/// `numlen(num)`: number of decimal digits in `num` (1 for 0).
210#[must_use]
211pub const fn num_len(num: u32) -> usize {
212 let mut len = 1usize;
213 let mut n = num;
214 while n >= 10 {
215 len += 1;
216 n /= 10;
217 }
218 len
219}
220
221/// `argon2_encodedlen(...)` from `src/argon2.c`.
222///
223/// ```text
224/// strlen("$$v=$m=,t=,p=$$") + strlen(type) + numlen(t_cost) + numlen(m_cost)
225/// + numlen(parallelism) + b64len(saltlen) + b64len(hashlen)
226/// + numlen(ARGON2_VERSION_NUMBER) + 1
227/// ```
228///
229/// The trailing `+ 1` is the C string's NUL terminator. It is kept so the value
230/// matches the C byte for byte; a Rust [`String`] is one byte shorter. It is
231/// also exactly the buffer size `encode_string` wants, which reserves the
232/// same byte (see there).
233///
234/// Note the C uses `numlen(ARGON2_VERSION_NUMBER)` and not the version actually
235/// being encoded. Both `0x10` (16) and `0x13` (19) are two digits, so it makes
236/// no difference; it is kept verbatim. (In the C it *can* differ, because
237/// `ctx->version` is a raw `uint32_t`: with `version = 0` the string is one
238/// byte shorter than advertised, and a buffer of `argon2_encodedlen() - 1`
239/// suffices. Measured on 1051 of 30000 fuzzed cases, all of them `version = 0`.
240/// [`Version`] is a closed enum of two two-digit values, so the size is always
241/// exact here — see `encode_needs_encoded_len_bytes_exactly`.)
242///
243/// # Argument order
244///
245/// `t_cost` comes before `m_cost` here, which is the reverse of
246/// [`Params::new`]:
247///
248/// ```text
249/// Params::new(m_cost, t_cost, lanes, output_len)
250/// encoded_len(algorithm, t_cost, m_cost, lanes, salt_len, hash_len)
251/// ^^^^^^^^^^^^^^ reversed against Params::new
252/// ```
253///
254/// The order is the C's, kept so a call can be transcribed position for
255/// position: `argon2_encodedlen(t_cost, m_cost, parallelism, saltlen, hashlen,
256/// type)`, declared at `argon2.h:429` and defined at `argon2.c:447`. One
257/// argument did move. `type` went from last to first and became `algorithm`.
258/// The other five kept their order among themselves; `parallelism` is spelled
259/// `lanes` here, the name [`Params`] uses for it.
260///
261/// `algorithm` comes first here and on the rest of the encode-and-construct
262/// side; the verify family keeps the C's trailing `type` and takes it last.
263///
264/// ```
265/// use argon2_rust::{Algorithm, Params, encoded_len};
266///
267/// // Params::new takes m_cost first.
268/// let params = Params::new(65536, 3, 1, 32).unwrap();
269/// assert_eq!((params.m_cost(), params.t_cost()), (65536, 3));
270///
271/// // encoded_len takes t_cost first: the same two costs, the other way round.
272/// let n = encoded_len(
273/// Algorithm::Argon2id,
274/// params.t_cost(),
275/// params.m_cost(),
276/// params.lanes(),
277/// 16, // salt_len
278/// 32, // hash_len
279/// );
280/// assert_eq!(n, 98);
281/// ```
282///
283/// # Against a string the crate really produced
284///
285/// The value is a C buffer size, so it counts the NUL terminator described
286/// above and a Rust [`String`] is one byte shorter. That is the whole of the
287/// relationship, and it is exact rather than an upper bound - see
288/// `encode_needs_encoded_len_bytes_exactly`:
289///
290/// ```
291/// use argon2_rust::{Algorithm, Argon2, Params, Version, encoded_len};
292///
293/// let params = Params::new(64, 1, 1, 32)?;
294/// let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
295/// let encoded = argon2.hash_encoded(b"password", b"somesalt")?;
296///
297/// // `salt_len` is the salt's own 8 bytes, not the 11 its base64 occupies.
298/// let n = encoded_len(Algorithm::Argon2id, 1, 64, 1, 8, 32);
299/// assert_eq!(n, 84);
300/// assert_eq!(encoded.len(), n - 1);
301/// assert_eq!(
302/// encoded,
303/// "$argon2id$v=19$m=64,t=1,p=1$c29tZXNhbHQ$cpx6VEQbwTVZvcpxNIxOVUWZ5xnAipUmAe1cg2GMG70",
304/// );
305/// # Ok::<(), argon2_rust::Error>(())
306/// ```
307#[must_use]
308pub fn encoded_len(
309 algorithm: Algorithm,
310 t_cost: u32,
311 m_cost: u32,
312 lanes: u32,
313 salt_len: u32,
314 hash_len: u32,
315) -> usize {
316 "$$v=$m=,t=,p=$$".len()
317 + algorithm.as_str().len()
318 + num_len(t_cost)
319 + num_len(m_cost)
320 + num_len(lanes)
321 + b64_len(salt_len)
322 + b64_len(hash_len)
323 + num_len(Version::DEFAULT.as_u32())
324 + 1
325}
326
327/// [`encoded_len`] computed from slice lengths, without the `u32` casts.
328///
329/// [`encode_string_alloc`] sizes its buffer with this so a salt longer than
330/// `u32::MAX` cannot silently wrap into a too-small allocation.
331fn encoded_len_usize(
332 algorithm: Algorithm,
333 t_cost: u32,
334 m_cost: u32,
335 lanes: u32,
336 salt_len: usize,
337 hash_len: usize,
338) -> usize {
339 "$$v=$m=,t=,p=$$".len()
340 + algorithm.as_str().len()
341 + num_len(t_cost)
342 + num_len(m_cost)
343 + num_len(lanes)
344 + b64_len_usize(salt_len)
345 + b64_len_usize(hash_len)
346 + num_len(Version::DEFAULT.as_u32())
347 + 1
348}
349
350// ---------------------------------------------------------------------------
351// Base64
352// ---------------------------------------------------------------------------
353
354/// `to_base64(dst, dst_len, src, src_len)`.
355///
356/// Writes the unpadded base64 of `src` into `dst` and returns how many bytes
357/// were written. No NUL terminator is written (the C writes one; Rust does not
358/// need it), but the capacity check is kept identical: the C requires
359/// `dst_len > olen`, so this requires `dst.len() > b64_len(src.len())`.
360///
361/// # Errors
362///
363/// [`Error::EncodingFail`] if `dst` is too small.
364pub fn to_base64(dst: &mut [u8], src: &[u8]) -> Result<usize, Error> {
365 if src.len() < crate::base64::MIN_ENCODE_LEN {
366 // Keep backend lookup and the generalized prefix state completely out
367 // of tiny salts. This is the original scalar function's exact shape.
368 return to_base64_scalar(dst, src);
369 }
370 let backend = crate::base64::base64_backend();
371 // SAFETY: runtime detection returns only an executable backend.
372 unsafe { to_base64_with_backend(dst, src, backend) }
373}
374
375/// The original reference-C loop, kept whole so short inputs do not pay for or
376/// inhibit optimization around a SIMD prefix they cannot use.
377#[inline(always)]
378fn to_base64_scalar(dst: &mut [u8], src: &[u8]) -> Result<usize, Error> {
379 let olen = b64_len_usize(src.len());
380 if dst.len() <= olen {
381 return Err(Error::EncodingFail);
382 }
383
384 let mut acc: u32 = 0;
385 let mut acc_len: u32 = 0;
386 let mut written = 0usize;
387 for &byte in src {
388 acc = (acc << 8) | byte as u32;
389 acc_len += 8;
390 while acc_len >= 6 {
391 acc_len -= 6;
392 dst[written] = b64_byte_to_char((acc >> acc_len) & 0x3f);
393 written += 1;
394 }
395 }
396 if acc_len > 0 {
397 dst[written] = b64_byte_to_char((acc << (6 - acc_len)) & 0x3f);
398 written += 1;
399 }
400
401 debug_assert_eq!(written, olen);
402 Ok(written)
403}
404
405/// Encode with an explicitly selected Base64 backend.
406///
407/// This is an unstable test/benchmark hook. Normal callers use [`to_base64`],
408/// which performs safe runtime detection.
409///
410/// # Safety
411///
412/// `backend` must be executable on the current CPU, as reported by
413/// [`Base64Backend::is_available`].
414#[inline]
415pub unsafe fn to_base64_with_backend(
416 dst: &mut [u8],
417 src: &[u8],
418 backend: Base64Backend,
419) -> Result<usize, Error> {
420 if backend == Base64Backend::Scalar {
421 return to_base64_scalar(dst, src);
422 }
423
424 let olen = b64_len_usize(src.len());
425 if dst.len() <= olen {
426 return Err(Error::EncodingFail);
427 }
428
429 // SAFETY: transferred from this function's caller. The capacity check
430 // above proves every complete vector store is within `dst`.
431 let (consumed, mut written) = unsafe { crate::base64::encode_prefix(backend, dst, src) };
432 let mut acc: u32 = 0;
433 let mut acc_len: u32 = 0;
434
435 for &byte in &src[consumed..] {
436 // The C writes `(acc << 8) + *buf++`; the low 8 bits of `acc << 8` are
437 // zero, so `|` is the same value and cannot overflow in debug builds.
438 acc = (acc << 8) | byte as u32;
439 acc_len += 8;
440 while acc_len >= 6 {
441 acc_len -= 6;
442 dst[written] = b64_byte_to_char((acc >> acc_len) & 0x3F);
443 written += 1;
444 }
445 }
446 if acc_len > 0 {
447 dst[written] = b64_byte_to_char((acc << (6 - acc_len)) & 0x3F);
448 written += 1;
449 }
450
451 debug_assert!(written == olen);
452 Ok(written)
453}
454
455/// `from_base64(dst, dst_len, src)`.
456///
457/// Decodes until the first non-base64 byte. Returns
458/// `(bytes_written, bytes_consumed)`, where `bytes_consumed` indexes the first
459/// non-base64 byte in `src` — the equivalent of the pointer the C returns. The
460/// end of the slice acts as the C's terminating NUL, which is itself not a
461/// base64 character.
462///
463/// # Errors
464///
465/// [`Error::DecodingFail`] if `dst` is too small, if `acc_len > 4` at the end,
466/// or if any buffered low bits are non-zero.
467pub fn from_base64(dst: &mut [u8], src: &[u8]) -> Result<(usize, usize), Error> {
468 if src.len() < crate::base64::MIN_DECODE_LEN {
469 // As in the encoder, keep both lookup and generalized prefix state out
470 // of inputs too short for this architecture's smallest vector.
471 return from_base64_scalar(dst, src);
472 }
473 let backend = crate::base64::base64_backend();
474 // SAFETY: as in `to_base64`, detection proves the feature contract.
475 unsafe { from_base64_with_backend(dst, src, backend) }
476}
477
478/// The original reference-C loop, kept whole for the scalar and short-input
479/// paths just like [`to_base64_scalar`].
480#[inline(always)]
481fn from_base64_scalar(dst: &mut [u8], src: &[u8]) -> Result<(usize, usize), Error> {
482 let mut consumed = 0usize;
483 let mut len = 0usize;
484 let mut acc: u32 = 0;
485 let mut acc_len: u32 = 0;
486
487 loop {
488 // Past the end of the slice, feed the NUL the C would have read.
489 let c = match src.get(consumed) {
490 Some(&byte) => byte as u32,
491 None => 0,
492 };
493 let d = b64_char_to_byte(c);
494 if d == 0xFF {
495 break;
496 }
497 consumed += 1;
498 acc = (acc << 6) | d;
499 acc_len += 6;
500 if acc_len >= 8 {
501 acc_len -= 8;
502 if len >= dst.len() {
503 return Err(Error::DecodingFail);
504 }
505 dst[len] = ((acc >> acc_len) & 0xFF) as u8;
506 len += 1;
507 }
508 }
509
510 if acc_len > 4 || (acc & ((1u32 << acc_len) - 1)) != 0 {
511 return Err(Error::DecodingFail);
512 }
513
514 Ok((len, consumed))
515}
516
517/// Decode with an explicitly selected Base64 backend.
518///
519/// This preserves [`from_base64`]'s exact stopping and error behavior and is
520/// exposed only as an unstable differential-test/benchmark hook.
521///
522/// # Safety
523///
524/// `backend` must be executable on the current CPU, as reported by
525/// [`Base64Backend::is_available`].
526#[inline]
527pub unsafe fn from_base64_with_backend(
528 dst: &mut [u8],
529 src: &[u8],
530 backend: Base64Backend,
531) -> Result<(usize, usize), Error> {
532 if backend == Base64Backend::Scalar {
533 return from_base64_scalar(dst, src);
534 }
535
536 // SAFETY: transferred from this function's caller. Each backend checks the
537 // supplied slice lengths before loading or storing a complete block.
538 let (mut consumed, mut len) = unsafe { crate::base64::decode_prefix(backend, dst, src) };
539 let mut acc: u32 = 0;
540 let mut acc_len: u32 = 0;
541
542 loop {
543 // Past the end of the slice, feed the NUL the C would have read.
544 let c = match src.get(consumed) {
545 Some(&byte) => byte as u32,
546 None => 0,
547 };
548 let d = b64_char_to_byte(c);
549 if d == 0xFF {
550 break;
551 }
552 consumed += 1;
553 // As in `to_base64`, `|` matches the C's `+` bit for bit.
554 acc = (acc << 6) | d;
555 acc_len += 6;
556 if acc_len >= 8 {
557 acc_len -= 8;
558 // The C is `if ((len++) >= *dst_len) return NULL;`, i.e. the test
559 // uses the pre-increment value.
560 if len >= dst.len() {
561 return Err(Error::DecodingFail);
562 }
563 dst[len] = ((acc >> acc_len) & 0xFF) as u8;
564 len += 1;
565 }
566 }
567
568 // An input length of 1 modulo 4 leaves 6 unprocessed bits, which is
569 // invalid; otherwise 0, 2 or 4 bits are buffered and they must be zero.
570 if acc_len > 4 || (acc & ((1u32 << acc_len) - 1)) != 0 {
571 return Err(Error::DecodingFail);
572 }
573
574 Ok((len, consumed))
575}
576
577// ---------------------------------------------------------------------------
578// decode_decimal
579// ---------------------------------------------------------------------------
580
581/// `decode_decimal(str, v)`.
582///
583/// Returns `(value, digits_consumed)`, or `None` when there is no digit at all,
584/// when the encoding is not minimal (a leading `'0'` with more digits after
585/// it), or when the value overflows.
586///
587/// The C accumulates in `unsigned long`, which is 64-bit on every target this
588/// crate supports, so `u64` matches it. On a hypothetical 32-bit `unsigned
589/// long` the outcome would still be the same, because every caller is
590/// `DECIMAL_U32`, which rejects anything above `u32::MAX` anyway.
591fn decode_decimal(src: &[u8]) -> Option<(u64, usize)> {
592 let mut acc: u64 = 0;
593 let mut i = 0usize;
594
595 while let Some(&c) = src.get(i) {
596 if !c.is_ascii_digit() {
597 break;
598 }
599 let digit = (c - b'0') as u64;
600 if acc > u64::MAX / 10 {
601 return None;
602 }
603 acc *= 10;
604 if digit > u64::MAX - acc {
605 return None;
606 }
607 acc += digit;
608 i += 1;
609 }
610
611 // `if (str == orig || (*orig == '0' && str != (orig + 1))) return NULL;`
612 if i == 0 {
613 return None;
614 }
615 if src[0] == b'0' && i != 1 {
616 return None;
617 }
618
619 Some((acc, i))
620}
621
622// ---------------------------------------------------------------------------
623// encode_string
624// ---------------------------------------------------------------------------
625
626/// The C's `SS`/`SX`/`SB` macros: a cursor that always keeps one byte spare for
627/// the NUL terminator the C writes, so the capacity requirement is identical.
628struct Writer<'a> {
629 dst: &'a mut [u8],
630 pos: usize,
631}
632
633impl Writer<'_> {
634 /// `SS(str)`: `if (pp_len >= dst_len) return ARGON2_ENCODING_FAIL;`.
635 fn put(&mut self, bytes: &[u8]) -> Result<(), Error> {
636 let remaining = self.dst.len() - self.pos;
637 if bytes.len() >= remaining {
638 return Err(Error::EncodingFail);
639 }
640 let end = self.pos + bytes.len();
641 self.dst[self.pos..end].copy_from_slice(bytes);
642 self.pos = end;
643 Ok(())
644 }
645
646 /// `SX(x)`: the decimal form of `x`, no allocation.
647 fn put_u32(&mut self, value: u32) -> Result<(), Error> {
648 // `u32::MAX` is 4294967295: ten digits.
649 let mut buf = [0u8; 10];
650 let mut i = buf.len();
651 let mut n = value;
652 loop {
653 i -= 1;
654 buf[i] = b'0' + (n % 10) as u8;
655 n /= 10;
656 if n == 0 {
657 break;
658 }
659 }
660 self.put(&buf[i..])
661 }
662
663 /// `SB(buf, len)`: base64, which does its own capacity check.
664 fn put_base64(&mut self, src: &[u8]) -> Result<(), Error> {
665 let written = to_base64(&mut self.dst[self.pos..], src)?;
666 self.pos += written;
667 Ok(())
668 }
669}
670
671/// `validate_inputs(ctx)` as `encode_string` and `decode_string` run it.
672///
673/// `out_len` is the *tag* length, which is what `ctx->outlen` holds in both
674/// call sites, and `pwd_len` is 0 (see the module-level divergence note).
675fn validate_for_string(params: &Params, salt_len: usize, hash_len: usize) -> Result<(), Error> {
676 validate_inputs(
677 hash_len,
678 0,
679 salt_len,
680 0,
681 0,
682 params.m_cost(),
683 params.t_cost(),
684 params.lanes(),
685 params.threads(),
686 )
687}
688
689/// `encode_string(dst, dst_len, ctx, type)`.
690///
691/// Writes the PHC string into `dst` (no NUL terminator) and returns its length.
692/// Always emits `$v=`, as the C does.
693///
694/// `dst` must hold the string **plus one byte**, because the C reserves room
695/// for its NUL terminator and this port keeps the capacity rule identical:
696/// a buffer of exactly [`encoded_len`] bytes is what succeeds, in Rust and in
697/// C alike.
698///
699/// `hash.len()` plays the role of `ctx->outlen` — it is the length that gets
700/// encoded, so it, and not [`Params::output_len`], is what the leading
701/// `validate_inputs()` checks. For a tag produced from these `params` the two
702/// are the same value.
703///
704/// # Errors
705///
706/// [`Error::EncodingFail`] if `dst` is too small, or whatever
707/// [`crate::params::validate_inputs`] returns — `encode_string` runs it first,
708/// exactly like the C.
709pub fn encode_string(
710 dst: &mut [u8],
711 algorithm: Algorithm,
712 version: Version,
713 params: &Params,
714 salt: &[u8],
715 hash: &[u8],
716) -> Result<usize, Error> {
717 validate_for_string(params, salt.len(), hash.len())?;
718
719 let mut w = Writer { dst, pos: 0 };
720
721 w.put(b"$")?;
722 w.put(algorithm.as_str().as_bytes())?;
723
724 w.put(b"$v=")?;
725 w.put_u32(version.as_u32())?;
726
727 w.put(b"$m=")?;
728 w.put_u32(params.m_cost())?;
729 w.put(b",t=")?;
730 w.put_u32(params.t_cost())?;
731 w.put(b",p=")?;
732 w.put_u32(params.lanes())?;
733
734 w.put(b"$")?;
735 w.put_base64(salt)?;
736
737 w.put(b"$")?;
738 w.put_base64(hash)?;
739
740 Ok(w.pos)
741}
742
743/// [`encode_string`] into a freshly allocated [`String`].
744///
745/// # Errors
746///
747/// As [`encode_string`], plus [`Error::MemoryAllocationError`] if the buffer
748/// cannot be allocated (the C returns the same code when its `malloc` fails).
749pub fn encode_string_alloc(
750 algorithm: Algorithm,
751 version: Version,
752 params: &Params,
753 salt: &[u8],
754 hash: &[u8],
755) -> Result<String, Error> {
756 // Validate before allocating, so an over-long salt reports SaltTooLong
757 // rather than failing to allocate a buffer sized from it.
758 validate_for_string(params, salt.len(), hash.len())?;
759
760 let capacity = encoded_len_usize(
761 algorithm,
762 params.t_cost(),
763 params.m_cost(),
764 params.lanes(),
765 salt.len(),
766 hash.len(),
767 );
768
769 let mut buf = alloc_zeroed_vec(capacity)?;
770 let written = encode_string(&mut buf, algorithm, version, params, salt, hash)?;
771 buf.truncate(written);
772
773 // Every byte written comes from the base64 alphabet, the decimal digits or
774 // the ASCII punctuation above, so this is always valid UTF-8.
775 String::from_utf8(buf).map_err(|_| Error::EncodingFail)
776}
777
778/// A zeroed `Vec<u8>` of `len` bytes, without the abort-on-OOM of
779/// `Vec::with_capacity`.
780fn alloc_zeroed_vec(len: usize) -> Result<Vec<u8>, Error> {
781 let mut v = Vec::new();
782 v.try_reserve(len)
783 .map_err(|_| Error::MemoryAllocationError)?;
784 // Cannot reallocate: the capacity was just reserved.
785 v.resize(len, 0);
786 Ok(v)
787}
788
789// ---------------------------------------------------------------------------
790// decode_string
791// ---------------------------------------------------------------------------
792
793/// The fields a PHC string yields.
794#[derive(Debug, Clone, PartialEq, Eq)]
795pub struct Decoded {
796 /// The algorithm named by the string. Always equal to the one requested,
797 /// since a mismatch is a decoding failure.
798 pub algorithm: Algorithm,
799 /// The version. `0x10` when the `$v=` field is absent.
800 pub version: Version,
801 /// `m_cost`, `t_cost`, `lanes`, `threads == lanes`, and
802 /// `output_len == hash.len()`.
803 pub params: Params,
804 /// The decoded salt.
805 pub salt: Vec<u8>,
806 /// The decoded tag.
807 pub hash: Vec<u8>,
808}
809
810/// The remainder of `src` from `pos`, never panicking.
811#[inline]
812fn rest(src: &[u8], pos: usize) -> &[u8] {
813 src.get(pos..).unwrap_or(&[])
814}
815
816/// The `CC(prefix)` macro: consume `prefix` or fail.
817fn expect(src: &[u8], pos: &mut usize, prefix: &[u8]) -> Result<(), Error> {
818 if rest(src, *pos).starts_with(prefix) {
819 *pos += prefix.len();
820 Ok(())
821 } else {
822 Err(Error::DecodingFail)
823 }
824}
825
826/// The `CC_opt(prefix, code)` macro: consume `prefix` if it is there.
827fn expect_opt(src: &[u8], pos: &mut usize, prefix: &[u8]) -> bool {
828 if rest(src, *pos).starts_with(prefix) {
829 *pos += prefix.len();
830 true
831 } else {
832 false
833 }
834}
835
836/// The `DECIMAL_U32(x)` macro.
837fn decimal_u32(src: &[u8], pos: &mut usize) -> Result<u32, Error> {
838 let (value, consumed) = decode_decimal(rest(src, *pos)).ok_or(Error::DecodingFail)?;
839 if value > u32::MAX as u64 {
840 return Err(Error::DecodingFail);
841 }
842 *pos += consumed;
843 Ok(value as u32)
844}
845
846/// The `BIN(buf, max_len, len)` macro.
847///
848/// The C sizes the destination at `strlen(encoded)` (see `argon2_verify`), so
849/// the "output buffer too small" branch of `from_base64` is unreachable there.
850/// The bound used here — three bytes out per four characters in — is likewise
851/// never exceeded, so the two agree.
852fn decode_bin(src: &[u8], pos: &mut usize) -> Result<Vec<u8>, Error> {
853 let tail = rest(src, *pos);
854 // `n` base64 characters decode to `floor(3n / 4)` bytes; `n/4*3 + 3` is an
855 // upper bound for every `n`, and cannot overflow for any real slice.
856 let max_len = tail.len() / 4 * 3 + 3;
857
858 let mut buf = alloc_zeroed_vec(max_len)?;
859 let (written, consumed) = from_base64(&mut buf, tail)?;
860 // `bin_len > UINT32_MAX` is a decoding failure in the C.
861 if written > u32::MAX as usize {
862 return Err(Error::DecodingFail);
863 }
864 buf.truncate(written);
865 *pos += consumed;
866 Ok(buf)
867}
868
869/// `decode_string(ctx, str, type)`.
870///
871/// # Errors
872///
873/// [`Error::DecodingFail`] for a malformed string, or whatever
874/// [`crate::params::validate_inputs`] returns — the C runs the full validation
875/// before accepting the string, so a well-formed string with a zero-length salt
876/// yields [`Error::SaltTooShort`] and not [`Error::DecodingFail`].
877///
878/// See the module documentation for the known divergences from the C
879/// (unrepresentable versions, embedded NULs, and raw/non-ASCII input).
880pub fn decode_string(encoded: &str, algorithm: Algorithm) -> Result<Decoded, Error> {
881 let src = encoded.as_bytes();
882 let mut pos = 0usize;
883
884 // argon2.c:268-271
885 // encoded_len = strlen(encoded);
886 // if (encoded_len > UINT32_MAX) return ARGON2_DECODING_FAIL;
887 //
888 // The C puts this in `argon2_verify`, one level up, and computes
889 // `max_field_len` from it. Here it lives in the decoder because all four
890 // verify entry points funnel through this function, so one check covers
891 // them and cannot drift; through the public API the behaviour is identical.
892 // Reachable only where `usize` is wider than `u32`, from a `&str` at least
893 // 4 GiB long.
894 if src.len() > u32::MAX as usize {
895 return Err(Error::DecodingFail);
896 }
897
898 // CC("$"); CC(type_string);
899 //
900 // No `ARGON2_INCORRECT_TYPE` branch: `argon2_type2string` only returns NULL
901 // for a type outside the enum, which `Algorithm` cannot represent.
902 expect(src, &mut pos, b"$")?;
903 expect(src, &mut pos, algorithm.as_str().as_bytes())?;
904
905 // ctx->version = ARGON2_VERSION_10; CC_opt("$v=", DECIMAL_U32(version));
906 let mut version_value = Version::V0x10.as_u32();
907 if expect_opt(src, &mut pos, b"$v=") {
908 version_value = decimal_u32(src, &mut pos)?;
909 }
910
911 expect(src, &mut pos, b"$m=")?;
912 let m_cost = decimal_u32(src, &mut pos)?;
913 expect(src, &mut pos, b",t=")?;
914 let t_cost = decimal_u32(src, &mut pos)?;
915 expect(src, &mut pos, b",p=")?;
916 let lanes = decimal_u32(src, &mut pos)?;
917 // `ctx->threads = ctx->lanes;`
918 let threads = lanes;
919
920 expect(src, &mut pos, b"$")?;
921 let salt = decode_bin(src, &mut pos)?;
922 expect(src, &mut pos, b"$")?;
923 let hash = decode_bin(src, &mut pos)?;
924
925 // "On return, must have valid context": the full validate_inputs(), in the
926 // C's order, before the trailing-character check.
927 validate_inputs(
928 hash.len(),
929 0,
930 salt.len(),
931 0,
932 0,
933 m_cost,
934 t_cost,
935 lanes,
936 threads,
937 )?;
938
939 // "Can't have any additional characters".
940 if pos != src.len() {
941 return Err(Error::DecodingFail);
942 }
943
944 // Last, so that every error code above still matches the C exactly.
945 let version = Version::from_u32(version_value).ok_or(Error::DecodingFail)?;
946
947 // Cannot fail: `validate_inputs` above already accepted these values, and
948 // `Params` re-checks them with a salt length that is valid by construction.
949 let params = Params::new_with_threads(m_cost, t_cost, lanes, threads, hash.len())?;
950
951 Ok(Decoded {
952 algorithm,
953 version,
954 params,
955 salt,
956 hash,
957 })
958}
959
960#[cfg(test)]
961mod tests {
962 use super::*;
963 use alloc::vec;
964
965 // From `phc-winner-argon2/src/test.c`.
966 const V13_ARGON2I: &str = "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ\
967 $wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA";
968 const V10_ARGON2I: &str = "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ\
969 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
970 const V13_ARGON2ID: &str = "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ\
971 $CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
972
973 /// `f6c4db4a...`, the raw tag of the v=0x10 Argon2i vector in test.c.
974 const V10_TAG: [u8; 32] = [
975 0xf6, 0xc4, 0xdb, 0x4a, 0x54, 0xe2, 0xa3, 0x70, 0x62, 0x7a, 0xff, 0x3d, 0xb6, 0x17, 0x6b,
976 0x94, 0xa2, 0xa2, 0x09, 0xa6, 0x2c, 0x8e, 0x36, 0x15, 0x27, 0x11, 0x80, 0x2f, 0x7b, 0x30,
977 0xc6, 0x94,
978 ];
979
980 fn b64(src: &[u8]) -> Vec<u8> {
981 let mut out = vec![0u8; b64_len_usize(src.len()) + 1];
982 let n = to_base64(&mut out, src).expect("buffer sized by b64_len");
983 out.truncate(n);
984 out
985 }
986
987 fn unb64(src: &[u8]) -> Result<Vec<u8>, Error> {
988 let mut out = vec![0u8; src.len()];
989 let (n, consumed) = from_base64(&mut out, src)?;
990 assert_eq!(consumed, src.len(), "test inputs are pure base64");
991 out.truncate(n);
992 Ok(out)
993 }
994
995 fn b64_with_backend(
996 dst: &mut [u8],
997 src: &[u8],
998 backend: Base64Backend,
999 ) -> Result<usize, Error> {
1000 assert!(backend.is_available());
1001 // SAFETY: the assertion establishes this test process can execute the
1002 // requested backend; slice bounds remain checked by the implementation.
1003 unsafe { to_base64_with_backend(dst, src, backend) }
1004 }
1005
1006 fn unb64_with_backend(
1007 dst: &mut [u8],
1008 src: &[u8],
1009 backend: Base64Backend,
1010 ) -> Result<(usize, usize), Error> {
1011 assert!(backend.is_available());
1012 // SAFETY: as in `b64_with_backend`, availability is proved immediately
1013 // above and both pointer/length pairs come from live slices.
1014 unsafe { from_base64_with_backend(dst, src, backend) }
1015 }
1016
1017 // -- lengths ------------------------------------------------------------
1018
1019 #[test]
1020 fn b64_len_matches_c() {
1021 assert_eq!(b64_len(0), 0);
1022 assert_eq!(b64_len(1), 2);
1023 assert_eq!(b64_len(2), 3);
1024 assert_eq!(b64_len(3), 4);
1025 assert_eq!(b64_len(4), 6);
1026 // "somesalt" -> "c29tZXNhbHQ", "…" -> the 43-char tag.
1027 assert_eq!(b64_len(8), 11);
1028 assert_eq!(b64_len(32), 43);
1029 // Cross-check against the encoder for every small length.
1030 for len in 0u32..64 {
1031 let src = vec![0xABu8; len as usize];
1032 assert_eq!(b64(&src).len(), b64_len(len), "len {len}");
1033 }
1034 }
1035
1036 #[test]
1037 fn num_len_matches_c() {
1038 assert_eq!(num_len(0), 1);
1039 assert_eq!(num_len(9), 1);
1040 assert_eq!(num_len(10), 2);
1041 assert_eq!(num_len(19), 2);
1042 assert_eq!(num_len(65536), 5);
1043 assert_eq!(num_len(u32::MAX), 10);
1044 }
1045
1046 #[test]
1047 fn encoded_len_matches_the_c_vector() {
1048 // $argon2id$v=19$m=65536,t=2,p=1$<11>$<43> is 86 chars + NUL.
1049 let n = encoded_len(Algorithm::Argon2id, 2, 65536, 1, 8, 32);
1050 assert_eq!(n, V13_ARGON2ID.len() + 1);
1051 assert_eq!(n, 87);
1052 assert_eq!(
1053 encoded_len(Algorithm::Argon2i, 2, 65536, 1, 8, 32),
1054 V13_ARGON2I.len() + 1
1055 );
1056 }
1057
1058 // Pins the claim in `encoded_len`'s `# Argument order` section: the C's
1059 // `argon2_encodedlen` (`argon2.c:447`) takes `t_cost` before `m_cost`, this
1060 // port keeps that order, and it is the reverse of `Params::new`.
1061 //
1062 // A caller who supplies the two costs the other way round gets no error
1063 // back, and the reason is arithmetic rather than luck. `t_cost` and `m_cost`
1064 // each reach the result through exactly one term, `num_len(t_cost)` and
1065 // `num_len(m_cost)`, and the two terms are added, so the sum does not depend
1066 // on which digit count came from which cost. Nothing else in the body reads
1067 // either value. The swap is therefore *always* harmless, not usually
1068 // harmless: the swapped call returns the same number, and that number is
1069 // also the correct one. Nothing passed to `encoded_len` reaches the emitted
1070 // string either -- `encode_string` writes the `m=` and `t=` fields out of
1071 // the `&Params` it is handed. A swapped call site is a cosmetic
1072 // inconsistency, not a latent bug.
1073 //
1074 // That makes the equality a property of this one formula and not a rule
1075 // about the crate. This test pins it at every digit-count boundary and will
1076 // fail here first if the length ever stops being a plain sum of the two
1077 // terms, at which point the doc gets fixed with it.
1078 #[test]
1079 fn encoded_len_is_symmetric_in_m_and_t() {
1080 // The pair the doc example uses, the C's own test vector, and the
1081 // extreme: `u32::MAX` is ten digits against one, the widest the two
1082 // terms can differ.
1083 assert_eq!(encoded_len(Algorithm::Argon2id, 3, 65536, 1, 16, 32), 98);
1084 assert_eq!(encoded_len(Algorithm::Argon2id, 65536, 3, 1, 16, 32), 98);
1085 assert_eq!(encoded_len(Algorithm::Argon2id, 2, 65536, 1, 8, 32), 87);
1086 assert_eq!(encoded_len(Algorithm::Argon2id, 65536, 2, 1, 8, 32), 87);
1087 assert_eq!(encoded_len(Algorithm::Argon2id, 1, u32::MAX, 1, 16, 32), 103);
1088 assert_eq!(encoded_len(Algorithm::Argon2id, u32::MAX, 1, 1, 16, 32), 103);
1089
1090 // Every place `num_len` changes answer, both sides of each step, over
1091 // all three algorithm strings, so the property is pinned rather than
1092 // sampled at a few lucky points.
1093 const BOUNDARIES: [u32; 22] = [
1094 0,
1095 1,
1096 9,
1097 10,
1098 99,
1099 100,
1100 999,
1101 1_000,
1102 9_999,
1103 10_000,
1104 99_999,
1105 100_000,
1106 999_999,
1107 1_000_000,
1108 9_999_999,
1109 10_000_000,
1110 99_999_999,
1111 100_000_000,
1112 999_999_999,
1113 1_000_000_000,
1114 65536,
1115 u32::MAX,
1116 ];
1117 for algorithm in [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id] {
1118 for t_cost in BOUNDARIES {
1119 for m_cost in BOUNDARIES {
1120 assert_eq!(
1121 encoded_len(algorithm, t_cost, m_cost, 1, 16, 32),
1122 encoded_len(algorithm, m_cost, t_cost, 1, 16, 32),
1123 "{algorithm:?} t_cost={t_cost} m_cost={m_cost}"
1124 );
1125 }
1126 }
1127 }
1128 }
1129
1130 // -- base64 -------------------------------------------------------------
1131
1132 #[test]
1133 fn b64_tables_are_the_standard_alphabet() {
1134 const ALPHABET: &[u8; 64] =
1135 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1136 for (value, &ch) in ALPHABET.iter().enumerate() {
1137 assert_eq!(b64_byte_to_char(value as u32), ch, "value {value}");
1138 assert_eq!(b64_char_to_byte(ch as u32), value as u32, "char {ch}");
1139 }
1140 }
1141
1142 #[test]
1143 fn b64_char_to_byte_rejects_everything_else() {
1144 // The 'A' quirk: a computed 0 is only valid for 'A'.
1145 assert_eq!(b64_char_to_byte(b'A' as u32), 0);
1146 for c in 0u32..256 {
1147 let is_b64 = (c as u8).is_ascii_alphanumeric() || c == b'+' as u32 || c == b'/' as u32;
1148 if !is_b64 {
1149 assert_eq!(b64_char_to_byte(c), 0xFF, "char {c} must be invalid");
1150 }
1151 }
1152 assert_eq!(b64_char_to_byte(b'=' as u32), 0xFF); // no padding
1153 assert_eq!(b64_char_to_byte(0), 0xFF); // NUL terminator
1154 assert_eq!(b64_char_to_byte(b'$' as u32), 0xFF); // field separator
1155
1156 // Bytes >= 0x80 are rejected here. The C's are not, wherever `char` is
1157 // signed — it returns 63 for all 128 of them. See the module docs; this
1158 // loop is the pin for the port's (stricter, portable) choice.
1159 for c in 0x80u32..256 {
1160 assert_eq!(b64_char_to_byte(c), 0xFF, "byte {c:#04x} must be invalid");
1161 }
1162 }
1163
1164 /// `b64_char_to_byte` over `0..=127`, dumped from the C reference.
1165 ///
1166 /// Produced by a harness that `#include`s `encoding.c` (the function is
1167 /// `static`) and prints `b64_char_to_byte(i)` for every `i`, linked against
1168 /// `phc-winner-argon2/libargon2.a`. Only the ASCII half is transcribed: the
1169 /// upper half is the signed-`char` bug documented at the top of this file,
1170 /// where the C returns 63 for all 128 values.
1171 #[rustfmt::skip]
1172 const C_CHAR_TO_BYTE_ASCII: [u32; 128] = [
1173 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
1174 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
1175 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
1176 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 255, 255, 255,
1177 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
1178 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
1179 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
1180 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255,
1181 ];
1182
1183 #[test]
1184 fn b64_char_to_byte_matches_the_c_dump() {
1185 for (c, &want) in C_CHAR_TO_BYTE_ASCII.iter().enumerate() {
1186 assert_eq!(b64_char_to_byte(c as u32), want, "char {c}");
1187 }
1188 }
1189
1190 /// `b64_byte_to_char` over `0..64`, dumped from the same C harness.
1191 #[rustfmt::skip]
1192 const C_BYTE_TO_CHAR: [u8; 64] = [
1193 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
1194 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102,
1195 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
1196 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47,
1197 ];
1198
1199 #[test]
1200 fn b64_byte_to_char_matches_the_c_dump() {
1201 for (x, &want) in C_BYTE_TO_CHAR.iter().enumerate() {
1202 assert_eq!(b64_byte_to_char(x as u32), want, "value {x}");
1203 }
1204 }
1205
1206 #[test]
1207 fn non_ascii_bytes_are_rejected_in_a_field() {
1208 // Measured: the C decodes both of these, identically, to the salt
1209 // ff ff 6d 65 73 61 6c 74, because 0xC3 and 0xA9 each read as '/'.
1210 // This port rejects the first and accepts the second.
1211 let utf8 = "$argon2i$v=19$m=65536,t=2,p=1$é9tZXNhbHQ\
1212 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1213 assert_eq!(
1214 decode_string(utf8, Algorithm::Argon2i),
1215 Err(Error::DecodingFail)
1216 );
1217 let slashes = "$argon2i$v=19$m=65536,t=2,p=1$//9tZXNhbHQ\
1218 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1219 let d = decode_string(slashes, Algorithm::Argon2i).unwrap();
1220 assert_eq!(d.salt, [0xff, 0xff, 0x6d, 0x65, 0x73, 0x61, 0x6c, 0x74]);
1221 }
1222
1223 #[test]
1224 fn base64_known_vectors() {
1225 assert_eq!(b64(b"somesalt"), b"c29tZXNhbHQ");
1226 assert_eq!(b64(b"diffsalt"), b"ZGlmZnNhbHQ");
1227 assert_eq!(
1228 b64(&V10_TAG),
1229 b"9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
1230 );
1231 assert_eq!(unb64(b"c29tZXNhbHQ").unwrap(), b"somesalt");
1232 assert_eq!(
1233 unb64(b"9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ").unwrap(),
1234 &V10_TAG
1235 );
1236 }
1237
1238 #[test]
1239 fn base64_round_trips_every_short_length() {
1240 for len in 0usize..96 {
1241 let src: Vec<u8> = (0..len)
1242 .map(|i| (i as u8).wrapping_mul(37) ^ 0x5A)
1243 .collect();
1244 let encoded = b64(&src);
1245 assert_eq!(unb64(&encoded).unwrap(), src, "len {len}");
1246 }
1247 }
1248
1249 /// Every executable SIMD backend against the scalar oracle, across the
1250 /// block boundaries and tails each implementation can take. This checks
1251 /// the primitive directly rather than relying on PHC vectors whose usual
1252 /// 16/32-byte fields exercise only two shapes.
1253 #[test]
1254 fn every_base64_backend_matches_scalar_across_lengths() {
1255 for len in 0usize..=512 {
1256 let src: Vec<u8> = (0..len)
1257 .map(|i| (i as u8).wrapping_mul(197) ^ (len as u8).wrapping_mul(11))
1258 .collect();
1259 let capacity = b64_len_usize(len) + 1;
1260 let mut expected = vec![0xa5; capacity];
1261 let expected_len =
1262 b64_with_backend(&mut expected, &src, Base64Backend::Scalar).unwrap();
1263
1264 for &backend in Base64Backend::ALL {
1265 if !backend.is_available()
1266 || (cfg!(miri) && backend != Base64Backend::Scalar)
1267 {
1268 continue;
1269 }
1270 let mut actual = vec![0xa5; capacity];
1271 let actual_len = b64_with_backend(&mut actual, &src, backend).unwrap();
1272 assert_eq!(actual_len, expected_len, "{backend} length {len}");
1273 assert_eq!(actual, expected, "{backend} bytes at length {len}");
1274
1275 let mut scalar_decoded = vec![0x5a; len];
1276 let scalar_result = unb64_with_backend(
1277 &mut scalar_decoded,
1278 &expected[..expected_len],
1279 Base64Backend::Scalar,
1280 );
1281 let mut simd_decoded = vec![0x5a; len];
1282 let simd_result = unb64_with_backend(
1283 &mut simd_decoded,
1284 &expected[..expected_len],
1285 backend,
1286 );
1287 assert_eq!(simd_result, scalar_result, "{backend} decode length {len}");
1288 assert_eq!(simd_decoded, scalar_decoded, "{backend} decode bytes {len}");
1289 assert_eq!(simd_decoded, src, "{backend} round trip {len}");
1290 }
1291 }
1292 }
1293
1294 /// An invalid byte in a vector must not make the SIMD prefix lose the C
1295 /// decoder's exact stopping position. The vector is retried by scalar, so
1296 /// both the return value and all bytes written before an error match.
1297 #[test]
1298 fn every_base64_backend_matches_scalar_on_invalid_bytes_and_short_outputs() {
1299 let raw: Vec<u8> = (0..96)
1300 .map(|i| (i as u8).wrapping_mul(37) ^ 0x5a)
1301 .collect();
1302 let encoded = b64(&raw);
1303
1304 for &backend in Base64Backend::ALL {
1305 if !backend.is_available() || (cfg!(miri) && backend != Base64Backend::Scalar) {
1306 continue;
1307 }
1308
1309 for pos in 0..encoded.len() {
1310 for invalid in [0, b'$', b'=', 0x80, 0xff] {
1311 let mut input = encoded.clone();
1312 input[pos] = invalid;
1313 let mut expected = vec![0xa5; raw.len()];
1314 let expected_result = unb64_with_backend(
1315 &mut expected,
1316 &input,
1317 Base64Backend::Scalar,
1318 );
1319 let mut actual = vec![0xa5; raw.len()];
1320 let actual_result = unb64_with_backend(&mut actual, &input, backend);
1321 assert_eq!(actual_result, expected_result, "{backend} pos {pos} byte {invalid:#x}");
1322 assert_eq!(actual, expected, "{backend} output at pos {pos} byte {invalid:#x}");
1323 }
1324 }
1325
1326 for dst_len in 0..raw.len() {
1327 let mut expected = vec![0xa5; dst_len];
1328 let expected_result = unb64_with_backend(
1329 &mut expected,
1330 &encoded,
1331 Base64Backend::Scalar,
1332 );
1333 let mut actual = vec![0xa5; dst_len];
1334 let actual_result = unb64_with_backend(&mut actual, &encoded, backend);
1335 assert_eq!(actual_result, expected_result, "{backend} dst length {dst_len}");
1336 assert_eq!(actual, expected, "{backend} dst bytes at length {dst_len}");
1337 }
1338 }
1339 }
1340
1341 #[test]
1342 fn to_base64_rejects_a_tight_buffer() {
1343 // The C requires dst_len > olen, i.e. room for the NUL as well.
1344 let mut exact = [0u8; 11];
1345 assert_eq!(to_base64(&mut exact, b"somesalt"), Err(Error::EncodingFail));
1346 let mut roomy = [0u8; 12];
1347 assert_eq!(to_base64(&mut roomy, b"somesalt"), Ok(11));
1348 // Even an empty input needs one byte for the terminator.
1349 assert_eq!(to_base64(&mut [], b""), Err(Error::EncodingFail));
1350 assert_eq!(to_base64(&mut [0u8; 1], b""), Ok(0));
1351 }
1352
1353 #[test]
1354 fn from_base64_stops_at_the_first_non_b64_char() {
1355 let mut out = [0u8; 16];
1356 let (written, consumed) = from_base64(&mut out, b"c29tZXNhbHQ$rest").unwrap();
1357 assert_eq!(written, 8);
1358 assert_eq!(consumed, 11);
1359 assert_eq!(&out[..8], b"somesalt");
1360
1361 // An empty run is fine and consumes nothing (this is how the C accepts
1362 // "$argon2i$m=…,p=1$$<tag>" and only later reports SaltTooShort).
1363 let (written, consumed) = from_base64(&mut out, b"$tag").unwrap();
1364 assert_eq!((written, consumed), (0, 0));
1365 }
1366
1367 #[test]
1368 fn from_base64_rejects_leftover_bits() {
1369 let mut out = [0u8; 16];
1370 // 5 chars -> 30 bits -> acc_len == 6 > 4.
1371 assert_eq!(
1372 from_base64(&mut out, b"AAAAA"),
1373 Err(Error::DecodingFail),
1374 "acc_len > 4"
1375 );
1376 // 1 char -> 6 bits -> acc_len == 6 > 4.
1377 assert_eq!(from_base64(&mut out, b"A"), Err(Error::DecodingFail));
1378 // 2 chars, 4 buffered bits, non-zero: 'B' == 1.
1379 assert_eq!(from_base64(&mut out, b"AB"), Err(Error::DecodingFail));
1380 // Same shape but the buffered bits are zero.
1381 assert_eq!(from_base64(&mut out, b"AA"), Ok((1, 2)));
1382 // 3 chars, 2 buffered bits, non-zero: 'B' == 000001.
1383 assert_eq!(from_base64(&mut out, b"AAB"), Err(Error::DecodingFail));
1384 assert_eq!(from_base64(&mut out, b"AAA"), Ok((2, 3)));
1385 }
1386
1387 #[test]
1388 fn from_base64_rejects_a_short_buffer() {
1389 let mut out = [0u8; 4];
1390 assert_eq!(
1391 from_base64(&mut out, b"c29tZXNhbHQ"),
1392 Err(Error::DecodingFail)
1393 );
1394 // The C's `if ((len++) >= *dst_len)` tests the pre-increment value, so
1395 // an exactly-sized buffer is fine and one byte less is not.
1396 let mut exact = [0u8; 8];
1397 assert_eq!(from_base64(&mut exact, b"c29tZXNhbHQ"), Ok((8, 11)));
1398 assert_eq!(&exact, b"somesalt");
1399 let mut tight = [0u8; 7];
1400 assert_eq!(
1401 from_base64(&mut tight, b"c29tZXNhbHQ"),
1402 Err(Error::DecodingFail)
1403 );
1404 }
1405
1406 // -- decode_decimal -----------------------------------------------------
1407
1408 #[test]
1409 fn decode_decimal_matches_c() {
1410 assert_eq!(decode_decimal(b"0"), Some((0, 1)));
1411 assert_eq!(decode_decimal(b"19"), Some((19, 2)));
1412 assert_eq!(decode_decimal(b"65536,t=2"), Some((65536, 5)));
1413 assert_eq!(decode_decimal(b"4294967295"), Some((4294967295, 10)));
1414 // No digits at all.
1415 assert_eq!(decode_decimal(b""), None);
1416 assert_eq!(decode_decimal(b"$"), None);
1417 assert_eq!(decode_decimal(b"x1"), None);
1418 // Non-minimal.
1419 assert_eq!(decode_decimal(b"01"), None);
1420 assert_eq!(decode_decimal(b"00"), None);
1421 assert_eq!(decode_decimal(b"0019"), None);
1422 // Overflow of `unsigned long`.
1423 assert_eq!(
1424 decode_decimal(b"18446744073709551615"),
1425 Some((u64::MAX, 20))
1426 );
1427 assert_eq!(decode_decimal(b"18446744073709551616"), None);
1428 assert_eq!(decode_decimal(b"99999999999999999999999"), None);
1429 }
1430
1431 #[test]
1432 fn decode_decimal_matches_the_c_dump() {
1433 // Every pair below was produced by running the C's `decode_decimal`
1434 // (it is `static`, so via a harness that #includes encoding.c) linked
1435 // against phc-winner-argon2/libargon2.a. `None` is the C's NULL.
1436 /// `(input, decode_decimal(input))`, the C's NULL being `None`.
1437 type Case = (&'static [u8], Option<(u64, usize)>);
1438
1439 let cases: &[Case] = &[
1440 (b"", None),
1441 (b"0", Some((0, 1))),
1442 (b"00", None),
1443 (b"000", None),
1444 (b"01", None),
1445 (b"0019", None),
1446 (b"1", Some((1, 1))),
1447 (b"9", Some((9, 1))),
1448 (b"19", Some((19, 2))),
1449 (b"10", Some((10, 2))),
1450 (b"007", None),
1451 (b"4294967295", Some((4294967295, 10))),
1452 (b"4294967296", Some((4294967296, 10))),
1453 (b"9223372036854775807", Some((9223372036854775807, 19))),
1454 (b"18446744073709551615", Some((18446744073709551615, 20))),
1455 (b"18446744073709551616", None),
1456 (b"18446744073709551620", None),
1457 (b"19999999999999999999", None),
1458 (b"20000000000000000000", None),
1459 (b"99999999999999999999", None),
1460 (b"1000000000000000000000000", None),
1461 (b"65536,t=2", Some((65536, 5))),
1462 (b"1,t=2", Some((1, 1))),
1463 (b"x", None),
1464 (b"1x", Some((1, 1))),
1465 (b" 1", None),
1466 (b"+1", None),
1467 (b"-1", None),
1468 (b"1 ", Some((1, 1))),
1469 // "0" is minimal on its own, so the 'x' just ends the run.
1470 (b"0x10", Some((0, 1))),
1471 (b"12$", Some((12, 2))),
1472 (b"0,", Some((0, 1))),
1473 (b"10$", Some((10, 2))),
1474 (b"$", None),
1475 (b"1234567890", Some((1234567890, 10))),
1476 ];
1477 for (input, want) in cases {
1478 assert_eq!(
1479 decode_decimal(input),
1480 *want,
1481 "input {:?}",
1482 core::str::from_utf8(input).unwrap_or("<non-utf8>")
1483 );
1484 }
1485 }
1486
1487 #[test]
1488 fn decimal_fields_are_u32_bounded() {
1489 // DECIMAL_U32 rejects anything above UINT32_MAX.
1490 let too_big = "$argon2i$v=19$m=4294967296,t=2,p=1$c29tZXNhbHQ\
1491 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1492 assert_eq!(
1493 decode_string(too_big, Algorithm::Argon2i),
1494 Err(Error::DecodingFail)
1495 );
1496 let leading_zero = "$argon2i$v=19$m=065536,t=2,p=1$c29tZXNhbHQ\
1497 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1498 assert_eq!(
1499 decode_string(leading_zero, Algorithm::Argon2i),
1500 Err(Error::DecodingFail)
1501 );
1502 }
1503
1504 // -- encode -------------------------------------------------------------
1505
1506 #[test]
1507 fn encode_matches_the_official_vectors() {
1508 let params = Params::new(65536, 2, 1, 32).unwrap();
1509 let tag = unb64(b"wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA").unwrap();
1510 let encoded = encode_string_alloc(
1511 Algorithm::Argon2i,
1512 Version::V0x13,
1513 ¶ms,
1514 b"somesalt",
1515 &tag,
1516 )
1517 .unwrap();
1518 assert_eq!(encoded, V13_ARGON2I);
1519
1520 let tag = unb64(b"CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc").unwrap();
1521 let encoded = encode_string_alloc(
1522 Algorithm::Argon2id,
1523 Version::V0x13,
1524 ¶ms,
1525 b"somesalt",
1526 &tag,
1527 )
1528 .unwrap();
1529 assert_eq!(encoded, V13_ARGON2ID);
1530
1531 // The C always emits "$v=", even for 0x10.
1532 let encoded = encode_string_alloc(
1533 Algorithm::Argon2i,
1534 Version::V0x10,
1535 ¶ms,
1536 b"somesalt",
1537 &V10_TAG,
1538 )
1539 .unwrap();
1540 assert_eq!(
1541 encoded,
1542 "$argon2i$v=16$m=65536,t=2,p=1$c29tZXNhbHQ\
1543 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
1544 );
1545 }
1546
1547 #[test]
1548 fn encode_needs_encoded_len_bytes_exactly() {
1549 let params = Params::new(65536, 2, 1, 32).unwrap();
1550 let want = encoded_len(Algorithm::Argon2i, 2, 65536, 1, 8, 32);
1551 assert_eq!(want, V13_ARGON2I.len() + 1);
1552
1553 let mut buf = vec![0u8; want];
1554 let n = encode_string(
1555 &mut buf,
1556 Algorithm::Argon2i,
1557 Version::V0x13,
1558 ¶ms,
1559 b"somesalt",
1560 &V10_TAG,
1561 )
1562 .unwrap();
1563 assert_eq!(n, want - 1);
1564
1565 // One byte less is a failure, exactly as in the C.
1566 let mut buf = vec![0u8; want - 1];
1567 assert_eq!(
1568 encode_string(
1569 &mut buf,
1570 Algorithm::Argon2i,
1571 Version::V0x13,
1572 ¶ms,
1573 b"somesalt",
1574 &V10_TAG,
1575 ),
1576 Err(Error::EncodingFail)
1577 );
1578 }
1579
1580 #[test]
1581 fn encode_validates_first() {
1582 let params = Params::new(65536, 2, 1, 32).unwrap();
1583 // Short salt: validate_inputs runs before anything is written.
1584 assert_eq!(
1585 encode_string_alloc(
1586 Algorithm::Argon2i,
1587 Version::V0x13,
1588 ¶ms,
1589 b"short",
1590 &V10_TAG
1591 ),
1592 Err(Error::SaltTooShort)
1593 );
1594 // Short tag.
1595 assert_eq!(
1596 encode_string_alloc(
1597 Algorithm::Argon2i,
1598 Version::V0x13,
1599 ¶ms,
1600 b"somesalt",
1601 &[0u8; 3]
1602 ),
1603 Err(Error::OutputTooShort)
1604 );
1605 }
1606
1607 // -- decode -------------------------------------------------------------
1608
1609 #[test]
1610 fn decode_the_v13_vector() {
1611 let d = decode_string(V13_ARGON2I, Algorithm::Argon2i).unwrap();
1612 assert_eq!(d.algorithm, Algorithm::Argon2i);
1613 assert_eq!(d.version, Version::V0x13);
1614 assert_eq!(d.params.m_cost(), 65536);
1615 assert_eq!(d.params.t_cost(), 2);
1616 assert_eq!(d.params.lanes(), 1);
1617 assert_eq!(d.params.output_len(), 32);
1618 assert_eq!(d.salt, b"somesalt");
1619 assert_eq!(
1620 d.hash,
1621 unb64(b"wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA").unwrap()
1622 );
1623 }
1624
1625 #[test]
1626 fn decode_defaults_the_version_to_0x10() {
1627 let d = decode_string(V10_ARGON2I, Algorithm::Argon2i).unwrap();
1628 assert_eq!(d.version, Version::V0x10);
1629 assert_eq!(d.salt, b"somesalt");
1630 assert_eq!(d.hash, &V10_TAG);
1631 }
1632
1633 #[test]
1634 fn decode_sets_threads_to_lanes() {
1635 let s = "$argon2id$v=19$m=65536,t=2,p=4$c29tZXNhbHQ\
1636 $CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
1637 let d = decode_string(s, Algorithm::Argon2id).unwrap();
1638 assert_eq!(d.params.lanes(), 4);
1639 assert_eq!(d.params.threads(), 4);
1640 }
1641
1642 #[test]
1643 fn encode_decode_round_trip() {
1644 let salt = b"0123456789abcdef";
1645 let tag: Vec<u8> = (0u8..48).collect();
1646 for algorithm in Algorithm::ALL {
1647 for version in Version::ALL {
1648 for lanes in [1u32, 2, 255] {
1649 let params = Params::new(1 << 16, 3, lanes, tag.len()).unwrap();
1650 let encoded =
1651 encode_string_alloc(algorithm, version, ¶ms, salt, &tag).unwrap();
1652 let d = decode_string(&encoded, algorithm).unwrap();
1653 assert_eq!(d.algorithm, algorithm);
1654 assert_eq!(d.version, version);
1655 assert_eq!(d.params, params);
1656 assert_eq!(d.salt, salt);
1657 assert_eq!(d.hash, tag);
1658 // And re-encoding is byte-identical.
1659 assert_eq!(
1660 encode_string_alloc(d.algorithm, d.version, &d.params, &d.salt, &d.hash)
1661 .unwrap(),
1662 encoded
1663 );
1664 }
1665 }
1666 }
1667 }
1668
1669 // The four malformed strings from `src/test.c`, both version flavours.
1670
1671 #[test]
1672 fn decode_rejects_a_missing_dollar_before_the_salt() {
1673 // "…,p=1c29tZXNhbHQ$…": the '$' after p=1 is gone.
1674 let v10 = "$argon2i$m=65536,t=2,p=1c29tZXNhbHQ\
1675 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1676 assert_eq!(
1677 decode_string(v10, Algorithm::Argon2i),
1678 Err(Error::DecodingFail)
1679 );
1680 let v13 = "$argon2i$v=19$m=65536,t=2,p=1c29tZXNhbHQ\
1681 $wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA";
1682 assert_eq!(
1683 decode_string(v13, Algorithm::Argon2i),
1684 Err(Error::DecodingFail)
1685 );
1686 }
1687
1688 #[test]
1689 fn decode_rejects_a_missing_dollar_before_the_tag() {
1690 // The salt and tag run together into one 54-character base64 field,
1691 // which decodes cleanly (54 chars leave 4 zero bits); the failure comes
1692 // from the CC("$") that follows.
1693 let v10 = "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ\
1694 9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1695 assert_eq!(
1696 decode_string(v10, Algorithm::Argon2i),
1697 Err(Error::DecodingFail)
1698 );
1699 let v13 = "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ\
1700 wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA";
1701 assert_eq!(
1702 decode_string(v13, Algorithm::Argon2i),
1703 Err(Error::DecodingFail)
1704 );
1705 }
1706
1707 #[test]
1708 fn decode_reports_salt_too_short_not_decoding_fail() {
1709 // This is the distinction tests/vectors.rs relies on: the string parses,
1710 // and it is validate_inputs() that rejects it.
1711 let v10 = "$argon2i$m=65536,t=2,p=1$\
1712 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1713 assert_eq!(
1714 decode_string(v10, Algorithm::Argon2i),
1715 Err(Error::SaltTooShort)
1716 );
1717 let v13 = "$argon2i$v=19$m=65536,t=2,p=1$\
1718 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1719 assert_eq!(
1720 decode_string(v13, Algorithm::Argon2i),
1721 Err(Error::SaltTooShort)
1722 );
1723 // A 7-byte salt is also too short, and still not a DecodingFail.
1724 let short = "$argon2i$v=19$m=65536,t=2,p=1$c2hvcnRz\
1725 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1726 assert_eq!(
1727 decode_string(short, Algorithm::Argon2i),
1728 Err(Error::SaltTooShort)
1729 );
1730 }
1731
1732 #[test]
1733 fn decode_argon2i_is_a_prefix_of_argon2id() {
1734 // CC("argon2i") matches the first seven characters of "argon2id"; the
1735 // leftover 'd' then fails the CC("$m=") (or the CC_opt("$v=")).
1736 assert_eq!(
1737 decode_string(V13_ARGON2ID, Algorithm::Argon2i),
1738 Err(Error::DecodingFail)
1739 );
1740 let v10_id = "$argon2id$m=65536,t=2,p=1$c29tZXNhbHQ\
1741 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1742 assert_eq!(
1743 decode_string(v10_id, Algorithm::Argon2i),
1744 Err(Error::DecodingFail)
1745 );
1746 // And the other way round: "argon2i" is not "argon2id".
1747 assert_eq!(
1748 decode_string(V13_ARGON2I, Algorithm::Argon2id),
1749 Err(Error::DecodingFail)
1750 );
1751 assert_eq!(
1752 decode_string(V13_ARGON2I, Algorithm::Argon2d),
1753 Err(Error::DecodingFail)
1754 );
1755 // The correct type still works, of course.
1756 assert!(decode_string(V13_ARGON2ID, Algorithm::Argon2id).is_ok());
1757 }
1758
1759 #[test]
1760 fn decode_rejects_structural_damage() {
1761 let cases: &[&str] = &[
1762 "",
1763 "$",
1764 "argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1765 "$argon2i",
1766 "$argon2i$v=19",
1767 "$argon2i$v=19$m=65536,t=2,p=1",
1768 "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ",
1769 // 'x' is not a decimal digit.
1770 "$argon2i$v=19$m=x,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1771 // Fields out of order.
1772 "$argon2i$v=19$t=2,m=65536,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1773 // Trailing junk after the tag.
1774 "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ$",
1775 "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ ",
1776 // '=' padding is not part of the alphabet, so it ends the field.
1777 "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ=$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1778 ];
1779 for case in cases {
1780 assert_eq!(
1781 decode_string(case, Algorithm::Argon2i),
1782 Err(Error::DecodingFail),
1783 "expected DecodingFail for {case:?}"
1784 );
1785 }
1786 }
1787
1788 #[test]
1789 fn decode_surfaces_the_c_validation_codes() {
1790 // A 3-byte tag: outlen is checked first.
1791 assert_eq!(
1792 decode_string(
1793 "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$AAAA",
1794 Algorithm::Argon2i
1795 ),
1796 Err(Error::OutputTooShort)
1797 );
1798 // m_cost < ARGON2_MIN_MEMORY.
1799 assert_eq!(
1800 decode_string(
1801 "$argon2i$v=19$m=1,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1802 Algorithm::Argon2i
1803 ),
1804 Err(Error::MemoryTooLittle)
1805 );
1806 // m_cost < 8 * lanes.
1807 assert_eq!(
1808 decode_string(
1809 "$argon2i$v=19$m=16,t=2,p=4$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1810 Algorithm::Argon2i
1811 ),
1812 Err(Error::MemoryTooLittle)
1813 );
1814 // t_cost < ARGON2_MIN_TIME.
1815 assert_eq!(
1816 decode_string(
1817 "$argon2i$v=19$m=65536,t=0,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1818 Algorithm::Argon2i
1819 ),
1820 Err(Error::TimeTooSmall)
1821 );
1822 // lanes < ARGON2_MIN_LANES.
1823 assert_eq!(
1824 decode_string(
1825 "$argon2i$v=19$m=65536,t=2,p=0$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1826 Algorithm::Argon2i
1827 ),
1828 Err(Error::LanesTooFew)
1829 );
1830 // lanes > ARGON2_MAX_LANES (16777215).
1831 #[cfg(target_pointer_width = "64")]
1832 assert_eq!(
1833 decode_string(
1834 "$argon2i$v=19$m=4294967295,t=2,p=16777216$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1835 Algorithm::Argon2i
1836 ),
1837 Err(Error::LanesTooMany)
1838 );
1839 // On a 32-bit target ARGON2_MAX_MEMORY is 2 MiB (the C's own
1840 // pointer-width rule), so — exactly as the C on 32-bit — the memory
1841 // check fires before the lanes check gets a chance to.
1842 #[cfg(target_pointer_width = "32")]
1843 assert_eq!(
1844 decode_string(
1845 "$argon2i$v=19$m=4294967295,t=2,p=16777216$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
1846 Algorithm::Argon2i
1847 ),
1848 Err(Error::MemoryTooMuch)
1849 );
1850 }
1851
1852 #[test]
1853 fn validation_runs_before_the_trailing_character_check() {
1854 // Both wrong: the C returns SALT_TOO_SHORT because validate_inputs()
1855 // comes first.
1856 let s = "$argon2i$v=19$m=65536,t=2,p=1$\
1857 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ!!!";
1858 assert_eq!(
1859 decode_string(s, Algorithm::Argon2i),
1860 Err(Error::SaltTooShort)
1861 );
1862 }
1863
1864 #[test]
1865 fn decode_rejects_an_unrepresentable_version() {
1866 // Documented divergence: the C accepts this (validate_inputs never
1867 // looks at the version) and treats it as 0x13.
1868 let s = "$argon2i$v=99$m=65536,t=2,p=1$c29tZXNhbHQ\
1869 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1870 assert_eq!(
1871 decode_string(s, Algorithm::Argon2i),
1872 Err(Error::DecodingFail)
1873 );
1874 // …but an earlier error still wins, so the codes stay C-compatible.
1875 let s = "$argon2i$v=99$m=65536,t=2,p=1$$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1876 assert_eq!(
1877 decode_string(s, Algorithm::Argon2i),
1878 Err(Error::SaltTooShort)
1879 );
1880 }
1881
1882 #[test]
1883 fn decode_accepts_a_long_salt_and_tag() {
1884 let salt: Vec<u8> = (0u8..=255).collect();
1885 let tag: Vec<u8> = (0u8..=200).rev().collect();
1886 let params = Params::new(1 << 16, 1, 1, tag.len()).unwrap();
1887 let encoded =
1888 encode_string_alloc(Algorithm::Argon2d, Version::V0x13, ¶ms, &salt, &tag).unwrap();
1889 let d = decode_string(&encoded, Algorithm::Argon2d).unwrap();
1890 assert_eq!(d.salt, salt);
1891 assert_eq!(d.hash, tag);
1892 assert_eq!(d.params.output_len(), tag.len());
1893 }
1894}