Skip to main content

encoded_len

Function encoded_len 

Source
pub fn encoded_len(
    algorithm: Algorithm,
    t_cost: u32,
    m_cost: u32,
    lanes: u32,
    salt_len: u32,
    hash_len: u32,
) -> usize
Expand 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) + 1

The 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 reverse of Params::new:

Params::new(m_cost, t_cost, lanes, output_len)
encoded_len(algorithm, t_cost, m_cost, lanes, salt_len, hash_len)
                       ^^^^^^^^^^^^^^ reversed against Params::new

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::new takes m_cost first.
let params = Params::new(65536, 3, 1, 32).unwrap();
assert_eq!((params.m_cost(), params.t_cost()), (65536, 3));

// encoded_len takes t_cost first: the same two costs, the other way round.
let n = encoded_len(
    Algorithm::Argon2id,
    params.t_cost(),
    params.m_cost(),
    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};

let params = Params::new(64, 1, 1, 32)?;
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.
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",
);