318 lines
7.9 KiB
Rust
318 lines
7.9 KiB
Rust
use super::compress::{Block, compress_block};
|
|
use super::hash::{block_to_bytes, hash_long, initial_block, initial_hash};
|
|
use super::kdf::ARGON2ID_TYPE;
|
|
use super::{Argon2Params, KdfError};
|
|
|
|
/// Core Argon2id function.
|
|
///
|
|
/// This implements Argon2id v1.3 using BLAKE2b for H and H'.
|
|
pub(super) fn argon2id(
|
|
password: &[u8],
|
|
salt: &[u8],
|
|
params: Argon2Params,
|
|
output_len: usize,
|
|
) -> Result<Vec<u8>, KdfError> {
|
|
if params.time_cost == 0 {
|
|
return Err(KdfError::InvalidTimeCost);
|
|
}
|
|
|
|
if params.parallelism == 0 {
|
|
return Err(KdfError::InvalidParallelism);
|
|
}
|
|
|
|
let parallelism = params.parallelism as usize;
|
|
let requested_memory = params.memory_kib as usize;
|
|
|
|
/*
|
|
* Argon2 requires at least 8 KiB per lane.
|
|
*/
|
|
if requested_memory < 8 * parallelism {
|
|
return Err(KdfError::InvalidMemoryCost);
|
|
}
|
|
|
|
/*
|
|
* The actual memory size is rounded down to a multiple of 4 * parallelism.
|
|
*/
|
|
let memory_kib = 4 * parallelism * (requested_memory / (4 * parallelism));
|
|
|
|
/*
|
|
* Number of blocks in each lane.
|
|
*/
|
|
let lane_length = memory_kib / parallelism;
|
|
|
|
/*
|
|
* Four segments per lane.
|
|
*/
|
|
let segment_length = lane_length / 4;
|
|
|
|
/*
|
|
* Initial hash H_0.
|
|
*/
|
|
let h0 = initial_hash(password, salt, params, memory_kib as u32, output_len);
|
|
|
|
/*
|
|
* Allocate the complete Argon2 memory matrix.
|
|
*
|
|
* Layout:
|
|
*
|
|
* lane 0: [block][block][block]...
|
|
* lane 1: [block][block][block]...
|
|
* ...
|
|
*/
|
|
let mut memory = vec![Block::ZERO; memory_kib];
|
|
|
|
/*
|
|
* Initialize the first two blocks of every lane:
|
|
*
|
|
* B[i][0] = H'(H_0 || LE32(0) || LE32(i))
|
|
* B[i][1] = H'(H_0 || LE32(1) || LE32(i))
|
|
*/
|
|
for lane in 0..parallelism {
|
|
memory[lane * lane_length] = initial_block(&h0, 0, lane);
|
|
|
|
memory[lane * lane_length + 1] = initial_block(&h0, 1, lane);
|
|
}
|
|
|
|
/*
|
|
* Fill the memory matrix.
|
|
*
|
|
* Argon2id:
|
|
*
|
|
* - pass 0, slices 0 and 1 use Argon2i style data independent addressing;
|
|
* - everything else uses Argon2d style addressing.
|
|
*/
|
|
for pass in 0..params.time_cost as usize {
|
|
for slice in 0..4 {
|
|
for lane in 0..parallelism {
|
|
process_segment(
|
|
&mut memory,
|
|
lane_length,
|
|
segment_length,
|
|
parallelism,
|
|
pass,
|
|
slice,
|
|
lane,
|
|
params.time_cost as usize,
|
|
memory_kib as u64,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Final block:
|
|
*
|
|
* C = B[0][q-1] XOR B[1][q-1] XOR ... XOR B[p-1][q-1]
|
|
*/
|
|
let mut final_block = Block::ZERO;
|
|
|
|
for lane in 0..parallelism {
|
|
let offset = lane * lane_length + lane_length - 1;
|
|
|
|
for (dst, src) in final_block.0.iter_mut().zip(memory[offset].0.iter()) {
|
|
*dst ^= *src;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Final Argon2 output:
|
|
*
|
|
* H'(C, output_len)
|
|
*/
|
|
let final_bytes = block_to_bytes(&final_block);
|
|
|
|
Ok(hash_long(&final_bytes, output_len))
|
|
}
|
|
|
|
/// Process one Argon2 segment.
|
|
fn process_segment(
|
|
memory: &mut [Block],
|
|
lane_length: usize,
|
|
segment_length: usize,
|
|
parallelism: usize,
|
|
pass: usize,
|
|
slice: usize,
|
|
lane: usize,
|
|
total_time: usize,
|
|
memory_kib: u64,
|
|
) {
|
|
/*
|
|
* Argon2id uses data-independent addressing only during the first two slices of the first pass.
|
|
*/
|
|
let data_independent = pass == 0 && slice < 2;
|
|
|
|
let mut address_block = Block::ZERO;
|
|
let mut input_block = Block::ZERO;
|
|
|
|
/*
|
|
* Position within this segment.
|
|
*/
|
|
let mut index = 0usize;
|
|
|
|
if data_independent {
|
|
input_block.0[0] = pass as u64;
|
|
input_block.0[1] = lane as u64;
|
|
input_block.0[2] = slice as u64;
|
|
input_block.0[3] = memory_kib;
|
|
input_block.0[4] = total_time as u64;
|
|
input_block.0[5] = ARGON2ID_TYPE as u64;
|
|
|
|
/*
|
|
* In the first segment, blocks 0 and 1 already exist, so generation starts at block 2.
|
|
*/
|
|
if pass == 0 && slice == 0 {
|
|
input_block.0[6] += 1;
|
|
|
|
address_block = compress_block(&input_block, &Block::ZERO, None);
|
|
|
|
address_block = compress_block(&address_block, &Block::ZERO, None);
|
|
|
|
index = 2;
|
|
}
|
|
}
|
|
|
|
while index < segment_length {
|
|
let current_offset = lane * lane_length + slice * segment_length + index;
|
|
|
|
/*
|
|
* Previous block in this lane.
|
|
*
|
|
* At the beginning of a lane, the previous block is the final block of the same lane.
|
|
*/
|
|
let previous_offset = if slice == 0 && index == 0 {
|
|
current_offset + lane_length - 1
|
|
} else {
|
|
current_offset - 1
|
|
};
|
|
|
|
/*
|
|
* Obtain J_1/J_2.
|
|
*/
|
|
let pseudo_random = if data_independent {
|
|
/*
|
|
* Generate a new address block every 128 positions.
|
|
*/
|
|
if index.is_multiple_of(128) {
|
|
input_block.0[6] += 1;
|
|
|
|
address_block = compress_block(&input_block, &Block::ZERO, None);
|
|
|
|
address_block = compress_block(&address_block, &Block::ZERO, None);
|
|
}
|
|
|
|
address_block.0[index % 128]
|
|
} else {
|
|
/*
|
|
* Argon2d addressing uses the first word of the previous block.
|
|
*/
|
|
memory[previous_offset].0[0]
|
|
};
|
|
|
|
/*
|
|
* Select the reference block.
|
|
*/
|
|
let reference_offset = index_alpha(
|
|
pseudo_random,
|
|
lane_length,
|
|
segment_length,
|
|
parallelism,
|
|
pass,
|
|
slice,
|
|
lane,
|
|
index,
|
|
);
|
|
|
|
/*
|
|
* Later passes XOR the newly generated block with the block that was already present at this position.
|
|
*/
|
|
let previous_block = memory[previous_offset];
|
|
|
|
let reference_block = memory[reference_offset];
|
|
|
|
let old_block = if pass == 0 {
|
|
None
|
|
} else {
|
|
Some(&memory[current_offset])
|
|
};
|
|
|
|
let new_block = compress_block(&previous_block, &reference_block, old_block);
|
|
|
|
memory[current_offset] = new_block;
|
|
|
|
index += 1;
|
|
}
|
|
}
|
|
|
|
/// Select the reference block for an Argon2 block.
|
|
///
|
|
/// This implements the Argon2 mapping from J_1/J_2 to the reference lane and reference position.
|
|
fn index_alpha(
|
|
random: u64,
|
|
lane_length: usize,
|
|
segment_length: usize,
|
|
parallelism: usize,
|
|
pass: usize,
|
|
slice: usize,
|
|
lane: usize,
|
|
index: usize,
|
|
) -> usize {
|
|
/*
|
|
* J_2 selects the reference lane.
|
|
*/
|
|
let mut reference_lane = ((random >> 32) as usize) % parallelism;
|
|
|
|
/*
|
|
* During the first slice of the first pass, references must remain in the same lane.
|
|
*/
|
|
if pass == 0 && slice == 0 {
|
|
reference_lane = lane;
|
|
}
|
|
|
|
/*
|
|
* Compute the size of the allowed reference area.
|
|
*/
|
|
let mut reference_area_size = 3 * segment_length;
|
|
|
|
let mut start_position = ((slice + 1) % 4) * segment_length;
|
|
|
|
if lane == reference_lane {
|
|
reference_area_size += index;
|
|
}
|
|
|
|
if pass == 0 {
|
|
reference_area_size = slice * segment_length;
|
|
|
|
start_position = 0;
|
|
|
|
if slice == 0 || lane == reference_lane {
|
|
reference_area_size += index;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The current block cannot reference itself.
|
|
*/
|
|
if index == 0 || lane == reference_lane {
|
|
reference_area_size -= 1;
|
|
}
|
|
|
|
/*
|
|
* Non-uniform mapping:
|
|
*
|
|
* x = J_1^2 / 2^32
|
|
* y = |W| * x / 2^32
|
|
*/
|
|
let mut x = (random as u32) as u64;
|
|
|
|
x = (x * x) >> 32;
|
|
|
|
let y = (x * reference_area_size as u64) >> 32;
|
|
|
|
/*
|
|
* Map into the selected lane.
|
|
*/
|
|
let relative_position = (start_position + reference_area_size - (y as usize + 1)) % lane_length;
|
|
|
|
reference_lane * lane_length + relative_position
|
|
}
|