Files
filecrypt-rs/src/kdf/argon2.rs
T
2026-10-01 18:28:40 +02:00

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
}