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