pub fn encoded_len(
algorithm: Algorithm,
t_cost: u32,
m_cost: u32,
lanes: u32,
salt_len: u32,
hash_len: u32,
) -> usizeExpand description
argon2_encodedlen(...) from src/argon2.c.
strlen("$$v=$m=,t=,p=$$") + strlen(type) + numlen(t_cost) + numlen(m_cost)
+ numlen(parallelism) + b64len(saltlen) + b64len(hashlen)
+ numlen(ARGON2_VERSION_NUMBER) + 1The trailing + 1 is the C string’s NUL terminator. It is kept so the value
matches the C byte for byte; a Rust String is one byte shorter. It is
also exactly the buffer size encode_string wants, which reserves the
same byte (see there).
Note the C uses numlen(ARGON2_VERSION_NUMBER) and not the version actually
being encoded. Both 0x10 (16) and 0x13 (19) are two digits, so it makes
no difference; it is kept verbatim. (In the C it can differ, because
ctx->version is a raw uint32_t: with version = 0 the string is one
byte shorter than advertised, and a buffer of argon2_encodedlen() - 1
suffices. Measured on 1051 of 30000 fuzzed cases, all of them version = 0.
Version is a closed enum of two two-digit values, so the size is always
exact here — see encode_needs_encoded_len_bytes_exactly.)
§Argument order
t_cost comes before m_cost here, which is the opposite of the order a
PHC string carries them in:
$argon2id$v=19$m=65536,t=3,p=1$<salt>$<tag>
^^^^^^^^^^^^^^^ the string reads m, then t, then p
encoded_len(algorithm, t_cost, m_cost, lanes, salt_len, hash_len)
^^^^^^^^^^^^^^ this call reads t, then mSo a call transcribed field by field off a string is a call with the two
costs swapped. Here that is harmless, and provably so rather than by luck:
the result is a plain sum of num_len(t_cost) and num_len(m_cost), so it
does not depend on which digit count came from which cost — pinned at every
digit-count boundary by encoded_len_is_symmetric_in_m_and_t.
The transposition is not harmless anywhere that hashes, and the decoder
behind Argon2::verify_encoded is where it
would bite: m= must become the memory cost and t= the pass count, never
the other way round, or a string verifies against a tag its writer never
produced. Params itself is built through named setters —
ParamsBuilder::memory and
ParamsBuilder::passes — so there is
no order to get wrong on that side. This function is the positional one.
The order is the C’s, kept so a call can be transcribed position for
position: argon2_encodedlen(t_cost, m_cost, parallelism, saltlen, hashlen, type), declared at argon2.h:429 and defined at argon2.c:447. One
argument did move. type went from last to first and became algorithm.
The other five kept their order among themselves; parallelism is spelled
lanes here, the name Params uses for it.
algorithm comes first here and on the rest of the encode-and-construct
side; the verify family keeps the C’s trailing type and takes it last.
use argon2_rust::{Algorithm, Params, encoded_len, params::{Memory, TagLen}};
// The builder names each cost, so nothing here has an order to reverse.
let params = Params::builder()
.memory(Memory::kib(65536))
.passes(3)
.lanes(1)
.tag_len(TagLen::bytes(32))
.build()?;
assert_eq!((params.memory_kib(), params.passes()), (65536, 3));
// `encoded_len` is positional, and it takes t_cost first: the same two
// costs, the other way round from the `m=65536,t=3` the string will show.
let n = encoded_len(
Algorithm::Argon2id,
params.passes(),
params.memory_kib(),
params.lanes(),
16, // salt_len
32, // hash_len
);
assert_eq!(n, 98);§Against a string the crate really produced
The value is a C buffer size, so it counts the NUL terminator described
above and a Rust String is one byte shorter. That is the whole of the
relationship, and it is exact rather than an upper bound - see
encode_needs_encoded_len_bytes_exactly:
use argon2_rust::{
Algorithm, Argon2, Params, Version, encoded_len,
params::{Memory, TagLen},
};
let params = Params::builder()
.memory(Memory::kib(64))
.passes(1)
.lanes(1)
.tag_len(TagLen::bytes(32))
.build()?;
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let encoded = argon2.hash_encoded(b"password", b"somesalt")?;
// `salt_len` is the salt's own 8 bytes, not the 11 its base64 occupies.
// Note the `1, 64` against the string's `m=64,t=1`: t_cost comes first.
let n = encoded_len(Algorithm::Argon2id, 1, 64, 1, 8, 32);
assert_eq!(n, 84);
assert_eq!(encoded.len(), n - 1);
assert_eq!(
encoded,
"$argon2id$v=19$m=64,t=1,p=1$c29tZXNhbHQ$cpx6VEQbwTVZvcpxNIxOVUWZ5xnAipUmAe1cg2GMG70",
);