PBKDF: implement argon2id
This commit is contained in:
1 parent
1fcf38db41
commit
b30b5d0add
13 files changed
+1291
-148
No files matched your search
@@ -0,0 +1,317 @@
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user