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 to_base64_raw(dst.as_mut_ptr(), dst.len(), src)
398}
399
400fn to_base64_raw(dst: *mut u8, dst_len: usize, src: &[u8]) -> Result<usize, Error> {
401 if src.len() < crate::base64::MIN_ENCODE_LEN {
402 // Keep backend lookup and the generalized prefix state completely out
403 // of tiny salts. This is the original scalar function's exact shape.
404 return to_base64_scalar(dst, dst_len, src);
405 }
406 let backend = crate::base64::base64_backend();
407 // SAFETY: runtime detection returns only an executable backend.
408 unsafe { to_base64_with_backend_raw(dst, dst_len, src, backend) }
409}
410
411/// The original reference-C loop, kept whole so short inputs do not pay for or
412/// inhibit optimization around a SIMD prefix they cannot use.
413///
414/// Writes only; `dst` may be uninitialized spare capacity.
415#[inline(always)]
416fn to_base64_scalar(dst: *mut u8, dst_len: usize, src: &[u8]) -> Result<usize, Error> {
417 let olen = b64_len_usize(src.len());
418 if dst_len <= olen {
419 return Err(Error::EncodingFail);
420 }
421
422 let mut acc: u32 = 0;
423 let mut acc_len: u32 = 0;
424 let mut written = 0usize;
425 for &byte in src {
426 acc = (acc << 8) | byte as u32;
427 acc_len += 8;
428 while acc_len >= 6 {
429 acc_len -= 6;
430 // SAFETY: `written < olen < dst_len`.
431 unsafe {
432 dst.add(written)
433 .write(b64_byte_to_char((acc >> acc_len) & 0x3f));
434 }
435 written += 1;
436 }
437 }
438 if acc_len > 0 {
439 // SAFETY: the last leftover character is still inside `olen`.
440 unsafe {
441 dst.add(written)
442 .write(b64_byte_to_char((acc << (6 - acc_len)) & 0x3f));
443 }
444 written += 1;
445 }
446
447 debug_assert_eq!(written, olen);
448 Ok(written)
449}
450
451/// Encode with an explicitly selected Base64 backend.
452///
453/// This is an unstable test/benchmark hook. Normal callers use [`to_base64`],
454/// which performs safe runtime detection.
455///
456/// # Safety
457///
458/// `backend` must be executable on the current CPU, as reported by
459/// [`Base64Backend::is_available`].
460#[inline]
461pub unsafe fn to_base64_with_backend(
462 dst: &mut [u8],
463 src: &[u8],
464 backend: Base64Backend,
465) -> Result<usize, Error> {
466 // SAFETY: `dst` is a live initialized slice; the backend contract is the
467 // caller's, same as before this raw-pointer split.
468 unsafe { to_base64_with_backend_raw(dst.as_mut_ptr(), dst.len(), src, backend) }
469}
470
471unsafe fn to_base64_with_backend_raw(
472 dst: *mut u8,
473 dst_len: usize,
474 src: &[u8],
475 backend: Base64Backend,
476) -> Result<usize, Error> {
477 if backend == Base64Backend::Scalar {
478 return to_base64_scalar(dst, dst_len, src);
479 }
480
481 let olen = b64_len_usize(src.len());
482 if dst_len <= olen {
483 return Err(Error::EncodingFail);
484 }
485
486 // SAFETY: transferred from this function's caller. The capacity check
487 // above proves every complete vector store is within `dst_len`.
488 let (consumed, mut written) = unsafe { crate::base64::encode_prefix(backend, dst, src) };
489 let mut acc: u32 = 0;
490 let mut acc_len: u32 = 0;
491
492 for &byte in &src[consumed..] {
493 // The C writes `(acc << 8) + *buf++`; the low 8 bits of `acc << 8` are
494 // zero, so `|` is the same value and cannot overflow in debug builds.
495 acc = (acc << 8) | byte as u32;
496 acc_len += 8;
497 while acc_len >= 6 {
498 acc_len -= 6;
499 // SAFETY: `written` stays inside `olen < dst_len`.
500 unsafe {
501 dst.add(written)
502 .write(b64_byte_to_char((acc >> acc_len) & 0x3F));
503 }
504 written += 1;
505 }
506 }
507 if acc_len > 0 {
508 // SAFETY: leftover character is the last of `olen`.
509 unsafe {
510 dst.add(written)
511 .write(b64_byte_to_char((acc << (6 - acc_len)) & 0x3F));
512 }
513 written += 1;
514 }
515
516 debug_assert!(written == olen);
517 Ok(written)
518}
519
520/// `from_base64(dst, dst_len, src)`.
521///
522/// Decodes until the first non-base64 byte. Returns
523/// `(bytes_written, bytes_consumed)`, where `bytes_consumed` indexes the first
524/// non-base64 byte in `src` — the equivalent of the pointer the C returns. The
525/// end of the slice acts as the C's terminating NUL, which is itself not a
526/// base64 character.
527///
528/// # Errors
529///
530/// [`Error::DecodingFail`] if `dst` is too small, if `acc_len > 4` at the end,
531/// or if any buffered low bits are non-zero.
532pub fn from_base64(dst: &mut [u8], src: &[u8]) -> Result<(usize, usize), Error> {
533 from_base64_raw(dst.as_mut_ptr(), dst.len(), src)
534}
535
536fn from_base64_raw(dst: *mut u8, dst_len: usize, src: &[u8]) -> Result<(usize, usize), Error> {
537 if src.len() < crate::base64::MIN_DECODE_LEN {
538 // As in the encoder, keep both lookup and generalized prefix state out
539 // of inputs too short for this architecture's smallest vector.
540 return from_base64_scalar(dst, dst_len, src);
541 }
542 let backend = crate::base64::base64_backend();
543 // SAFETY: as in `to_base64`, detection proves the feature contract.
544 unsafe { from_base64_with_backend_raw(dst, dst_len, src, backend) }
545}
546
547/// The original reference-C loop, kept whole for the scalar and short-input
548/// paths just like [`to_base64_scalar`].
549///
550/// Writes only; `dst` may be uninitialized spare capacity.
551#[inline(always)]
552fn from_base64_scalar(dst: *mut u8, dst_len: usize, src: &[u8]) -> Result<(usize, usize), Error> {
553 let mut consumed = 0usize;
554 let mut len = 0usize;
555 let mut acc: u32 = 0;
556 let mut acc_len: u32 = 0;
557
558 loop {
559 // Past the end of the slice, feed the NUL the C would have read.
560 let c = match src.get(consumed) {
561 Some(&byte) => byte as u32,
562 None => 0,
563 };
564 let d = b64_char_to_byte(c);
565 if d == 0xFF {
566 break;
567 }
568 consumed += 1;
569 acc = (acc << 6) | d;
570 acc_len += 6;
571 if acc_len >= 8 {
572 acc_len -= 8;
573 if len >= dst_len {
574 return Err(Error::DecodingFail);
575 }
576 // SAFETY: `len < dst_len`.
577 unsafe {
578 dst.add(len).write(((acc >> acc_len) & 0xFF) as u8);
579 }
580 len += 1;
581 }
582 }
583
584 if acc_len > 4 || (acc & ((1u32 << acc_len) - 1)) != 0 {
585 return Err(Error::DecodingFail);
586 }
587
588 Ok((len, consumed))
589}
590
591/// Decode with an explicitly selected Base64 backend.
592///
593/// This preserves [`from_base64`]'s exact stopping and error behavior and is
594/// exposed only as an unstable differential-test/benchmark hook.
595///
596/// # Safety
597///
598/// `backend` must be executable on the current CPU, as reported by
599/// [`Base64Backend::is_available`].
600#[inline]
601pub unsafe fn from_base64_with_backend(
602 dst: &mut [u8],
603 src: &[u8],
604 backend: Base64Backend,
605) -> Result<(usize, usize), Error> {
606 // SAFETY: `dst` is a live slice; the backend contract is the caller's.
607 unsafe { from_base64_with_backend_raw(dst.as_mut_ptr(), dst.len(), src, backend) }
608}
609
610unsafe fn from_base64_with_backend_raw(
611 dst: *mut u8,
612 dst_len: usize,
613 src: &[u8],
614 backend: Base64Backend,
615) -> Result<(usize, usize), Error> {
616 if backend == Base64Backend::Scalar {
617 return from_base64_scalar(dst, dst_len, src);
618 }
619
620 // SAFETY: transferred from this function's caller. Each backend checks the
621 // supplied slice lengths before loading or storing a complete block.
622 let (mut consumed, mut len) =
623 unsafe { crate::base64::decode_prefix(backend, dst, dst_len, src) };
624 let mut acc: u32 = 0;
625 let mut acc_len: u32 = 0;
626
627 loop {
628 // Past the end of the slice, feed the NUL the C would have read.
629 let c = match src.get(consumed) {
630 Some(&byte) => byte as u32,
631 None => 0,
632 };
633 let d = b64_char_to_byte(c);
634 if d == 0xFF {
635 break;
636 }
637 consumed += 1;
638 // As in `to_base64`, `|` matches the C's `+` bit for bit.
639 acc = (acc << 6) | d;
640 acc_len += 6;
641 if acc_len >= 8 {
642 acc_len -= 8;
643 // The C is `if ((len++) >= *dst_len) return NULL;`, i.e. the test
644 // uses the pre-increment value.
645 if len >= dst_len {
646 return Err(Error::DecodingFail);
647 }
648 // SAFETY: `len < dst_len`.
649 unsafe {
650 dst.add(len).write(((acc >> acc_len) & 0xFF) as u8);
651 }
652 len += 1;
653 }
654 }
655
656 // An input length of 1 modulo 4 leaves 6 unprocessed bits, which is
657 // invalid; otherwise 0, 2 or 4 bits are buffered and they must be zero.
658 if acc_len > 4 || (acc & ((1u32 << acc_len) - 1)) != 0 {
659 return Err(Error::DecodingFail);
660 }
661
662 Ok((len, consumed))
663}
664
665/// Encode `src` as unpadded standard Base64, the alphabet PHC strings use.
666///
667/// Same dispatch as [`to_base64`]: SIMD when `src` is at least one vector
668/// long (16 bytes on x86, 24 on aarch64), scalar below that.
669///
670/// ```
671/// use argon2_rust::encode_base64;
672///
673/// assert_eq!(encode_base64(b"somesalt")?, "c29tZXNhbHQ");
674/// # Ok::<(), argon2_rust::Error>(())
675/// ```
676///
677/// # Errors
678///
679/// [`Error::MemoryAllocationError`] if the output buffer cannot be allocated.
680pub fn encode_base64(src: &[u8]) -> Result<String, Error> {
681 // `to_base64` requires `dst_len > olen` (the C writes a NUL).
682 let cap = b64_len_usize(src.len()) + 1;
683 let mut buf = reserve_vec(cap)?;
684 let written = to_base64_raw(buf.as_mut_ptr(), cap, src)?;
685 // SAFETY: `to_base64_raw` wrote `written` bytes of the Base64 alphabet.
686 unsafe {
687 buf.set_len(written);
688 }
689 Ok(ascii_to_string(buf))
690}
691
692/// Decode unpadded standard Base64, the alphabet PHC strings use.
693///
694/// Same dispatch as [`from_base64`]. The whole of `src` must be alphabet
695/// characters; leftover junk is [`Error::DecodingFail`], unlike the C's
696/// `from_base64`, which stops at the first non-alphabet byte and leaves the
697/// tail for the caller. PHC fields have no tail.
698///
699/// ```
700/// use argon2_rust::decode_base64;
701///
702/// assert_eq!(decode_base64(b"c29tZXNhbHQ")?, b"somesalt");
703/// # Ok::<(), argon2_rust::Error>(())
704/// ```
705///
706/// # Errors
707///
708/// [`Error::DecodingFail`] if `src` is not entirely valid unpadded Base64, or
709/// [`Error::MemoryAllocationError`] if the output buffer cannot be allocated.
710pub fn decode_base64(src: &[u8]) -> Result<Vec<u8>, Error> {
711 // Isolated PHC fields are all alphabet; `n * 3 / 4` is exact for every
712 // valid unpadded length and is the most a valid decode can write.
713 let cap = src.len() * 3 / 4;
714 let mut buf = reserve_vec(cap)?;
715 let (written, consumed) = from_base64_raw(buf.as_mut_ptr(), cap, src)?;
716 if consumed != src.len() {
717 return Err(Error::DecodingFail);
718 }
719 // SAFETY: `from_base64_raw` wrote `written` bytes and `written <= cap`.
720 unsafe {
721 buf.set_len(written);
722 }
723 Ok(buf)
724}
725
726// ---------------------------------------------------------------------------
727// decode_decimal
728// ---------------------------------------------------------------------------
729
730/// `decode_decimal(str, v)`.
731///
732/// Returns `(value, digits_consumed)`, or `None` when there is no digit at all,
733/// when the encoding is not minimal (a leading `'0'` with more digits after
734/// it), or when the value overflows.
735///
736/// The C accumulates in `unsigned long`, which is 64-bit on every target this
737/// crate supports, so `u64` matches it. On a hypothetical 32-bit `unsigned
738/// long` the outcome would still be the same, because every caller is
739/// `DECIMAL_U32`, which rejects anything above `u32::MAX` anyway.
740fn decode_decimal(src: &[u8]) -> Option<(u64, usize)> {
741 let mut acc: u64 = 0;
742 let mut i = 0usize;
743
744 while let Some(&c) = src.get(i) {
745 if !c.is_ascii_digit() {
746 break;
747 }
748 let digit = (c - b'0') as u64;
749 if acc > u64::MAX / 10 {
750 return None;
751 }
752 acc *= 10;
753 if digit > u64::MAX - acc {
754 return None;
755 }
756 acc += digit;
757 i += 1;
758 }
759
760 // `if (str == orig || (*orig == '0' && str != (orig + 1))) return NULL;`
761 if i == 0 {
762 return None;
763 }
764 if src[0] == b'0' && i != 1 {
765 return None;
766 }
767
768 Some((acc, i))
769}
770
771// ---------------------------------------------------------------------------
772// encode_string
773// ---------------------------------------------------------------------------
774
775/// The C's `SS`/`SX`/`SB` macros: a cursor that always keeps one byte spare for
776/// the NUL terminator the C writes, so the capacity requirement is identical.
777struct Writer {
778 dst: *mut u8,
779 dst_len: usize,
780 pos: usize,
781}
782
783impl Writer {
784 /// `SS(str)`: `if (pp_len >= dst_len) return ARGON2_ENCODING_FAIL;`.
785 fn put(&mut self, bytes: &[u8]) -> Result<(), Error> {
786 let remaining = self.dst_len - self.pos;
787 if bytes.len() >= remaining {
788 return Err(Error::EncodingFail);
789 }
790 // SAFETY: the capacity check leaves room for `bytes`, and this write
791 // does not read the destination.
792 unsafe {
793 self.dst
794 .add(self.pos)
795 .copy_from_nonoverlapping(bytes.as_ptr(), bytes.len());
796 }
797 self.pos += bytes.len();
798 Ok(())
799 }
800
801 /// `SX(x)`: the decimal form of `x`, no allocation.
802 fn put_u32(&mut self, value: u32) -> Result<(), Error> {
803 // `u32::MAX` is 4294967295: ten digits.
804 let mut buf = [0u8; 10];
805 let mut i = buf.len();
806 let mut n = value;
807 loop {
808 i -= 1;
809 buf[i] = b'0' + (n % 10) as u8;
810 n /= 10;
811 if n == 0 {
812 break;
813 }
814 }
815 self.put(&buf[i..])
816 }
817
818 /// `SB(buf, len)`: base64, which does its own capacity check.
819 fn put_base64(&mut self, src: &[u8]) -> Result<(), Error> {
820 // SAFETY: `self.pos <= self.dst_len`; `to_base64_raw` only writes.
821 let written = to_base64_raw(
822 unsafe { self.dst.add(self.pos) },
823 self.dst_len - self.pos,
824 src,
825 )?;
826 self.pos += written;
827 Ok(())
828 }
829}
830
831/// `validate_inputs(ctx)` as `encode_string` and `decode_string` run it.
832///
833/// `out_len` is the *tag* length, which is what `ctx->outlen` holds in both
834/// call sites, and `pwd_len` is 0 (see the module-level divergence note).
835fn validate_for_string(params: &Params, salt_len: usize, hash_len: usize) -> Result<(), Error> {
836 validate_inputs(
837 hash_len,
838 0,
839 salt_len,
840 0,
841 0,
842 params.memory_kib(),
843 params.passes(),
844 params.lanes(),
845 params.threads(),
846 )
847}
848
849/// `encode_string(dst, dst_len, ctx, type)`.
850///
851/// Writes the PHC string into `dst` (no NUL terminator) and returns its length.
852/// Always emits `$v=`, as the C does.
853///
854/// `dst` must hold the string **plus one byte**, because the C reserves room
855/// for its NUL terminator and this port keeps the capacity rule identical:
856/// a buffer of exactly [`encoded_len`] bytes is what succeeds, in Rust and in
857/// C alike.
858///
859/// `hash.len()` plays the role of `ctx->outlen` — it is the length that gets
860/// encoded, so it, and not [`Params::tag_len_bytes`], is what the leading
861/// `validate_inputs()` checks. For a tag produced from these `params` the two
862/// are the same value.
863///
864/// # Errors
865///
866/// [`Error::EncodingFail`] if `dst` is too small, or whatever
867/// [`crate::params::validate_inputs`] returns — `encode_string` runs it first,
868/// exactly like the C.
869pub fn encode_string(
870 dst: &mut [u8],
871 algorithm: Algorithm,
872 version: Version,
873 params: &Params,
874 salt: &[u8],
875 hash: &[u8],
876) -> Result<usize, Error> {
877 encode_string_raw(dst.as_mut_ptr(), dst.len(), algorithm, version, params, salt, hash)
878}
879
880fn encode_string_raw(
881 dst: *mut u8,
882 dst_len: usize,
883 algorithm: Algorithm,
884 version: Version,
885 params: &Params,
886 salt: &[u8],
887 hash: &[u8],
888) -> Result<usize, Error> {
889 validate_for_string(params, salt.len(), hash.len())?;
890
891 let mut w = Writer {
892 dst,
893 dst_len,
894 pos: 0,
895 };
896
897 w.put(b"$")?;
898 w.put(algorithm.as_str().as_bytes())?;
899
900 w.put(b"$v=")?;
901 w.put_u32(version.as_u32())?;
902
903 w.put(b"$m=")?;
904 w.put_u32(params.memory_kib())?;
905 w.put(b",t=")?;
906 w.put_u32(params.passes())?;
907 w.put(b",p=")?;
908 w.put_u32(params.lanes())?;
909
910 w.put(b"$")?;
911 w.put_base64(salt)?;
912
913 w.put(b"$")?;
914 w.put_base64(hash)?;
915
916 Ok(w.pos)
917}
918
919/// [`encode_string`] into a freshly allocated [`String`].
920///
921/// # Errors
922///
923/// As [`encode_string`], plus [`Error::MemoryAllocationError`] if the buffer
924/// cannot be allocated (the C returns the same code when its `malloc` fails).
925pub fn encode_string_alloc(
926 algorithm: Algorithm,
927 version: Version,
928 params: &Params,
929 salt: &[u8],
930 hash: &[u8],
931) -> Result<String, Error> {
932 // Validate before allocating, so an over-long salt reports SaltTooLong
933 // rather than failing to allocate a buffer sized from it.
934 validate_for_string(params, salt.len(), hash.len())?;
935
936 let capacity = encoded_len_usize(
937 algorithm,
938 params.passes(),
939 params.memory_kib(),
940 params.lanes(),
941 salt.len(),
942 hash.len(),
943 );
944
945 let mut buf = reserve_vec(capacity)?;
946 let written = encode_string_raw(
947 buf.as_mut_ptr(),
948 capacity,
949 algorithm,
950 version,
951 params,
952 salt,
953 hash,
954 )?;
955 // SAFETY: `encode_string` wrote `written` ASCII bytes.
956 unsafe {
957 buf.set_len(written);
958 }
959 Ok(ascii_to_string(buf))
960}
961
962/// Spare capacity only — len stays 0 until the caller writes and `set_len`s.
963fn reserve_vec(cap: usize) -> Result<Vec<u8>, Error> {
964 let mut v = Vec::new();
965 if cap != 0 {
966 v.try_reserve_exact(cap)
967 .map_err(|_| Error::MemoryAllocationError)?;
968 }
969 Ok(v)
970}
971
972/// The PHC alphabet and punctuation are ASCII.
973fn ascii_to_string(buf: Vec<u8>) -> String {
974 // SAFETY: every byte came from `b64_byte_to_char`, a decimal digit, or a
975 // `$` / `v` / `m` / `t` / `p` / `=` / `,` literal.
976 unsafe { String::from_utf8_unchecked(buf) }
977}
978
979// ---------------------------------------------------------------------------
980// decode_string
981// ---------------------------------------------------------------------------
982
983/// The fields a PHC string yields.
984#[derive(Debug, Clone, PartialEq, Eq)]
985pub struct Decoded {
986 /// The algorithm named by the string.
987 ///
988 /// [`decode_string`] requires the caller to pass this type and fails on a
989 /// mismatch. [`decode_phc`] reads it from the `$argon2*` prefix.
990 pub algorithm: Algorithm,
991 /// The version. `0x10` when the `$v=` field is absent.
992 pub version: Version,
993 /// `m_cost`, `t_cost`, `lanes`, `threads == lanes`, and
994 /// `output_len == hash.len()`.
995 pub params: Params,
996 /// The decoded salt.
997 pub salt: Vec<u8>,
998 /// The decoded tag.
999 pub hash: Vec<u8>,
1000 /// Associated data from a `data=` parameter, if the string had one.
1001 ///
1002 /// The C `decode_string` has no such field; [`decode_string`] always
1003 /// leaves this empty. [`decode_phc`] fills it for `@phc/format` /
1004 /// node-argon2 strings.
1005 pub ad: Vec<u8>,
1006}
1007
1008/// The remainder of `src` from `pos`, never panicking.
1009#[inline]
1010fn rest(src: &[u8], pos: usize) -> &[u8] {
1011 src.get(pos..).unwrap_or(&[])
1012}
1013
1014/// The `CC(prefix)` macro: consume `prefix` or fail.
1015fn expect(src: &[u8], pos: &mut usize, prefix: &[u8]) -> Result<(), Error> {
1016 if rest(src, *pos).starts_with(prefix) {
1017 *pos += prefix.len();
1018 Ok(())
1019 } else {
1020 Err(Error::DecodingFail)
1021 }
1022}
1023
1024/// The `CC_opt(prefix, code)` macro: consume `prefix` if it is there.
1025fn expect_opt(src: &[u8], pos: &mut usize, prefix: &[u8]) -> bool {
1026 if rest(src, *pos).starts_with(prefix) {
1027 *pos += prefix.len();
1028 true
1029 } else {
1030 false
1031 }
1032}
1033
1034/// The `DECIMAL_U32(x)` macro.
1035fn decimal_u32(src: &[u8], pos: &mut usize) -> Result<u32, Error> {
1036 let (value, consumed) = decode_decimal(rest(src, *pos)).ok_or(Error::DecodingFail)?;
1037 if value > u32::MAX as u64 {
1038 return Err(Error::DecodingFail);
1039 }
1040 *pos += consumed;
1041 Ok(value as u32)
1042}
1043
1044/// The `BIN(buf, max_len, len)` macro.
1045///
1046/// The C sizes the destination at `strlen(encoded)` (see `argon2_verify`), so
1047/// the "output buffer too small" branch of `from_base64` is unreachable there.
1048/// The bound used here — three bytes out per four characters in — is likewise
1049/// never exceeded, so the two agree.
1050fn decode_bin(src: &[u8], pos: &mut usize) -> Result<Vec<u8>, Error> {
1051 let tail = rest(src, *pos);
1052 // `n` base64 characters decode to `floor(3n / 4)` bytes; `n/4*3 + 3` is an
1053 // upper bound for every `n`, and cannot overflow for any real slice.
1054 let max_len = tail.len() / 4 * 3 + 3;
1055
1056 let mut buf = reserve_vec(max_len)?;
1057 let (written, consumed) = from_base64_raw(buf.as_mut_ptr(), max_len, tail)?;
1058 // `bin_len > UINT32_MAX` is a decoding failure in the C.
1059 if written > u32::MAX as usize {
1060 return Err(Error::DecodingFail);
1061 }
1062 // SAFETY: `from_base64_raw` wrote `written` bytes and `written <= max_len`.
1063 unsafe {
1064 buf.set_len(written);
1065 }
1066 *pos += consumed;
1067 Ok(buf)
1068}
1069
1070/// `decode_string(ctx, str, type)`.
1071///
1072/// # Errors
1073///
1074/// [`Error::DecodingFail`] for a malformed string, or whatever
1075/// [`crate::params::validate_inputs`] returns — the C runs the full validation
1076/// before accepting the string, so a well-formed string with a zero-length salt
1077/// yields [`Error::SaltTooShort`] and not [`Error::DecodingFail`].
1078///
1079/// See the module documentation for the known divergences from the C
1080/// (unrepresentable versions, embedded NULs, and raw/non-ASCII input).
1081pub fn decode_string(encoded: &str, algorithm: Algorithm) -> Result<Decoded, Error> {
1082 let src = encoded.as_bytes();
1083 let mut pos = 0usize;
1084
1085 // argon2.c:268-271
1086 // encoded_len = strlen(encoded);
1087 // if (encoded_len > UINT32_MAX) return ARGON2_DECODING_FAIL;
1088 //
1089 // The C puts this in `argon2_verify`, one level up, and computes
1090 // `max_field_len` from it. Here it lives in the decoder because all four
1091 // verify entry points funnel through this function, so one check covers
1092 // them and cannot drift; through the public API the behaviour is identical.
1093 // Reachable only where `usize` is wider than `u32`, from a `&str` at least
1094 // 4 GiB long.
1095 if src.len() > u32::MAX as usize {
1096 return Err(Error::DecodingFail);
1097 }
1098
1099 // CC("$"); CC(type_string);
1100 //
1101 // No `ARGON2_INCORRECT_TYPE` branch: `argon2_type2string` only returns NULL
1102 // for a type outside the enum, which `Algorithm` cannot represent.
1103 expect(src, &mut pos, b"$")?;
1104 expect(src, &mut pos, algorithm.as_str().as_bytes())?;
1105
1106 // ctx->version = ARGON2_VERSION_10; CC_opt("$v=", DECIMAL_U32(version));
1107 let mut version_value = Version::V0x10.as_u32();
1108 if expect_opt(src, &mut pos, b"$v=") {
1109 version_value = decimal_u32(src, &mut pos)?;
1110 }
1111
1112 expect(src, &mut pos, b"$m=")?;
1113 let m_cost = decimal_u32(src, &mut pos)?;
1114 expect(src, &mut pos, b",t=")?;
1115 let t_cost = decimal_u32(src, &mut pos)?;
1116 expect(src, &mut pos, b",p=")?;
1117 let lanes = decimal_u32(src, &mut pos)?;
1118 // `ctx->threads = ctx->lanes;`
1119 let threads = lanes;
1120
1121 expect(src, &mut pos, b"$")?;
1122 let salt = decode_bin(src, &mut pos)?;
1123 expect(src, &mut pos, b"$")?;
1124 let hash = decode_bin(src, &mut pos)?;
1125
1126 // "On return, must have valid context": the full validate_inputs(), in the
1127 // C's order, before the trailing-character check.
1128 validate_inputs(
1129 hash.len(),
1130 0,
1131 salt.len(),
1132 0,
1133 0,
1134 m_cost,
1135 t_cost,
1136 lanes,
1137 threads,
1138 )?;
1139
1140 // "Can't have any additional characters".
1141 if pos != src.len() {
1142 return Err(Error::DecodingFail);
1143 }
1144
1145 // Last, so that every error code above still matches the C exactly.
1146 let version = Version::from_u32(version_value).ok_or(Error::DecodingFail)?;
1147
1148 // Attacker-chosen values from the string, through the same validation any
1149 // caller's parameters get. Both conversions into the typed units widen —
1150 // `m_cost` is a `u32`, and `hash.len()` is a `usize` — so neither can lose a
1151 // bit before `build()` range-checks it.
1152 //
1153 // `build()` cannot actually reject anything here: `validate_inputs` above
1154 // already accepted this `m_cost`, `t_cost`, `lanes`, `threads` and
1155 // `hash.len()`, and `build()` re-runs exactly that check with a salt length
1156 // that is valid by construction. Propagating rather than unwrapping keeps
1157 // that a fact about today's checks instead of an assumption baked into a
1158 // panic. Named setters also mean `m=` cannot land in the pass count: `m=`
1159 // was parsed into `m_cost` above and only `.memory()` receives it.
1160 let params = Params::builder()
1161 .memory(Memory::kib(u64::from(m_cost)))
1162 .passes(t_cost)
1163 .lanes(lanes)
1164 .threads(threads)
1165 .tag_len(TagLen::bytes(hash.len() as u64))
1166 .build()?;
1167
1168 Ok(Decoded {
1169 algorithm,
1170 version,
1171 params,
1172 salt,
1173 hash,
1174 ad: Vec::new(),
1175 })
1176}
1177
1178/// Decode a PHC string, detecting the algorithm from the `$argon2*` prefix.
1179///
1180/// Unlike [`decode_string`] (C `decode_string`, fixed `$m=,t=,p=`), this
1181/// accepts:
1182///
1183/// * any order of `m`, `t`, `p`
1184/// * an optional `data=` associated-data field (node-argon2 / `@phc/format`)
1185/// * unknown keys such as `keyid` (ignored)
1186///
1187/// `$v=` is still optional and still defaults to version 16.
1188///
1189/// The C-style encoder ([`encode_string`], [`crate::Argon2::hash_encoded`]) never
1190/// writes `data=`. This decoder exists so a verifier can still honour strings
1191/// other producers emit.
1192///
1193/// ```
1194/// use argon2_rust::{Algorithm, decode_phc};
1195///
1196/// // node-argon2 / `@phc/format` write `m,p,t`.
1197/// let d = decode_phc(
1198/// "$argon2id$v=19$m=64,p=1,t=1$c29tZXNhbHQ$cpx6VEQbwTVZvcpxNIxOVUWZ5xnAipUmAe1cg2GMG70",
1199/// )?;
1200/// assert_eq!(d.algorithm, Algorithm::Argon2id);
1201/// assert_eq!(d.params.memory_kib(), 64);
1202/// assert_eq!(d.params.passes(), 1);
1203/// assert_eq!(d.salt, b"somesalt");
1204/// assert!(d.ad.is_empty());
1205/// # Ok::<(), argon2_rust::Error>(())
1206/// ```
1207///
1208/// # Errors
1209///
1210/// [`Error::DecodingFail`] for a malformed string, or whatever
1211/// [`crate::params::validate_inputs`] returns.
1212pub fn decode_phc(encoded: &str) -> Result<Decoded, Error> {
1213 let src = encoded.as_bytes();
1214 if src.len() > u32::MAX as usize {
1215 return Err(Error::DecodingFail);
1216 }
1217 let mut pos = 0usize;
1218
1219 expect(src, &mut pos, b"$")?;
1220 let algorithm = if expect_opt(src, &mut pos, b"argon2id") {
1221 Algorithm::Argon2id
1222 } else if expect_opt(src, &mut pos, b"argon2i") {
1223 Algorithm::Argon2i
1224 } else if expect_opt(src, &mut pos, b"argon2d") {
1225 Algorithm::Argon2d
1226 } else {
1227 return Err(Error::DecodingFail);
1228 };
1229
1230 let mut version_value = Version::V0x10.as_u32();
1231 if expect_opt(src, &mut pos, b"$v=") {
1232 version_value = decimal_u32(src, &mut pos)?;
1233 }
1234
1235 expect(src, &mut pos, b"$")?;
1236 let param_end = find_byte(rest(src, pos), b'$').ok_or(Error::DecodingFail)?;
1237 let param_bytes = rest(src, pos).get(..param_end).unwrap_or(&[]);
1238 let (m_cost, t_cost, lanes, ad) = parse_phc_params(param_bytes)?;
1239 pos += param_end;
1240
1241 // Isolate each Base64 field so the SIMD decoder sees a clean alphabet
1242 // run. Feeding it `salt$hash` puts `$` in the first vector and the
1243 // prefix falls back to scalar.
1244 expect(src, &mut pos, b"$")?;
1245 let salt_end = find_byte(rest(src, pos), b'$').ok_or(Error::DecodingFail)?;
1246 let salt = decode_base64(&rest(src, pos)[..salt_end])?;
1247 pos += salt_end;
1248
1249 expect(src, &mut pos, b"$")?;
1250 // `decode_base64` requires the tail to be entirely alphabet, which is
1251 // the trailing-junk check `decode_string` does with `pos != src.len()`.
1252 let hash = decode_base64(rest(src, pos))?;
1253
1254 validate_inputs(
1255 hash.len(),
1256 0,
1257 salt.len(),
1258 0,
1259 ad.len(),
1260 m_cost,
1261 t_cost,
1262 lanes,
1263 lanes,
1264 )?;
1265
1266 let version = Version::from_u32(version_value).ok_or(Error::DecodingFail)?;
1267 let params = Params::builder()
1268 .memory(Memory::kib(u64::from(m_cost)))
1269 .passes(t_cost)
1270 .lanes(lanes)
1271 .threads(lanes)
1272 .tag_len(TagLen::bytes(hash.len() as u64))
1273 .build()?;
1274
1275 Ok(Decoded {
1276 algorithm,
1277 version,
1278 params,
1279 salt,
1280 hash,
1281 ad,
1282 })
1283}
1284
1285fn parse_phc_params(src: &[u8]) -> Result<(u32, u32, u32, Vec<u8>), Error> {
1286 let mut memory = None;
1287 let mut passes = None;
1288 let mut lanes = None;
1289 let mut ad = Vec::new();
1290 let mut rest = src;
1291 if rest.is_empty() {
1292 return Err(Error::DecodingFail);
1293 }
1294 loop {
1295 let (field, next) = match find_byte(rest, b',') {
1296 Some(i) => (&rest[..i], &rest[i + 1..]),
1297 None => (rest, [].as_slice()),
1298 };
1299 let eq = find_byte(field, b'=').ok_or(Error::DecodingFail)?;
1300 let key = &field[..eq];
1301 let value = &field[eq + 1..];
1302 match key {
1303 b"m" => memory = Some(parse_param_u32(value)?),
1304 b"t" => passes = Some(parse_param_u32(value)?),
1305 b"p" => lanes = Some(parse_param_u32(value)?),
1306 b"data" => ad = decode_base64(value)?,
1307 _ => {}
1308 }
1309 if next.is_empty() {
1310 break;
1311 }
1312 rest = next;
1313 }
1314 match (memory, passes, lanes) {
1315 (Some(m), Some(t), Some(p)) => Ok((m, t, p, ad)),
1316 _ => Err(Error::DecodingFail),
1317 }
1318}
1319
1320fn parse_param_u32(src: &[u8]) -> Result<u32, Error> {
1321 let (value, consumed) = decode_decimal(src).ok_or(Error::DecodingFail)?;
1322 if consumed != src.len() || value > u64::from(u32::MAX) {
1323 return Err(Error::DecodingFail);
1324 }
1325 Ok(value as u32)
1326}
1327
1328/// First index of `needle`. Delegates to [`memchr`], which has its own
1329/// runtime SIMD cascade when this crate's `std` feature is on.
1330#[inline]
1331fn find_byte(haystack: &[u8], needle: u8) -> Option<usize> {
1332 memchr::memchr(needle, haystack)
1333}
1334
1335#[cfg(test)]
1336mod tests {
1337 use super::*;
1338 use alloc::vec;
1339
1340 // From `phc-winner-argon2/src/test.c`.
1341 const V13_ARGON2I: &str = "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ\
1342 $wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA";
1343 const V10_ARGON2I: &str = "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ\
1344 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1345 const V13_ARGON2ID: &str = "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ\
1346 $CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
1347
1348 /// `f6c4db4a...`, the raw tag of the v=0x10 Argon2i vector in test.c.
1349 const V10_TAG: [u8; 32] = [
1350 0xf6, 0xc4, 0xdb, 0x4a, 0x54, 0xe2, 0xa3, 0x70, 0x62, 0x7a, 0xff, 0x3d, 0xb6, 0x17, 0x6b,
1351 0x94, 0xa2, 0xa2, 0x09, 0xa6, 0x2c, 0x8e, 0x36, 0x15, 0x27, 0x11, 0x80, 0x2f, 0x7b, 0x30,
1352 0xc6, 0x94,
1353 ];
1354
1355 fn b64(src: &[u8]) -> Vec<u8> {
1356 let mut out = vec![0u8; b64_len_usize(src.len()) + 1];
1357 let n = to_base64(&mut out, src).expect("buffer sized by b64_len");
1358 out.truncate(n);
1359 out
1360 }
1361
1362 fn unb64(src: &[u8]) -> Result<Vec<u8>, Error> {
1363 let mut out = vec![0u8; src.len()];
1364 let (n, consumed) = from_base64(&mut out, src)?;
1365 assert_eq!(consumed, src.len(), "test inputs are pure base64");
1366 out.truncate(n);
1367 Ok(out)
1368 }
1369
1370 fn b64_with_backend(
1371 dst: &mut [u8],
1372 src: &[u8],
1373 backend: Base64Backend,
1374 ) -> Result<usize, Error> {
1375 assert!(backend.is_available());
1376 // SAFETY: the assertion establishes this test process can execute the
1377 // requested backend; slice bounds remain checked by the implementation.
1378 unsafe { to_base64_with_backend(dst, src, backend) }
1379 }
1380
1381 fn unb64_with_backend(
1382 dst: &mut [u8],
1383 src: &[u8],
1384 backend: Base64Backend,
1385 ) -> Result<(usize, usize), Error> {
1386 assert!(backend.is_available());
1387 // SAFETY: as in `b64_with_backend`, availability is proved immediately
1388 // above and both pointer/length pairs come from live slices.
1389 unsafe { from_base64_with_backend(dst, src, backend) }
1390 }
1391
1392 // -- lengths ------------------------------------------------------------
1393
1394 #[test]
1395 fn encode_base64_matches_known_and_round_trips() {
1396 assert_eq!(encode_base64(b"somesalt").unwrap(), "c29tZXNhbHQ");
1397 assert_eq!(decode_base64(b"c29tZXNhbHQ").unwrap(), b"somesalt");
1398 for n in [0usize, 1, 2, 8, 16, 24, 32, 48] {
1399 let src: Vec<u8> = (0..n).map(|i| i as u8).collect();
1400 let encoded = encode_base64(&src).unwrap();
1401 assert_eq!(decode_base64(encoded.as_bytes()).unwrap(), src, "n={n}");
1402 }
1403 }
1404
1405 #[test]
1406 fn decode_base64_rejects_junk_and_bad_length() {
1407 assert_eq!(decode_base64(b"A"), Err(Error::DecodingFail));
1408 assert_eq!(decode_base64(b"????"), Err(Error::DecodingFail));
1409 assert_eq!(decode_base64(b"c29tZXNhbHQ!"), Err(Error::DecodingFail));
1410 }
1411
1412 #[test]
1413 fn find_byte_matches_iterator_position() {
1414 for n in [0usize, 1, 15, 16, 17, 31, 32, 64, 80] {
1415 let mut src = vec![b'A'; n];
1416 assert_eq!(find_byte(&src, b'$'), None, "n={n} miss");
1417 if n == 0 {
1418 continue;
1419 }
1420 src[n / 2] = b'$';
1421 assert_eq!(find_byte(&src, b'$'), Some(n / 2), "n={n} mid");
1422 src[n / 2] = b'A';
1423 src[n - 1] = b'$';
1424 assert_eq!(find_byte(&src, b'$'), Some(n - 1), "n={n} last");
1425 src[0] = b'$';
1426 assert_eq!(find_byte(&src, b'$'), Some(0), "n={n} first");
1427 }
1428 }
1429
1430 #[test]
1431 fn b64_len_matches_c() {
1432 assert_eq!(b64_len(0), 0);
1433 assert_eq!(b64_len(1), 2);
1434 assert_eq!(b64_len(2), 3);
1435 assert_eq!(b64_len(3), 4);
1436 assert_eq!(b64_len(4), 6);
1437 // "somesalt" -> "c29tZXNhbHQ", "…" -> the 43-char tag.
1438 assert_eq!(b64_len(8), 11);
1439 assert_eq!(b64_len(32), 43);
1440 // Cross-check against the encoder for every small length.
1441 for len in 0u32..64 {
1442 let src = vec![0xABu8; len as usize];
1443 assert_eq!(b64(&src).len(), b64_len(len), "len {len}");
1444 }
1445 }
1446
1447 #[test]
1448 fn num_len_matches_c() {
1449 assert_eq!(num_len(0), 1);
1450 assert_eq!(num_len(9), 1);
1451 assert_eq!(num_len(10), 2);
1452 assert_eq!(num_len(19), 2);
1453 assert_eq!(num_len(65536), 5);
1454 assert_eq!(num_len(u32::MAX), 10);
1455 }
1456
1457 #[test]
1458 fn encoded_len_matches_the_c_vector() {
1459 // $argon2id$v=19$m=65536,t=2,p=1$<11>$<43> is 86 chars + NUL.
1460 let n = encoded_len(Algorithm::Argon2id, 2, 65536, 1, 8, 32);
1461 assert_eq!(n, V13_ARGON2ID.len() + 1);
1462 assert_eq!(n, 87);
1463 assert_eq!(
1464 encoded_len(Algorithm::Argon2i, 2, 65536, 1, 8, 32),
1465 V13_ARGON2I.len() + 1
1466 );
1467 }
1468
1469 // Pins the claim in `encoded_len`'s `# Argument order` section: the C's
1470 // `argon2_encodedlen` (`argon2.c:447`) takes `t_cost` before `m_cost`, and
1471 // this port keeps that order. `ParamsBuilder` has no argument order to
1472 // reverse — `.memory()` and `.passes()` are named — so this positional
1473 // signature is now the only place in the crate where the two costs can be
1474 // transposed at all.
1475 //
1476 // A caller who supplies the two costs the other way round gets no error
1477 // back, and the reason is arithmetic rather than luck. `t_cost` and `m_cost`
1478 // each reach the result through exactly one term, `num_len(t_cost)` and
1479 // `num_len(m_cost)`, and the two terms are added, so the sum does not depend
1480 // on which digit count came from which cost. Nothing else in the body reads
1481 // either value. The swap is therefore *always* harmless, not usually
1482 // harmless: the swapped call returns the same number, and that number is
1483 // also the correct one. Nothing passed to `encoded_len` reaches the emitted
1484 // string either -- `encode_string` writes the `m=` and `t=` fields out of
1485 // the `&Params` it is handed. A swapped call site is a cosmetic
1486 // inconsistency, not a latent bug.
1487 //
1488 // That makes the equality a property of this one formula and not a rule
1489 // about the crate. This test pins it at every digit-count boundary and will
1490 // fail here first if the length ever stops being a plain sum of the two
1491 // terms, at which point the doc gets fixed with it.
1492 #[test]
1493 fn encoded_len_is_symmetric_in_m_and_t() {
1494 // The pair the doc example uses, the C's own test vector, and the
1495 // extreme: `u32::MAX` is ten digits against one, the widest the two
1496 // terms can differ.
1497 assert_eq!(encoded_len(Algorithm::Argon2id, 3, 65536, 1, 16, 32), 98);
1498 assert_eq!(encoded_len(Algorithm::Argon2id, 65536, 3, 1, 16, 32), 98);
1499 assert_eq!(encoded_len(Algorithm::Argon2id, 2, 65536, 1, 8, 32), 87);
1500 assert_eq!(encoded_len(Algorithm::Argon2id, 65536, 2, 1, 8, 32), 87);
1501 assert_eq!(encoded_len(Algorithm::Argon2id, 1, u32::MAX, 1, 16, 32), 103);
1502 assert_eq!(encoded_len(Algorithm::Argon2id, u32::MAX, 1, 1, 16, 32), 103);
1503
1504 // Every place `num_len` changes answer, both sides of each step, over
1505 // all three algorithm strings, so the property is pinned rather than
1506 // sampled at a few lucky points.
1507 const BOUNDARIES: [u32; 22] = [
1508 0,
1509 1,
1510 9,
1511 10,
1512 99,
1513 100,
1514 999,
1515 1_000,
1516 9_999,
1517 10_000,
1518 99_999,
1519 100_000,
1520 999_999,
1521 1_000_000,
1522 9_999_999,
1523 10_000_000,
1524 99_999_999,
1525 100_000_000,
1526 999_999_999,
1527 1_000_000_000,
1528 65536,
1529 u32::MAX,
1530 ];
1531 for algorithm in [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id] {
1532 for t_cost in BOUNDARIES {
1533 for m_cost in BOUNDARIES {
1534 assert_eq!(
1535 encoded_len(algorithm, t_cost, m_cost, 1, 16, 32),
1536 encoded_len(algorithm, m_cost, t_cost, 1, 16, 32),
1537 "{algorithm:?} t_cost={t_cost} m_cost={m_cost}"
1538 );
1539 }
1540 }
1541 }
1542 }
1543
1544 // -- base64 -------------------------------------------------------------
1545
1546 #[test]
1547 fn b64_tables_are_the_standard_alphabet() {
1548 const ALPHABET: &[u8; 64] =
1549 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1550 for (value, &ch) in ALPHABET.iter().enumerate() {
1551 assert_eq!(b64_byte_to_char(value as u32), ch, "value {value}");
1552 assert_eq!(b64_char_to_byte(ch as u32), value as u32, "char {ch}");
1553 }
1554 }
1555
1556 #[test]
1557 fn b64_char_to_byte_rejects_everything_else() {
1558 // The 'A' quirk: a computed 0 is only valid for 'A'.
1559 assert_eq!(b64_char_to_byte(b'A' as u32), 0);
1560 for c in 0u32..256 {
1561 let is_b64 = (c as u8).is_ascii_alphanumeric() || c == b'+' as u32 || c == b'/' as u32;
1562 if !is_b64 {
1563 assert_eq!(b64_char_to_byte(c), 0xFF, "char {c} must be invalid");
1564 }
1565 }
1566 assert_eq!(b64_char_to_byte(b'=' as u32), 0xFF); // no padding
1567 assert_eq!(b64_char_to_byte(0), 0xFF); // NUL terminator
1568 assert_eq!(b64_char_to_byte(b'$' as u32), 0xFF); // field separator
1569
1570 // Bytes >= 0x80 are rejected here. The C's are not, wherever `char` is
1571 // signed — it returns 63 for all 128 of them. See the module docs; this
1572 // loop is the pin for the port's (stricter, portable) choice.
1573 for c in 0x80u32..256 {
1574 assert_eq!(b64_char_to_byte(c), 0xFF, "byte {c:#04x} must be invalid");
1575 }
1576 }
1577
1578 /// `b64_char_to_byte` over `0..=127`, dumped from the C reference.
1579 ///
1580 /// Produced by a harness that `#include`s `encoding.c` (the function is
1581 /// `static`) and prints `b64_char_to_byte(i)` for every `i`, linked against
1582 /// `phc-winner-argon2/libargon2.a`. Only the ASCII half is transcribed: the
1583 /// upper half is the signed-`char` bug documented at the top of this file,
1584 /// where the C returns 63 for all 128 values.
1585 #[rustfmt::skip]
1586 const C_CHAR_TO_BYTE_ASCII: [u32; 128] = [
1587 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
1588 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
1589 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
1590 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 255, 255, 255,
1591 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
1592 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
1593 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
1594 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255,
1595 ];
1596
1597 #[test]
1598 fn b64_char_to_byte_matches_the_c_dump() {
1599 for (c, &want) in C_CHAR_TO_BYTE_ASCII.iter().enumerate() {
1600 assert_eq!(b64_char_to_byte(c as u32), want, "char {c}");
1601 }
1602 }
1603
1604 /// `b64_byte_to_char` over `0..64`, dumped from the same C harness.
1605 #[rustfmt::skip]
1606 const C_BYTE_TO_CHAR: [u8; 64] = [
1607 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
1608 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102,
1609 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
1610 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47,
1611 ];
1612
1613 #[test]
1614 fn b64_byte_to_char_matches_the_c_dump() {
1615 for (x, &want) in C_BYTE_TO_CHAR.iter().enumerate() {
1616 assert_eq!(b64_byte_to_char(x as u32), want, "value {x}");
1617 }
1618 }
1619
1620 #[test]
1621 fn non_ascii_bytes_are_rejected_in_a_field() {
1622 // Measured: the C decodes both of these, identically, to the salt
1623 // ff ff 6d 65 73 61 6c 74, because 0xC3 and 0xA9 each read as '/'.
1624 // This port rejects the first and accepts the second.
1625 let utf8 = "$argon2i$v=19$m=65536,t=2,p=1$é9tZXNhbHQ\
1626 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1627 assert_eq!(
1628 decode_string(utf8, Algorithm::Argon2i),
1629 Err(Error::DecodingFail)
1630 );
1631 let slashes = "$argon2i$v=19$m=65536,t=2,p=1$//9tZXNhbHQ\
1632 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1633 let d = decode_string(slashes, Algorithm::Argon2i).unwrap();
1634 assert_eq!(d.salt, [0xff, 0xff, 0x6d, 0x65, 0x73, 0x61, 0x6c, 0x74]);
1635 }
1636
1637 #[test]
1638 fn base64_known_vectors() {
1639 assert_eq!(b64(b"somesalt"), b"c29tZXNhbHQ");
1640 assert_eq!(b64(b"diffsalt"), b"ZGlmZnNhbHQ");
1641 assert_eq!(
1642 b64(&V10_TAG),
1643 b"9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
1644 );
1645 assert_eq!(unb64(b"c29tZXNhbHQ").unwrap(), b"somesalt");
1646 assert_eq!(
1647 unb64(b"9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ").unwrap(),
1648 &V10_TAG
1649 );
1650 }
1651
1652 #[test]
1653 fn base64_round_trips_every_short_length() {
1654 for len in 0usize..96 {
1655 let src: Vec<u8> = (0..len)
1656 .map(|i| (i as u8).wrapping_mul(37) ^ 0x5A)
1657 .collect();
1658 let encoded = b64(&src);
1659 assert_eq!(unb64(&encoded).unwrap(), src, "len {len}");
1660 }
1661 }
1662
1663 /// Every executable SIMD backend against the scalar oracle, across the
1664 /// block boundaries and tails each implementation can take. This checks
1665 /// the primitive directly rather than relying on PHC vectors whose usual
1666 /// 16/32-byte fields exercise only two shapes.
1667 #[test]
1668 fn every_base64_backend_matches_scalar_across_lengths() {
1669 for len in 0usize..=512 {
1670 let src: Vec<u8> = (0..len)
1671 .map(|i| (i as u8).wrapping_mul(197) ^ (len as u8).wrapping_mul(11))
1672 .collect();
1673 let capacity = b64_len_usize(len) + 1;
1674 let mut expected = vec![0xa5; capacity];
1675 let expected_len =
1676 b64_with_backend(&mut expected, &src, Base64Backend::Scalar).unwrap();
1677
1678 for &backend in Base64Backend::ALL {
1679 if !backend.is_available()
1680 || (cfg!(miri) && backend != Base64Backend::Scalar)
1681 {
1682 continue;
1683 }
1684 let mut actual = vec![0xa5; capacity];
1685 let actual_len = b64_with_backend(&mut actual, &src, backend).unwrap();
1686 assert_eq!(actual_len, expected_len, "{backend} length {len}");
1687 assert_eq!(actual, expected, "{backend} bytes at length {len}");
1688
1689 let mut scalar_decoded = vec![0x5a; len];
1690 let scalar_result = unb64_with_backend(
1691 &mut scalar_decoded,
1692 &expected[..expected_len],
1693 Base64Backend::Scalar,
1694 );
1695 let mut simd_decoded = vec![0x5a; len];
1696 let simd_result = unb64_with_backend(
1697 &mut simd_decoded,
1698 &expected[..expected_len],
1699 backend,
1700 );
1701 assert_eq!(simd_result, scalar_result, "{backend} decode length {len}");
1702 assert_eq!(simd_decoded, scalar_decoded, "{backend} decode bytes {len}");
1703 assert_eq!(simd_decoded, src, "{backend} round trip {len}");
1704 }
1705 }
1706 }
1707
1708 /// An invalid byte in a vector must not make the SIMD prefix lose the C
1709 /// decoder's exact stopping position. The vector is retried by scalar, so
1710 /// both the return value and all bytes written before an error match.
1711 #[test]
1712 fn every_base64_backend_matches_scalar_on_invalid_bytes_and_short_outputs() {
1713 let raw: Vec<u8> = (0..96)
1714 .map(|i| (i as u8).wrapping_mul(37) ^ 0x5a)
1715 .collect();
1716 let encoded = b64(&raw);
1717
1718 for &backend in Base64Backend::ALL {
1719 if !backend.is_available() || (cfg!(miri) && backend != Base64Backend::Scalar) {
1720 continue;
1721 }
1722
1723 for pos in 0..encoded.len() {
1724 for invalid in [0, b'$', b'=', 0x80, 0xff] {
1725 let mut input = encoded.clone();
1726 input[pos] = invalid;
1727 let mut expected = vec![0xa5; raw.len()];
1728 let expected_result = unb64_with_backend(
1729 &mut expected,
1730 &input,
1731 Base64Backend::Scalar,
1732 );
1733 let mut actual = vec![0xa5; raw.len()];
1734 let actual_result = unb64_with_backend(&mut actual, &input, backend);
1735 assert_eq!(actual_result, expected_result, "{backend} pos {pos} byte {invalid:#x}");
1736 assert_eq!(actual, expected, "{backend} output at pos {pos} byte {invalid:#x}");
1737 }
1738 }
1739
1740 for dst_len in 0..raw.len() {
1741 let mut expected = vec![0xa5; dst_len];
1742 let expected_result = unb64_with_backend(
1743 &mut expected,
1744 &encoded,
1745 Base64Backend::Scalar,
1746 );
1747 let mut actual = vec![0xa5; dst_len];
1748 let actual_result = unb64_with_backend(&mut actual, &encoded, backend);
1749 assert_eq!(actual_result, expected_result, "{backend} dst length {dst_len}");
1750 assert_eq!(actual, expected, "{backend} dst bytes at length {dst_len}");
1751 }
1752 }
1753 }
1754
1755 #[test]
1756 fn to_base64_rejects_a_tight_buffer() {
1757 // The C requires dst_len > olen, i.e. room for the NUL as well.
1758 let mut exact = [0u8; 11];
1759 assert_eq!(to_base64(&mut exact, b"somesalt"), Err(Error::EncodingFail));
1760 let mut roomy = [0u8; 12];
1761 assert_eq!(to_base64(&mut roomy, b"somesalt"), Ok(11));
1762 // Even an empty input needs one byte for the terminator.
1763 assert_eq!(to_base64(&mut [], b""), Err(Error::EncodingFail));
1764 assert_eq!(to_base64(&mut [0u8; 1], b""), Ok(0));
1765 }
1766
1767 #[test]
1768 fn from_base64_stops_at_the_first_non_b64_char() {
1769 let mut out = [0u8; 16];
1770 let (written, consumed) = from_base64(&mut out, b"c29tZXNhbHQ$rest").unwrap();
1771 assert_eq!(written, 8);
1772 assert_eq!(consumed, 11);
1773 assert_eq!(&out[..8], b"somesalt");
1774
1775 // An empty run is fine and consumes nothing (this is how the C accepts
1776 // "$argon2i$m=…,p=1$$<tag>" and only later reports SaltTooShort).
1777 let (written, consumed) = from_base64(&mut out, b"$tag").unwrap();
1778 assert_eq!((written, consumed), (0, 0));
1779 }
1780
1781 #[test]
1782 fn from_base64_rejects_leftover_bits() {
1783 let mut out = [0u8; 16];
1784 // 5 chars -> 30 bits -> acc_len == 6 > 4.
1785 assert_eq!(
1786 from_base64(&mut out, b"AAAAA"),
1787 Err(Error::DecodingFail),
1788 "acc_len > 4"
1789 );
1790 // 1 char -> 6 bits -> acc_len == 6 > 4.
1791 assert_eq!(from_base64(&mut out, b"A"), Err(Error::DecodingFail));
1792 // 2 chars, 4 buffered bits, non-zero: 'B' == 1.
1793 assert_eq!(from_base64(&mut out, b"AB"), Err(Error::DecodingFail));
1794 // Same shape but the buffered bits are zero.
1795 assert_eq!(from_base64(&mut out, b"AA"), Ok((1, 2)));
1796 // 3 chars, 2 buffered bits, non-zero: 'B' == 000001.
1797 assert_eq!(from_base64(&mut out, b"AAB"), Err(Error::DecodingFail));
1798 assert_eq!(from_base64(&mut out, b"AAA"), Ok((2, 3)));
1799 }
1800
1801 #[test]
1802 fn from_base64_rejects_a_short_buffer() {
1803 let mut out = [0u8; 4];
1804 assert_eq!(
1805 from_base64(&mut out, b"c29tZXNhbHQ"),
1806 Err(Error::DecodingFail)
1807 );
1808 // The C's `if ((len++) >= *dst_len)` tests the pre-increment value, so
1809 // an exactly-sized buffer is fine and one byte less is not.
1810 let mut exact = [0u8; 8];
1811 assert_eq!(from_base64(&mut exact, b"c29tZXNhbHQ"), Ok((8, 11)));
1812 assert_eq!(&exact, b"somesalt");
1813 let mut tight = [0u8; 7];
1814 assert_eq!(
1815 from_base64(&mut tight, b"c29tZXNhbHQ"),
1816 Err(Error::DecodingFail)
1817 );
1818 }
1819
1820 // -- decode_decimal -----------------------------------------------------
1821
1822 #[test]
1823 fn decode_decimal_matches_c() {
1824 assert_eq!(decode_decimal(b"0"), Some((0, 1)));
1825 assert_eq!(decode_decimal(b"19"), Some((19, 2)));
1826 assert_eq!(decode_decimal(b"65536,t=2"), Some((65536, 5)));
1827 assert_eq!(decode_decimal(b"4294967295"), Some((4294967295, 10)));
1828 // No digits at all.
1829 assert_eq!(decode_decimal(b""), None);
1830 assert_eq!(decode_decimal(b"$"), None);
1831 assert_eq!(decode_decimal(b"x1"), None);
1832 // Non-minimal.
1833 assert_eq!(decode_decimal(b"01"), None);
1834 assert_eq!(decode_decimal(b"00"), None);
1835 assert_eq!(decode_decimal(b"0019"), None);
1836 // Overflow of `unsigned long`.
1837 assert_eq!(
1838 decode_decimal(b"18446744073709551615"),
1839 Some((u64::MAX, 20))
1840 );
1841 assert_eq!(decode_decimal(b"18446744073709551616"), None);
1842 assert_eq!(decode_decimal(b"99999999999999999999999"), None);
1843 }
1844
1845 #[test]
1846 fn decode_decimal_matches_the_c_dump() {
1847 // Every pair below was produced by running the C's `decode_decimal`
1848 // (it is `static`, so via a harness that #includes encoding.c) linked
1849 // against phc-winner-argon2/libargon2.a. `None` is the C's NULL.
1850 /// `(input, decode_decimal(input))`, the C's NULL being `None`.
1851 type Case = (&'static [u8], Option<(u64, usize)>);
1852
1853 let cases: &[Case] = &[
1854 (b"", None),
1855 (b"0", Some((0, 1))),
1856 (b"00", None),
1857 (b"000", None),
1858 (b"01", None),
1859 (b"0019", None),
1860 (b"1", Some((1, 1))),
1861 (b"9", Some((9, 1))),
1862 (b"19", Some((19, 2))),
1863 (b"10", Some((10, 2))),
1864 (b"007", None),
1865 (b"4294967295", Some((4294967295, 10))),
1866 (b"4294967296", Some((4294967296, 10))),
1867 (b"9223372036854775807", Some((9223372036854775807, 19))),
1868 (b"18446744073709551615", Some((18446744073709551615, 20))),
1869 (b"18446744073709551616", None),
1870 (b"18446744073709551620", None),
1871 (b"19999999999999999999", None),
1872 (b"20000000000000000000", None),
1873 (b"99999999999999999999", None),
1874 (b"1000000000000000000000000", None),
1875 (b"65536,t=2", Some((65536, 5))),
1876 (b"1,t=2", Some((1, 1))),
1877 (b"x", None),
1878 (b"1x", Some((1, 1))),
1879 (b" 1", None),
1880 (b"+1", None),
1881 (b"-1", None),
1882 (b"1 ", Some((1, 1))),
1883 // "0" is minimal on its own, so the 'x' just ends the run.
1884 (b"0x10", Some((0, 1))),
1885 (b"12$", Some((12, 2))),
1886 (b"0,", Some((0, 1))),
1887 (b"10$", Some((10, 2))),
1888 (b"$", None),
1889 (b"1234567890", Some((1234567890, 10))),
1890 ];
1891 for (input, want) in cases {
1892 assert_eq!(
1893 decode_decimal(input),
1894 *want,
1895 "input {:?}",
1896 core::str::from_utf8(input).unwrap_or("<non-utf8>")
1897 );
1898 }
1899 }
1900
1901 #[test]
1902 fn decimal_fields_are_u32_bounded() {
1903 // DECIMAL_U32 rejects anything above UINT32_MAX.
1904 let too_big = "$argon2i$v=19$m=4294967296,t=2,p=1$c29tZXNhbHQ\
1905 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1906 assert_eq!(
1907 decode_string(too_big, Algorithm::Argon2i),
1908 Err(Error::DecodingFail)
1909 );
1910 let leading_zero = "$argon2i$v=19$m=065536,t=2,p=1$c29tZXNhbHQ\
1911 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
1912 assert_eq!(
1913 decode_string(leading_zero, Algorithm::Argon2i),
1914 Err(Error::DecodingFail)
1915 );
1916 }
1917
1918 // -- encode -------------------------------------------------------------
1919
1920 #[test]
1921 fn encode_matches_the_official_vectors() {
1922 let params = Params::builder()
1923 .memory(Memory::kib(65536))
1924 .passes(2)
1925 .lanes(1)
1926 .tag_len(TagLen::bytes(32))
1927 .build()
1928 .unwrap();
1929 let tag = unb64(b"wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA").unwrap();
1930 let encoded = encode_string_alloc(
1931 Algorithm::Argon2i,
1932 Version::V0x13,
1933 ¶ms,
1934 b"somesalt",
1935 &tag,
1936 )
1937 .unwrap();
1938 assert_eq!(encoded, V13_ARGON2I);
1939
1940 let tag = unb64(b"CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc").unwrap();
1941 let encoded = encode_string_alloc(
1942 Algorithm::Argon2id,
1943 Version::V0x13,
1944 ¶ms,
1945 b"somesalt",
1946 &tag,
1947 )
1948 .unwrap();
1949 assert_eq!(encoded, V13_ARGON2ID);
1950
1951 // The C always emits "$v=", even for 0x10.
1952 let encoded = encode_string_alloc(
1953 Algorithm::Argon2i,
1954 Version::V0x10,
1955 ¶ms,
1956 b"somesalt",
1957 &V10_TAG,
1958 )
1959 .unwrap();
1960 assert_eq!(
1961 encoded,
1962 "$argon2i$v=16$m=65536,t=2,p=1$c29tZXNhbHQ\
1963 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
1964 );
1965 }
1966
1967 #[test]
1968 fn encode_needs_encoded_len_bytes_exactly() {
1969 let params = Params::builder()
1970 .memory(Memory::kib(65536))
1971 .passes(2)
1972 .lanes(1)
1973 .tag_len(TagLen::bytes(32))
1974 .build()
1975 .unwrap();
1976 let want = encoded_len(Algorithm::Argon2i, 2, 65536, 1, 8, 32);
1977 assert_eq!(want, V13_ARGON2I.len() + 1);
1978
1979 let mut buf = vec![0u8; want];
1980 let n = encode_string(
1981 &mut buf,
1982 Algorithm::Argon2i,
1983 Version::V0x13,
1984 ¶ms,
1985 b"somesalt",
1986 &V10_TAG,
1987 )
1988 .unwrap();
1989 assert_eq!(n, want - 1);
1990
1991 // One byte less is a failure, exactly as in the C.
1992 let mut buf = vec![0u8; want - 1];
1993 assert_eq!(
1994 encode_string(
1995 &mut buf,
1996 Algorithm::Argon2i,
1997 Version::V0x13,
1998 ¶ms,
1999 b"somesalt",
2000 &V10_TAG,
2001 ),
2002 Err(Error::EncodingFail)
2003 );
2004 }
2005
2006 #[test]
2007 fn encode_validates_first() {
2008 let params = Params::builder()
2009 .memory(Memory::kib(65536))
2010 .passes(2)
2011 .lanes(1)
2012 .tag_len(TagLen::bytes(32))
2013 .build()
2014 .unwrap();
2015 // Short salt: validate_inputs runs before anything is written.
2016 assert_eq!(
2017 encode_string_alloc(
2018 Algorithm::Argon2i,
2019 Version::V0x13,
2020 ¶ms,
2021 b"short",
2022 &V10_TAG
2023 ),
2024 Err(Error::SaltTooShort)
2025 );
2026 // Short tag.
2027 assert_eq!(
2028 encode_string_alloc(
2029 Algorithm::Argon2i,
2030 Version::V0x13,
2031 ¶ms,
2032 b"somesalt",
2033 &[0u8; 3]
2034 ),
2035 Err(Error::OutputTooShort)
2036 );
2037 }
2038
2039 // -- decode -------------------------------------------------------------
2040
2041 #[test]
2042 fn decode_the_v13_vector() {
2043 let d = decode_string(V13_ARGON2I, Algorithm::Argon2i).unwrap();
2044 assert_eq!(d.algorithm, Algorithm::Argon2i);
2045 assert_eq!(d.version, Version::V0x13);
2046 assert_eq!(d.params.memory_kib(), 65536);
2047 assert_eq!(d.params.passes(), 2);
2048 assert_eq!(d.params.lanes(), 1);
2049 assert_eq!(d.params.tag_len_bytes(), 32);
2050 assert_eq!(d.salt, b"somesalt");
2051 assert!(d.ad.is_empty());
2052 assert_eq!(
2053 d.hash,
2054 unb64(b"wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA").unwrap()
2055 );
2056 }
2057
2058 #[test]
2059 fn decode_defaults_the_version_to_0x10() {
2060 let d = decode_string(V10_ARGON2I, Algorithm::Argon2i).unwrap();
2061 assert_eq!(d.version, Version::V0x10);
2062 assert_eq!(d.salt, b"somesalt");
2063 assert_eq!(d.hash, &V10_TAG);
2064 }
2065
2066 #[test]
2067 fn decode_sets_threads_to_lanes() {
2068 let s = "$argon2id$v=19$m=65536,t=2,p=4$c29tZXNhbHQ\
2069 $CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
2070 let d = decode_string(s, Algorithm::Argon2id).unwrap();
2071 assert_eq!(d.params.lanes(), 4);
2072 assert_eq!(d.params.threads(), 4);
2073 assert!(d.ad.is_empty());
2074 }
2075
2076 #[test]
2077 fn decode_phc_accepts_c_strings_and_detects_the_algorithm() {
2078 let d = decode_phc(V13_ARGON2ID).unwrap();
2079 let c = decode_string(V13_ARGON2ID, Algorithm::Argon2id).unwrap();
2080 assert_eq!(d, c);
2081 assert!(d.ad.is_empty());
2082 assert_eq!(decode_phc(V13_ARGON2I).unwrap().algorithm, Algorithm::Argon2i);
2083 assert_eq!(decode_phc(V10_ARGON2I).unwrap().version, Version::V0x10);
2084 }
2085
2086 #[test]
2087 fn decode_phc_accepts_m_p_t_order_and_unknown_keys() {
2088 let reordered = "$argon2id$v=19$m=65536,p=1,t=2$c29tZXNhbHQ\
2089 $CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
2090 let d = decode_phc(reordered).unwrap();
2091 assert_eq!(d.params.memory_kib(), 65536);
2092 assert_eq!(d.params.passes(), 2);
2093 assert_eq!(d.params.lanes(), 1);
2094 assert_eq!(d.salt, b"somesalt");
2095 assert!(d.ad.is_empty());
2096
2097 let with_keyid = "$argon2id$v=19$m=65536,t=2,p=1,keyid=abc$c29tZXNhbHQ\
2098 $CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
2099 let k = decode_phc(with_keyid).unwrap();
2100 assert_eq!(k.hash, d.hash);
2101 assert!(k.ad.is_empty());
2102
2103 // C-strict decoder still requires `$m=,t=,p=`.
2104 assert_eq!(
2105 decode_string(reordered, Algorithm::Argon2id),
2106 Err(Error::DecodingFail)
2107 );
2108 }
2109
2110 #[test]
2111 fn decode_phc_reads_data_associated_data() {
2112 let encoded = "$argon2id$v=19$m=65536,t=2,p=1,data=c29tZXNhbHQ$c29tZXNhbHQ\
2113 $CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc";
2114 let d = decode_phc(encoded).unwrap();
2115 assert_eq!(d.ad, b"somesalt");
2116 assert_eq!(d.salt, b"somesalt");
2117 assert_eq!(decode_string(encoded, Algorithm::Argon2id), Err(Error::DecodingFail));
2118 }
2119
2120 #[test]
2121 fn decode_phc_rejects_missing_required_params() {
2122 assert_eq!(
2123 decode_phc("$argon2id$v=19$m=65536,t=2$c29tZXNhbHQ$AAAA"),
2124 Err(Error::DecodingFail)
2125 );
2126 assert_eq!(decode_phc("$argon2x$v=19$m=8,t=1,p=1$c29tZXNhbHQ$AAAA"), Err(Error::DecodingFail));
2127 assert_eq!(decode_phc("argon2id$v=19$m=8,t=1,p=1$c29tZXNhbHQ$AAAA"), Err(Error::DecodingFail));
2128 }
2129
2130 #[test]
2131 fn encode_decode_round_trip() {
2132 let salt = b"0123456789abcdef";
2133 let tag: Vec<u8> = (0u8..48).collect();
2134 for algorithm in Algorithm::ALL {
2135 for version in Version::ALL {
2136 for lanes in [1u32, 2, 255] {
2137 let params = Params::builder()
2138 .memory(Memory::kib(1 << 16))
2139 .passes(3)
2140 .lanes(lanes)
2141 .tag_len(TagLen::bytes(tag.len() as u64))
2142 .build()
2143 .unwrap();
2144 let encoded =
2145 encode_string_alloc(algorithm, version, ¶ms, salt, &tag).unwrap();
2146 let d = decode_string(&encoded, algorithm).unwrap();
2147 assert_eq!(d.algorithm, algorithm);
2148 assert_eq!(d.version, version);
2149 assert!(d.ad.is_empty());
2150 assert_eq!(d.params, params);
2151 assert_eq!(d.salt, salt);
2152 assert_eq!(d.hash, tag);
2153 // And re-encoding is byte-identical.
2154 assert_eq!(
2155 encode_string_alloc(d.algorithm, d.version, &d.params, &d.salt, &d.hash)
2156 .unwrap(),
2157 encoded
2158 );
2159 }
2160 }
2161 }
2162 }
2163
2164 // The four malformed strings from `src/test.c`, both version flavours.
2165
2166 #[test]
2167 fn decode_rejects_a_missing_dollar_before_the_salt() {
2168 // "…,p=1c29tZXNhbHQ$…": the '$' after p=1 is gone.
2169 let v10 = "$argon2i$m=65536,t=2,p=1c29tZXNhbHQ\
2170 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2171 assert_eq!(
2172 decode_string(v10, Algorithm::Argon2i),
2173 Err(Error::DecodingFail)
2174 );
2175 let v13 = "$argon2i$v=19$m=65536,t=2,p=1c29tZXNhbHQ\
2176 $wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA";
2177 assert_eq!(
2178 decode_string(v13, Algorithm::Argon2i),
2179 Err(Error::DecodingFail)
2180 );
2181 }
2182
2183 #[test]
2184 fn decode_rejects_a_missing_dollar_before_the_tag() {
2185 // The salt and tag run together into one 54-character base64 field,
2186 // which decodes cleanly (54 chars leave 4 zero bits); the failure comes
2187 // from the CC("$") that follows.
2188 let v10 = "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ\
2189 9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2190 assert_eq!(
2191 decode_string(v10, Algorithm::Argon2i),
2192 Err(Error::DecodingFail)
2193 );
2194 let v13 = "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ\
2195 wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA";
2196 assert_eq!(
2197 decode_string(v13, Algorithm::Argon2i),
2198 Err(Error::DecodingFail)
2199 );
2200 }
2201
2202 #[test]
2203 fn decode_reports_salt_too_short_not_decoding_fail() {
2204 // This is the distinction tests/vectors.rs relies on: the string parses,
2205 // and it is validate_inputs() that rejects it.
2206 let v10 = "$argon2i$m=65536,t=2,p=1$\
2207 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2208 assert_eq!(
2209 decode_string(v10, Algorithm::Argon2i),
2210 Err(Error::SaltTooShort)
2211 );
2212 let v13 = "$argon2i$v=19$m=65536,t=2,p=1$\
2213 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2214 assert_eq!(
2215 decode_string(v13, Algorithm::Argon2i),
2216 Err(Error::SaltTooShort)
2217 );
2218 // A 7-byte salt is also too short, and still not a DecodingFail.
2219 let short = "$argon2i$v=19$m=65536,t=2,p=1$c2hvcnRz\
2220 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2221 assert_eq!(
2222 decode_string(short, Algorithm::Argon2i),
2223 Err(Error::SaltTooShort)
2224 );
2225 }
2226
2227 #[test]
2228 fn decode_argon2i_is_a_prefix_of_argon2id() {
2229 // CC("argon2i") matches the first seven characters of "argon2id"; the
2230 // leftover 'd' then fails the CC("$m=") (or the CC_opt("$v=")).
2231 assert_eq!(
2232 decode_string(V13_ARGON2ID, Algorithm::Argon2i),
2233 Err(Error::DecodingFail)
2234 );
2235 let v10_id = "$argon2id$m=65536,t=2,p=1$c29tZXNhbHQ\
2236 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2237 assert_eq!(
2238 decode_string(v10_id, Algorithm::Argon2i),
2239 Err(Error::DecodingFail)
2240 );
2241 // And the other way round: "argon2i" is not "argon2id".
2242 assert_eq!(
2243 decode_string(V13_ARGON2I, Algorithm::Argon2id),
2244 Err(Error::DecodingFail)
2245 );
2246 assert_eq!(
2247 decode_string(V13_ARGON2I, Algorithm::Argon2d),
2248 Err(Error::DecodingFail)
2249 );
2250 // The correct type still works, of course.
2251 assert!(decode_string(V13_ARGON2ID, Algorithm::Argon2id).is_ok());
2252 }
2253
2254 #[test]
2255 fn decode_rejects_structural_damage() {
2256 let cases: &[&str] = &[
2257 "",
2258 "$",
2259 "argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2260 "$argon2i",
2261 "$argon2i$v=19",
2262 "$argon2i$v=19$m=65536,t=2,p=1",
2263 "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ",
2264 // 'x' is not a decimal digit.
2265 "$argon2i$v=19$m=x,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2266 // Fields out of order.
2267 "$argon2i$v=19$t=2,m=65536,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2268 // Trailing junk after the tag.
2269 "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ$",
2270 "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ ",
2271 // '=' padding is not part of the alphabet, so it ends the field.
2272 "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ=$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2273 ];
2274 for case in cases {
2275 assert_eq!(
2276 decode_string(case, Algorithm::Argon2i),
2277 Err(Error::DecodingFail),
2278 "expected DecodingFail for {case:?}"
2279 );
2280 }
2281 }
2282
2283 #[test]
2284 fn decode_surfaces_the_c_validation_codes() {
2285 // A 3-byte tag: outlen is checked first.
2286 assert_eq!(
2287 decode_string(
2288 "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$AAAA",
2289 Algorithm::Argon2i
2290 ),
2291 Err(Error::OutputTooShort)
2292 );
2293 // m_cost < ARGON2_MIN_MEMORY.
2294 assert_eq!(
2295 decode_string(
2296 "$argon2i$v=19$m=1,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2297 Algorithm::Argon2i
2298 ),
2299 Err(Error::MemoryTooLittle)
2300 );
2301 // m_cost < 8 * lanes.
2302 assert_eq!(
2303 decode_string(
2304 "$argon2i$v=19$m=16,t=2,p=4$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2305 Algorithm::Argon2i
2306 ),
2307 Err(Error::MemoryTooLittle)
2308 );
2309 // t_cost < ARGON2_MIN_TIME.
2310 assert_eq!(
2311 decode_string(
2312 "$argon2i$v=19$m=65536,t=0,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2313 Algorithm::Argon2i
2314 ),
2315 Err(Error::TimeTooSmall)
2316 );
2317 // lanes < ARGON2_MIN_LANES.
2318 assert_eq!(
2319 decode_string(
2320 "$argon2i$v=19$m=65536,t=2,p=0$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2321 Algorithm::Argon2i
2322 ),
2323 Err(Error::LanesTooFew)
2324 );
2325 // lanes > ARGON2_MAX_LANES (16777215).
2326 #[cfg(target_pointer_width = "64")]
2327 assert_eq!(
2328 decode_string(
2329 "$argon2i$v=19$m=4294967295,t=2,p=16777216$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2330 Algorithm::Argon2i
2331 ),
2332 Err(Error::LanesTooMany)
2333 );
2334 // On a 32-bit target ARGON2_MAX_MEMORY is 2 MiB (the C's own
2335 // pointer-width rule), so — exactly as the C on 32-bit — the memory
2336 // check fires before the lanes check gets a chance to.
2337 #[cfg(target_pointer_width = "32")]
2338 assert_eq!(
2339 decode_string(
2340 "$argon2i$v=19$m=4294967295,t=2,p=16777216$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ",
2341 Algorithm::Argon2i
2342 ),
2343 Err(Error::MemoryTooMuch)
2344 );
2345 }
2346
2347 #[test]
2348 fn validation_runs_before_the_trailing_character_check() {
2349 // Both wrong: the C returns SALT_TOO_SHORT because validate_inputs()
2350 // comes first.
2351 let s = "$argon2i$v=19$m=65536,t=2,p=1$\
2352 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ!!!";
2353 assert_eq!(
2354 decode_string(s, Algorithm::Argon2i),
2355 Err(Error::SaltTooShort)
2356 );
2357 }
2358
2359 #[test]
2360 fn decode_rejects_an_unrepresentable_version() {
2361 // Documented divergence: the C accepts this (validate_inputs never
2362 // looks at the version) and treats it as 0x13.
2363 let s = "$argon2i$v=99$m=65536,t=2,p=1$c29tZXNhbHQ\
2364 $9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2365 assert_eq!(
2366 decode_string(s, Algorithm::Argon2i),
2367 Err(Error::DecodingFail)
2368 );
2369 // …but an earlier error still wins, so the codes stay C-compatible.
2370 let s = "$argon2i$v=99$m=65536,t=2,p=1$$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ";
2371 assert_eq!(
2372 decode_string(s, Algorithm::Argon2i),
2373 Err(Error::SaltTooShort)
2374 );
2375 }
2376
2377 #[test]
2378 fn decode_accepts_a_long_salt_and_tag() {
2379 let salt: Vec<u8> = (0u8..=255).collect();
2380 let tag: Vec<u8> = (0u8..=200).rev().collect();
2381 let params = Params::builder()
2382 .memory(Memory::kib(1 << 16))
2383 .passes(1)
2384 .lanes(1)
2385 .tag_len(TagLen::bytes(tag.len() as u64))
2386 .build()
2387 .unwrap();
2388 let encoded =
2389 encode_string_alloc(Algorithm::Argon2d, Version::V0x13, ¶ms, &salt, &tag).unwrap();
2390 let d = decode_string(&encoded, Algorithm::Argon2d).unwrap();
2391 assert_eq!(d.salt, salt);
2392 assert_eq!(d.hash, tag);
2393 assert_eq!(d.params.tag_len_bytes(), tag.len());
2394 }
2395}