use zeroize::Zeroize;
use crate::blake2b;
use crate::error::Error;
use crate::utils::{load_u64_le, rotr64};
pub(crate) const ARGON2_VERSION_NUMBER: u32 = 0x13;
const ARGON2_BLOCK_SIZE: usize = 1024;
const ARGON2_QWORDS_IN_BLOCK: usize = ARGON2_BLOCK_SIZE / 8;
const ARGON2_ADDRESSES_IN_BLOCK: u32 = 128;
const ARGON2_PREHASH_DIGEST_LENGTH: usize = 64;
const ARGON2_PREHASH_SEED_LENGTH: usize = 72;
const ARGON2_MIN_LANES: u32 = 1;
const ARGON2_MAX_LANES: u32 = 0xFFFFFF;
const ARGON2_SYNC_POINTS: u32 = 4;
const ARGON2_MIN_OUTLEN: usize = 16;
const ARGON2_MAX_OUTLEN: usize = 0xFFFFFFFF;
const ARGON2_MIN_MEMORY: u32 = 2 * ARGON2_SYNC_POINTS; const fn min(a: u64, b: u64) -> u64 {
[a, b][(a > b) as usize]
}
const ARGON2_MAX_MEMORY_BITS: u32 =
min(32, std::mem::size_of::<usize>() as u64 * 8 - 10 - 1) as u32;
const ARGON2_MAX_MEMORY: u32 = min(0xFFFFFFFF, 1u64 << ARGON2_MAX_MEMORY_BITS) as u32;
const ARGON2_MIN_TIME: u32 = 1;
const ARGON2_MAX_TIME: u32 = 0xFFFFFFFF;
const ARGON2_MIN_PWD_LENGTH: usize = 0;
const ARGON2_MAX_PWD_LENGTH: usize = 0xFFFFFFFF;
const ARGON2_MIN_AD_LENGTH: usize = 0;
const ARGON2_MAX_AD_LENGTH: usize = 0xFFFFFFFF;
const ARGON2_MIN_SALT_LENGTH: usize = 8;
const ARGON2_MAX_SALT_LENGTH: usize = 0xFFFFFFFF;
const ARGON2_MIN_SECRET: usize = 0;
const ARGON2_MAX_SECRET: usize = 0xFFFFFFFF;
#[derive(Clone, Copy, PartialEq)]
pub(crate) enum Argon2Type {
Argon2i = 1,
Argon2id = 2,
}
#[derive(Clone)]
struct Block {
v: [u64; ARGON2_QWORDS_IN_BLOCK],
}
#[cfg_attr(feature = "nightly", allow(clippy::derivable_impls))]
impl Default for Block {
fn default() -> Self {
Self {
v: [0u64; ARGON2_QWORDS_IN_BLOCK],
}
}
}
#[derive(Default)]
struct BlockRegion {
memory: Vec<Block>,
}
struct Argon2Instance {
region: BlockRegion,
pseudo_rands: Vec<u64>,
passes: u32, memory_blocks: u32, segment_length: u32,
lane_length: u32,
lanes: u32,
type_: Argon2Type,
}
impl Default for Argon2Instance {
fn default() -> Self {
Self {
region: Default::default(),
pseudo_rands: vec![],
passes: 0,
memory_blocks: 0,
segment_length: 0,
lane_length: 0,
lanes: 0,
type_: Argon2Type::Argon2id,
}
}
}
impl Argon2Instance {
fn new(
memory_blocks: u32,
segment_length: u32,
type_: Argon2Type,
t_cost: u32,
lanes: u32,
) -> Self {
let mut ret = Self {
memory_blocks,
segment_length,
type_,
lane_length: segment_length * ARGON2_SYNC_POINTS,
passes: t_cost,
lanes,
..Default::default()
};
ret.initialize();
ret
}
fn initialize(&mut self) {
self.pseudo_rands.resize(self.segment_length as usize, 0);
self.region
.memory
.resize(self.memory_blocks as usize, Default::default());
}
}
impl<'a> Argon2Context<'a> {
#[allow(clippy::too_many_arguments)]
fn new(
output: &'a mut [u8],
password: &'a [u8],
salt: &'a [u8],
secret: Option<&'a [u8]>,
ad: Option<&'a [u8]>,
t_cost: u32,
m_cost: u32,
parallelism: u32,
) -> Result<Self, Error> {
validate!(ARGON2_MIN_OUTLEN, ARGON2_MAX_OUTLEN, output.len(), "output");
validate!(
ARGON2_MIN_PWD_LENGTH,
ARGON2_MAX_PWD_LENGTH,
password.len(),
"password"
);
validate!(
ARGON2_MIN_SALT_LENGTH,
ARGON2_MAX_SALT_LENGTH,
salt.len(),
"salt"
);
if let Some(secret) = secret {
validate!(ARGON2_MIN_SECRET, ARGON2_MAX_SECRET, secret.len(), "secret");
}
if let Some(ad) = ad {
validate!(ARGON2_MIN_AD_LENGTH, ARGON2_MAX_AD_LENGTH, ad.len(), "ad");
}
validate!(
ARGON2_MIN_LANES,
ARGON2_MAX_LANES,
parallelism,
"parallelism"
);
validate!(ARGON2_MIN_MEMORY, ARGON2_MAX_MEMORY, m_cost, "m_cost");
validate!(ARGON2_MIN_TIME, ARGON2_MAX_TIME, t_cost, "t_cost");
Ok(Self {
output,
password,
salt,
secret,
ad,
t_cost,
m_cost,
lanes: parallelism,
})
}
}
struct Argon2Context<'a> {
output: &'a mut [u8],
password: &'a [u8],
salt: &'a [u8],
secret: Option<&'a [u8]>,
ad: Option<&'a [u8]>,
t_cost: u32, m_cost: u32, lanes: u32, }
#[allow(clippy::too_many_arguments)]
pub(crate) fn argon2_hash(
t_cost: u32,
m_cost: u32,
parallelism: u32,
password: &[u8],
salt: &[u8],
secret: Option<&[u8]>,
ad: Option<&[u8]>,
output: &mut [u8],
type_: Argon2Type,
) -> Result<(), Error> {
let memory_blocks = if m_cost < 2 * ARGON2_SYNC_POINTS * parallelism {
2 * ARGON2_SYNC_POINTS * parallelism
} else {
m_cost
};
let segment_length = memory_blocks / (parallelism * ARGON2_SYNC_POINTS);
let memory_blocks = segment_length * (parallelism * ARGON2_SYNC_POINTS);
let context = Argon2Context::new(
output,
password,
salt,
secret,
ad,
t_cost,
m_cost,
parallelism,
)?;
let mut instance =
Argon2Instance::new(memory_blocks, segment_length, type_, t_cost, parallelism);
let blockhash = argon2_initial_hash(&context, type_)?;
argon2_fill_first_blocks(blockhash, &mut instance)?;
for pass in 0..instance.passes {
argon2_fill_memory_blocks(&mut instance, pass);
}
argon2_finalize(context, instance)
}
fn argon2_finalize(context: Argon2Context, instance: Argon2Instance) -> Result<(), Error> {
let mut blockhash = Block::default();
copy_block(
&mut blockhash,
&instance.region.memory[instance.lane_length.wrapping_sub(1) as usize],
);
for l in 1..instance.lanes {
let last_block_in_lane = (l * instance.lane_length + (instance.lane_length - 1)) as usize;
xor_block(&mut blockhash, &instance.region.memory[last_block_in_lane]);
}
let mut blockhash_bytes = [0u8; ARGON2_BLOCK_SIZE];
store_block(&mut blockhash_bytes, &blockhash);
blake2b::longhash(context.output, &blockhash_bytes)
}
fn argon2_initial_hash(
context: &Argon2Context,
type_: Argon2Type,
) -> Result<[u8; ARGON2_PREHASH_SEED_LENGTH], Error> {
let mut blake2b = blake2b::State::init(ARGON2_PREHASH_DIGEST_LENGTH as u8, None, None, None)?;
blake2b.update(&context.lanes.to_le_bytes());
blake2b.update(&(context.output.len() as u32).to_le_bytes());
blake2b.update(&context.m_cost.to_le_bytes());
blake2b.update(&context.t_cost.to_le_bytes());
blake2b.update(&ARGON2_VERSION_NUMBER.to_le_bytes());
blake2b.update(&(type_ as u32).to_le_bytes());
blake2b.update(&(context.password.len() as u32).to_le_bytes());
if !context.password.is_empty() {
blake2b.update(context.password);
}
blake2b.update(&(context.salt.len() as u32).to_le_bytes());
if !context.salt.is_empty() {
blake2b.update(context.salt);
}
match context.secret {
Some(secret) => {
blake2b.update(&(secret.len() as u32).to_le_bytes());
if !secret.is_empty() {
blake2b.update(secret);
}
}
None => blake2b.update(&(0u32.to_le_bytes())), }
match context.ad {
Some(ad) => {
blake2b.update(&(ad.len() as u32).to_le_bytes());
if !ad.is_empty() {
blake2b.update(ad);
}
}
None => blake2b.update(&(0u32.to_le_bytes())), }
let mut blockhash = [0u8; ARGON2_PREHASH_SEED_LENGTH];
blake2b.finalize(&mut blockhash[..ARGON2_PREHASH_DIGEST_LENGTH])?;
Ok(blockhash)
}
#[derive(Default)]
struct Argon2Position {
pass: u32,
lane: u32,
slice: u8,
index: u32,
}
fn argon2_fill_memory_blocks(instance: &mut Argon2Instance, pass: u32) {
let mut position = Argon2Position {
pass,
..Default::default()
};
for s in 0..ARGON2_SYNC_POINTS {
position.slice = s as u8;
for l in 0..instance.lanes {
position.lane = l;
position.index = 0;
fill_segment(instance, &mut position);
}
}
}
fn fill_segment(instance: &mut Argon2Instance, position: &mut Argon2Position) {
let data_independent_addressing = !(instance.type_ == Argon2Type::Argon2id
&& (position.pass != 0 || position.slice as u32 >= (ARGON2_SYNC_POINTS / 2)));
if data_independent_addressing {
generate_addresses(instance, position);
}
let starting_index = if position.pass == 0 && position.slice == 0 {
2
} else {
0
};
let mut curr_offset = position.lane * instance.lane_length
+ position.slice as u32 * instance.segment_length
+ starting_index;
let mut prev_offset = if curr_offset % instance.lane_length == 0 {
curr_offset + instance.lane_length - 1
} else {
curr_offset - 1
};
for i in starting_index..instance.segment_length {
if curr_offset % instance.lane_length == 1 {
prev_offset = curr_offset - 1;
}
let pseudo_rand = if data_independent_addressing {
instance.pseudo_rands[i as usize]
} else {
instance.region.memory[prev_offset as usize].v[0]
};
let ref_lane = if position.pass == 0 && position.slice == 0 {
position.lane as u64
} else {
(pseudo_rand >> 32) % instance.lanes as u64
};
position.index = i;
let ref_index = index_alpha(
instance,
position,
(pseudo_rand & 0xFFFFFFFF) as u32,
ref_lane == position.lane as u64,
);
let prev_block = &instance.region.memory[prev_offset as usize];
let ref_block = &instance.region.memory
[(instance.lane_length as u64 * ref_lane + ref_index as u64) as usize];
let mut next_block = instance.region.memory[curr_offset as usize].clone();
fill_block(prev_block, ref_block, &mut next_block, position.pass != 0);
instance.region.memory[curr_offset as usize] = next_block;
curr_offset += 1;
prev_offset += 1;
}
}
fn index_alpha(
instance: &Argon2Instance,
position: &Argon2Position,
pseudo_rand: u32,
same_lane: bool,
) -> u32 {
let reference_area_size = if position.pass == 0 {
if position.slice == 0 {
position.index - 1 } else if same_lane {
position.slice as u32 * instance.segment_length + position.index - 1
} else if position.index == 0 {
position.slice as u32 * instance.segment_length - 1
} else {
position.slice as u32 * instance.segment_length
}
} else if same_lane {
instance.lane_length - instance.segment_length + position.index - 1
} else if position.index == 0 {
instance.lane_length - instance.segment_length - 1
} else {
instance.lane_length - instance.segment_length
};
let mut relative_position = pseudo_rand;
relative_position =
((relative_position as u64).wrapping_mul(relative_position as u64) >> 32) as u32;
relative_position = reference_area_size
- 1
- ((reference_area_size as u64).wrapping_mul(relative_position as u64) >> 32) as u32;
let start_position = if position.pass != 0 {
if position.slice as u32 == ARGON2_SYNC_POINTS - 1 {
0
} else {
(position.slice as u32 + 1) * instance.segment_length
}
} else {
0
};
(start_position + relative_position) % instance.lane_length }
fn generate_addresses(instance: &mut Argon2Instance, position: &Argon2Position) {
let mut input_block = Block::default();
let zero_block = Block::default();
let mut address_block = Block::default();
let mut tmp_block = Block::default();
input_block.v[0] = position.pass as u64;
input_block.v[1] = position.lane as u64;
input_block.v[2] = position.slice as u64;
input_block.v[3] = instance.memory_blocks as u64;
input_block.v[4] = instance.passes as u64;
input_block.v[5] = instance.type_ as u64;
for i in 0..instance.segment_length {
if i % ARGON2_ADDRESSES_IN_BLOCK == 0 {
input_block.v[6] += 1;
tmp_block.v.fill(0);
address_block.v.fill(0);
fill_block(&zero_block, &input_block, &mut tmp_block, true);
fill_block(&zero_block, &tmp_block, &mut address_block, true);
}
instance.pseudo_rands[i as usize] =
address_block.v[(i % ARGON2_ADDRESSES_IN_BLOCK) as usize];
}
}
#[inline]
fn fill_block(prev_block: &Block, ref_block: &Block, next_block: &mut Block, with_xor: bool) {
let mut block_r = Block::default();
let mut block_tmp = Block::default();
copy_block(&mut block_r, ref_block);
xor_block(&mut block_r, prev_block);
copy_block(&mut block_tmp, &block_r);
if with_xor {
xor_block(&mut block_tmp, next_block);
}
for i in 0..8 {
blake2_round_nomsg(
&mut block_r,
16 * i,
16 * i + 1,
16 * i + 2,
16 * i + 3,
16 * i + 4,
16 * i + 5,
16 * i + 6,
16 * i + 7,
16 * i + 8,
16 * i + 9,
16 * i + 10,
16 * i + 11,
16 * i + 12,
16 * i + 13,
16 * i + 14,
16 * i + 15,
);
}
for i in 0..8 {
blake2_round_nomsg(
&mut block_r,
2 * i,
2 * i + 1,
2 * i + 16,
2 * i + 17,
2 * i + 32,
2 * i + 33,
2 * i + 48,
2 * i + 49,
2 * i + 64,
2 * i + 65,
2 * i + 80,
2 * i + 81,
2 * i + 96,
2 * i + 97,
2 * i + 112,
2 * i + 113,
);
}
copy_block(next_block, &block_tmp);
xor_block(next_block, &block_r);
}
#[inline]
#[allow(clippy::too_many_arguments)]
fn blake2_round_nomsg(
block: &mut Block,
v0: usize,
v1: usize,
v2: usize,
v3: usize,
v4: usize,
v5: usize,
v6: usize,
v7: usize,
v8: usize,
v9: usize,
v10: usize,
v11: usize,
v12: usize,
v13: usize,
v14: usize,
v15: usize,
) {
let g = |block: &mut Block, a, b, c, d| {
block.v[a] = fblamka(block.v[a], block.v[b]);
block.v[d] = rotr64(block.v[d] ^ block.v[a], 32);
block.v[c] = fblamka(block.v[c], block.v[d]);
block.v[b] = rotr64(block.v[b] ^ block.v[c], 24);
block.v[a] = fblamka(block.v[a], block.v[b]);
block.v[d] = rotr64(block.v[d] ^ block.v[a], 16);
block.v[c] = fblamka(block.v[c], block.v[d]);
block.v[b] = rotr64(block.v[b] ^ block.v[c], 63);
};
g(block, v0, v4, v8, v12);
g(block, v1, v5, v9, v13);
g(block, v2, v6, v10, v14);
g(block, v3, v7, v11, v15);
g(block, v0, v5, v10, v15);
g(block, v1, v6, v11, v12);
g(block, v2, v7, v8, v13);
g(block, v3, v4, v9, v14);
}
#[inline]
fn fblamka(x: u64, y: u64) -> u64 {
let m = 0xFFFFFFFFu64;
let xy = (x & m) * (y & m);
x.wrapping_add(y).wrapping_add(2u64.wrapping_mul(xy))
}
fn copy_block(dst: &mut Block, src: &Block) {
dst.v.copy_from_slice(&src.v)
}
fn xor_block(dst: &mut Block, src: &Block) {
for i in 0..(dst.v.len()) {
dst.v[i] ^= src.v[i];
}
}
fn argon2_fill_first_blocks(
mut blockhash: [u8; ARGON2_PREHASH_SEED_LENGTH],
instance: &mut Argon2Instance,
) -> Result<(), Error> {
let mut blockhash_bytes = [0u8; ARGON2_BLOCK_SIZE];
for l in 0..instance.lanes {
blockhash[ARGON2_PREHASH_DIGEST_LENGTH..(ARGON2_PREHASH_DIGEST_LENGTH + 4)]
.copy_from_slice(&[0, 0, 0, 0]);
blockhash[(ARGON2_PREHASH_DIGEST_LENGTH + 4)..(ARGON2_PREHASH_DIGEST_LENGTH + 8)]
.copy_from_slice(&l.to_le_bytes());
blake2b::longhash(&mut blockhash_bytes, &blockhash)?;
load_block(
&mut instance.region.memory[(l * instance.lane_length) as usize],
&blockhash_bytes,
);
blockhash[ARGON2_PREHASH_DIGEST_LENGTH..(ARGON2_PREHASH_DIGEST_LENGTH + 4)]
.copy_from_slice(&[1, 0, 0, 0]);
blake2b::longhash(&mut blockhash_bytes, &blockhash)?;
load_block(
&mut instance.region.memory[(l * instance.lane_length + 1) as usize],
&blockhash_bytes,
);
}
blockhash_bytes.zeroize();
blockhash.zeroize();
Ok(())
}
fn load_block(block: &mut Block, input: &[u8]) {
for i in 0..ARGON2_QWORDS_IN_BLOCK {
let start = i * 8;
let end = start + 8;
block.v[i] = load_u64_le(&input[start..end]);
}
}
fn store_block(output: &mut [u8], block: &Block) {
for i in 0..ARGON2_QWORDS_IN_BLOCK {
let start = i * 8;
let end = start + 8;
output[start..end].copy_from_slice(&block.v[i].to_le_bytes());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vector_argon2i() {
let password = [1u8; 32];
let salt = [2u8; 16];
let secret = [3u8; 8];
let ad = [4u8; 12];
let mut hash = [0u8; 32];
super::argon2_hash(
3,
32,
4,
&password,
&salt,
Some(&secret),
Some(&ad),
&mut hash,
Argon2Type::Argon2i,
)
.expect("argon2_hash failed");
assert_eq!(
hash,
[
0xc8, 0x14, 0xd9, 0xd1, 0xdc, 0x7f, 0x37, 0xaa, 0x13, 0xf0, 0xd7, 0x7f, 0x24, 0x94,
0xbd, 0xa1, 0xc8, 0xde, 0x6b, 0x01, 0x6d, 0xd3, 0x88, 0xd2, 0x99, 0x52, 0xa4, 0xc4,
0x67, 0x2b, 0x6c, 0xe8,
]
);
}
extern "C" {
fn argon2_hash(
t_cost: u32,
m_cost: u32,
parallelism: u32,
pwd: *const u8,
pwdlen: usize,
salt: *const u8,
saltlen: usize,
hash: *mut u8,
hashlen: usize,
encoded: *mut u8,
encodedlen: usize,
type_: i32,
) -> i32;
}
#[test]
fn test_vector_argon2id() {
let password = [1u8; 32];
let salt = [2u8; 16];
let secret = [3u8; 8];
let ad = [4u8; 12];
let mut hash = [0u8; 32];
super::argon2_hash(
3,
32,
4,
&password,
&salt,
Some(&secret),
Some(&ad),
&mut hash,
Argon2Type::Argon2id,
)
.expect("argon2_hash failed");
assert_eq!(
hash,
[
0x0d, 0x64, 0x0d, 0xf5, 0x8d, 0x78, 0x76, 0x6c, 0x08, 0xc0, 0x37, 0xa3, 0x4a, 0x8b,
0x53, 0xc9, 0xd0, 0x1e, 0xf0, 0x45, 0x2d, 0x75, 0xb6, 0x5e, 0xb5, 0x25, 0x20, 0xe9,
0x6b, 0x01, 0xe6, 0x59,
]
);
}
#[test]
fn test_vector_argon2id_so() {
let password = [1u8; 32];
let salt = [2u8; 16];
let mut hash = [0u8; 32];
let mut so_hash = [0u8; 32];
super::argon2_hash(
3,
32,
4,
&password,
&salt,
None,
None,
&mut hash,
Argon2Type::Argon2id,
)
.expect("argon2_hash failed");
unsafe {
argon2_hash(
3,
32,
4,
password.as_ptr(),
password.len(),
salt.as_ptr(),
salt.len(),
so_hash.as_mut_ptr(),
so_hash.len(),
std::ptr::null_mut(),
0,
Argon2Type::Argon2id as i32,
);
}
assert_eq!(hash, so_hash);
assert_eq!(
hash,
[
3, 170, 185, 101, 193, 32, 1, 201, 215, 208, 210, 222, 51, 25, 44, 4, 148, 182,
132, 187, 20, 129, 150, 215, 60, 29, 241, 172, 175, 109, 12, 46
]
);
}
}