use crate::error::Error;
pub const MIN_LANES: u32 = 1;
pub const MAX_LANES: u32 = 0x00FF_FFFF;
pub const MIN_THREADS: u32 = 1;
pub const MAX_THREADS: u32 = 0x00FF_FFFF;
pub const SYNC_POINTS: u32 = 4;
pub const MIN_OUTLEN: u32 = 4;
pub const MAX_OUTLEN: u32 = 0xFFFF_FFFF;
pub const MIN_MEMORY: u32 = 2 * SYNC_POINTS;
pub const MAX_MEMORY_BITS: u32 = {
let ptr_bits = (size_of::<*const u8>() * 8) as u32;
let bits = ptr_bits - 10 - 1;
if bits < 32 { bits } else { 32 }
};
pub const MAX_MEMORY: u32 = {
let candidate: u64 = 1u64 << MAX_MEMORY_BITS;
if candidate < 0xFFFF_FFFF {
candidate as u32
} else {
0xFFFF_FFFF
}
};
pub const MIN_TIME: u32 = 1;
pub const MAX_TIME: u32 = 0xFFFF_FFFF;
pub const MIN_PWD_LENGTH: u32 = 0;
pub const MAX_PWD_LENGTH: u32 = 0xFFFF_FFFF;
pub const MIN_AD_LENGTH: u32 = 0;
pub const MAX_AD_LENGTH: u32 = 0xFFFF_FFFF;
pub const MIN_SALT_LENGTH: u32 = 8;
pub const MAX_SALT_LENGTH: u32 = 0xFFFF_FFFF;
pub const MIN_SECRET: u32 = 0;
pub const MAX_SECRET: u32 = 0xFFFF_FFFF;
pub const BLOCK_SIZE: usize = 1024;
pub const QWORDS_IN_BLOCK: usize = BLOCK_SIZE / 8;
pub const OWORDS_IN_BLOCK: usize = BLOCK_SIZE / 16;
pub const HWORDS_IN_BLOCK: usize = BLOCK_SIZE / 32;
pub const BITS512_WORDS_IN_BLOCK: usize = BLOCK_SIZE / 64;
pub const ADDRESSES_IN_BLOCK: usize = 128;
pub const PREHASH_DIGEST_LENGTH: usize = 64;
pub const PREHASH_SEED_LENGTH: usize = 72;
#[cfg(test)]
const MAX_DECODED_LANES: u32 = 255;
#[cfg(test)]
const MIN_DECODED_SALT_LEN: u32 = 8;
#[cfg(test)]
const MIN_DECODED_OUT_LEN: u32 = 12;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(u32)]
pub enum Algorithm {
Argon2d = 0,
Argon2i = 1,
#[default]
Argon2id = 2,
}
impl Algorithm {
#[inline]
#[must_use]
pub const fn as_u32(self) -> u32 {
self as u32
}
#[inline]
#[must_use]
pub const fn from_u32(value: u32) -> Option<Algorithm> {
match value {
0 => Some(Algorithm::Argon2d),
1 => Some(Algorithm::Argon2i),
2 => Some(Algorithm::Argon2id),
_ => None,
}
}
#[inline]
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Algorithm::Argon2d => "argon2d",
Algorithm::Argon2i => "argon2i",
Algorithm::Argon2id => "argon2id",
}
}
#[inline]
#[must_use]
pub const fn as_str_uppercase(self) -> &'static str {
match self {
Algorithm::Argon2d => "Argon2d",
Algorithm::Argon2i => "Argon2i",
Algorithm::Argon2id => "Argon2id",
}
}
pub const ALL: [Algorithm; 3] = [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(u32)]
pub enum Version {
V0x10 = 0x10,
#[default]
V0x13 = 0x13,
}
impl Version {
pub const DEFAULT: Version = Version::V0x13;
pub const ALL: [Version; 2] = [Version::V0x10, Version::V0x13];
#[inline]
#[must_use]
pub const fn as_u32(self) -> u32 {
self as u32
}
#[inline]
#[must_use]
pub const fn from_u32(value: u32) -> Option<Version> {
match value {
0x10 => Some(Version::V0x10),
0x13 => Some(Version::V0x13),
_ => None,
}
}
}
#[allow(clippy::absurd_extreme_comparisons)]
#[allow(clippy::too_many_arguments)]
pub const fn validate_inputs(
out_len: usize,
pwd_len: usize,
salt_len: usize,
secret_len: usize,
ad_len: usize,
m_cost: u32,
t_cost: u32,
lanes: u32,
threads: u32,
) -> Result<(), Error> {
if out_len < MIN_OUTLEN as usize {
return Err(Error::OutputTooShort);
}
if out_len > MAX_OUTLEN as usize {
return Err(Error::OutputTooLong);
}
if pwd_len > MAX_PWD_LENGTH as usize {
return Err(Error::PwdTooLong);
}
if salt_len < MIN_SALT_LENGTH as usize {
return Err(Error::SaltTooShort);
}
if salt_len > MAX_SALT_LENGTH as usize {
return Err(Error::SaltTooLong);
}
if secret_len > MAX_SECRET as usize {
return Err(Error::SecretTooLong);
}
if ad_len > MAX_AD_LENGTH as usize {
return Err(Error::AdTooLong);
}
if m_cost < MIN_MEMORY {
return Err(Error::MemoryTooLittle);
}
if m_cost > MAX_MEMORY {
return Err(Error::MemoryTooMuch);
}
if m_cost < 8u32.wrapping_mul(lanes) {
return Err(Error::MemoryTooLittle);
}
if t_cost < MIN_TIME {
return Err(Error::TimeTooSmall);
}
if t_cost > MAX_TIME {
return Err(Error::TimeTooLarge);
}
if lanes < MIN_LANES {
return Err(Error::LanesTooFew);
}
if lanes > MAX_LANES {
return Err(Error::LanesTooMany);
}
if threads < MIN_THREADS {
return Err(Error::ThreadsTooFew);
}
if threads > MAX_THREADS {
return Err(Error::ThreadsTooMany);
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Params {
m_cost: u32,
t_cost: u32,
lanes: u32,
threads: u32,
output_len: u32,
}
impl Params {
pub const DEFAULT_M_COST: u32 = 19456;
pub const DEFAULT_T_COST: u32 = 2;
pub const DEFAULT_LANES: u32 = 1;
pub const DEFAULT_OUTPUT_LEN: usize = 32;
pub const fn new(
m_cost: u32,
t_cost: u32,
lanes: u32,
output_len: usize,
) -> Result<Params, Error> {
Params::new_with_threads(m_cost, t_cost, lanes, lanes, output_len)
}
pub const fn new_with_threads(
m_cost: u32,
t_cost: u32,
lanes: u32,
threads: u32,
output_len: usize,
) -> Result<Params, Error> {
match validate_inputs(
output_len,
0,
MIN_SALT_LENGTH as usize,
0,
0,
m_cost,
t_cost,
lanes,
threads,
) {
Ok(()) => {}
Err(e) => return Err(e),
}
Ok(Params {
m_cost,
t_cost,
lanes,
threads,
output_len: output_len as u32,
})
}
pub const fn validate_for(
&self,
pwd_len: usize,
salt_len: usize,
secret_len: usize,
ad_len: usize,
) -> Result<(), Error> {
validate_inputs(
self.output_len as usize,
pwd_len,
salt_len,
secret_len,
ad_len,
self.m_cost,
self.t_cost,
self.lanes,
self.threads,
)
}
#[inline]
#[must_use]
pub const fn m_cost(&self) -> u32 {
self.m_cost
}
#[inline]
#[must_use]
pub const fn t_cost(&self) -> u32 {
self.t_cost
}
#[inline]
#[must_use]
pub const fn lanes(&self) -> u32 {
self.lanes
}
#[inline]
#[must_use]
pub const fn threads(&self) -> u32 {
self.threads
}
#[inline]
#[must_use]
pub const fn output_len(&self) -> usize {
self.output_len as usize
}
#[inline]
#[must_use]
pub const fn effective_threads(&self) -> u32 {
if self.threads > self.lanes {
self.lanes
} else {
self.threads
}
}
#[inline]
#[must_use]
pub const fn memory_layout(&self) -> (u32, u32, u32) {
let lanes_x_sync = self.lanes * SYNC_POINTS;
let min_blocks = 2 * SYNC_POINTS * self.lanes;
let mut memory_blocks = self.m_cost;
if memory_blocks < min_blocks {
memory_blocks = min_blocks;
}
let segment_length = memory_blocks / lanes_x_sync;
memory_blocks = segment_length * lanes_x_sync;
let lane_length = segment_length * SYNC_POINTS;
(memory_blocks, segment_length, lane_length)
}
#[inline]
#[must_use]
pub const fn memory_blocks(&self) -> u32 {
self.memory_layout().0
}
#[inline]
#[must_use]
pub const fn segment_length(&self) -> u32 {
self.memory_layout().1
}
#[inline]
#[must_use]
pub const fn lane_length(&self) -> u32 {
self.memory_layout().2
}
}
impl Default for Params {
fn default() -> Params {
Params {
m_cost: Params::DEFAULT_M_COST,
t_cost: Params::DEFAULT_T_COST,
lanes: Params::DEFAULT_LANES,
threads: Params::DEFAULT_LANES,
output_len: Params::DEFAULT_OUTPUT_LEN as u32,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constants_match_the_c_preprocessor() {
assert_eq!(MIN_MEMORY, 8);
assert_eq!(MAX_LANES, 16_777_215);
assert_eq!(MAX_OUTLEN, 4_294_967_295);
if size_of::<*const u8>() == 8 {
assert_eq!(MAX_MEMORY_BITS, 32);
assert_eq!(MAX_MEMORY, 4_294_967_295);
}
assert_eq!(BLOCK_SIZE, 1024);
assert_eq!(QWORDS_IN_BLOCK, 128);
assert_eq!(OWORDS_IN_BLOCK, 64);
assert_eq!(HWORDS_IN_BLOCK, 32);
assert_eq!(BITS512_WORDS_IN_BLOCK, 16);
assert_eq!(PREHASH_SEED_LENGTH - PREHASH_DIGEST_LENGTH, 8);
}
#[test]
fn decoded_mirrors_are_not_decoder_bounds() {
const { assert!(MAX_DECODED_LANES < MAX_LANES) }
const { assert!(MIN_DECODED_OUT_LEN > MIN_OUTLEN) }
const { assert!(MIN_DECODED_SALT_LEN == MIN_SALT_LENGTH) }
}
#[test]
fn decoded_bound_docs_quote_strings_this_crate_still_produces() {
use crate::Argon2;
let params = Params::new(2400, 1, 300, 32).unwrap();
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let encoded = argon2.hash_encoded(b"password", b"somesalt").unwrap();
assert_eq!(
encoded,
"$argon2id$v=19$m=2400,t=1,p=300$c29tZXNhbHQ$tPLI8hre65Crk/uP5eIGCZzn3TQ7RzRoXIkGzt5jQoI"
);
assert_eq!(
Argon2::verify_encoded(&encoded, b"password", Algorithm::Argon2id),
Ok(())
);
let params = Params::new(2400, 1, 1, 8).unwrap();
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let encoded = argon2.hash_encoded(b"password", b"somesalt").unwrap();
assert_eq!(
encoded,
"$argon2id$v=19$m=2400,t=1,p=1$c29tZXNhbHQ$kQGQLZpZJIk"
);
assert_eq!(
Argon2::verify_encoded(&encoded, b"password", Algorithm::Argon2id),
Ok(())
);
}
#[test]
fn default_params_are_valid() {
let d = Params::default();
assert!(d.validate_for(0, 8, 0, 0).is_ok());
}
#[test]
fn validate_order_salt_before_m_cost() {
assert_eq!(
validate_inputs(32, 0, 0, 0, 0, 0, 1, 1, 1),
Err(Error::SaltTooShort)
);
}
#[test]
fn validate_order_out_len_first() {
assert_eq!(
validate_inputs(0, 0, 0, 0, 0, 0, 0, 0, 0),
Err(Error::OutputTooShort)
);
}
#[test]
fn m_cost_lanes_product_wraps_like_c() {
assert_eq!(
validate_inputs(32, 0, 8, 0, 0, 1 << 16, 1, 0xFFFF_FFFF, 1),
Err(Error::MemoryTooLittle)
);
assert_eq!(
validate_inputs(32, 0, 8, 0, 0, 16, 1, 4, 4),
Err(Error::MemoryTooLittle)
);
assert_eq!(validate_inputs(32, 0, 8, 0, 0, 32, 1, 4, 4), Ok(()));
}
#[test]
fn lanes_zero_is_lanes_too_few() {
assert_eq!(
validate_inputs(32, 0, 8, 0, 0, 8, 1, 0, 1),
Err(Error::LanesTooFew)
);
}
#[test]
fn memory_layout_matches_argon2_ctx() {
let p = Params::new(8, 1, 1, 32).unwrap();
assert_eq!(p.memory_layout(), (8, 2, 8));
let p = Params::new(1 << 16, 2, 1, 32).unwrap();
assert_eq!(p.memory_layout(), (65536, 16384, 65536));
let p = Params::new(1 << 16, 2, 4, 32).unwrap();
assert_eq!(p.memory_layout(), (65536, 4096, 16384));
let p = Params::new(100, 1, 3, 32).unwrap();
let (blocks, seg, lane) = p.memory_layout();
assert_eq!(seg, 100 / 12);
assert_eq!(blocks, seg * 12);
assert_eq!(lane, seg * 4);
}
#[test]
fn effective_threads_is_min() {
let p = Params::new_with_threads(1 << 16, 1, 2, 8, 32).unwrap();
assert_eq!(p.threads(), 8);
assert_eq!(p.effective_threads(), 2);
}
#[test]
fn algorithm_and_version_round_trip() {
for a in Algorithm::ALL {
assert_eq!(Algorithm::from_u32(a.as_u32()), Some(a));
}
assert_eq!(Algorithm::from_u32(3), None);
assert_eq!(Algorithm::Argon2id.as_str(), "argon2id");
assert_eq!(Algorithm::Argon2i.as_str_uppercase(), "Argon2i");
for v in Version::ALL {
assert_eq!(Version::from_u32(v.as_u32()), Some(v));
}
assert_eq!(Version::from_u32(0x11), None);
assert_eq!(Version::default(), Version::V0x13);
assert_eq!(Algorithm::default(), Algorithm::Argon2id);
}
}