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

165 lines
3.8 KiB
Rust

use crate::blake2b::Blake2b;
use super::Argon2Params;
use super::compress::{BLOCK_SIZE, BLOCK_WORDS, Block};
use super::kdf::{ARGON2_VERSION, ARGON2ID_TYPE};
/// Compute the Argon2 initial hash H_0.
///
/// For our password KDF, the optional secret and associated-data
/// inputs are both empty, but their zero lengths are still encoded
/// because they are part of the Argon2 parameter structure.
pub(super) fn initial_hash(
password: &[u8],
salt: &[u8],
params: Argon2Params,
memory_kib: u32,
output_len: usize,
) -> [u8; 64] {
let mut input = Vec::with_capacity(6 * 4 + 4 + password.len() + 4 + salt.len() + 4 + 4);
// p
append_u32(&mut input, params.parallelism);
// T
append_u32(&mut input, output_len as u32);
// m'
append_u32(&mut input, memory_kib);
// t
append_u32(&mut input, params.time_cost);
// version
append_u32(&mut input, ARGON2_VERSION);
// type = Argon2id
append_u32(&mut input, ARGON2ID_TYPE);
// |P| and P
append_u32(&mut input, password.len() as u32);
input.extend_from_slice(password);
// |S| and S
append_u32(&mut input, salt.len() as u32);
input.extend_from_slice(salt);
// |K| = 0
append_u32(&mut input, 0);
// |X| = 0
append_u32(&mut input, 0);
let digest = Blake2b::hash(&input);
let mut output = [0u8; 64];
output.copy_from_slice(&digest);
output
}
/// Compute one of the two initial memory blocks.
pub(super) fn initial_block(h0: &[u8; 64], index: usize, lane: usize) -> Block {
let mut input = Vec::with_capacity(72);
input.extend_from_slice(h0);
append_u32(&mut input, index as u32);
append_u32(&mut input, lane as u32);
hash_long_block(&input)
}
/// Argon2 variable-length hash H'.
///
/// This is the H' construction specified by Argon2.
/// It ultimately uses the project's BLAKE2b implementation.
pub(super) fn hash_long(input: &[u8], output_len: usize) -> Vec<u8> {
assert!(output_len >= 1);
let mut prefixed = Vec::with_capacity(4 + input.len());
append_u32(&mut prefixed, output_len as u32);
prefixed.extend_from_slice(input);
/*
* For T <= 64:
*
* H'(T || A) = BLAKE2b-T(T || A)
*/
if output_len <= 64 {
return Blake2b::hash_with_output_length(&prefixed, output_len)
.expect("output length <= 64");
}
/*
* For T > 64:
*
* r = ceil(T / 32) - 2
*
* V_1 = H^64(T || A)
* V_i = H^64(V_{i-1})
* final = H^(T - 32*r)(V_r)
*/
let r = output_len.div_ceil(32) - 2;
let mut output = Vec::with_capacity(output_len);
let mut v = Blake2b::hash(&prefixed);
output.extend_from_slice(&v[..32]);
for _ in 1..r {
v = Blake2b::hash(&v);
output.extend_from_slice(&v[..32]);
}
let final_len = output_len - 32 * r;
v = Blake2b::hash_with_output_length(&v, final_len).expect("final H' output length is valid");
output.extend_from_slice(&v);
debug_assert_eq!(output.len(), output_len);
output
}
/// Compute H'(input, 1024).
fn hash_long_block(input: &[u8]) -> Block {
let digest = hash_long(input, BLOCK_SIZE);
let mut words = [0u64; BLOCK_WORDS];
for (i, word) in words.iter_mut().enumerate() {
let start = i * 8;
*word = u64::from_le_bytes(
digest[start..start + 8]
.try_into()
.expect("Argon2 block is 1024 bytes"),
);
}
Block(words)
}
/// Serialize an Argon2 block in little-endian order.
pub(super) fn block_to_bytes(block: &Block) -> Vec<u8> {
let mut output = Vec::with_capacity(BLOCK_SIZE);
for word in block.0 {
output.extend_from_slice(&word.to_le_bytes());
}
output
}
/// Append a little-endian u32.
#[inline]
fn append_u32(output: &mut Vec<u8>, value: u32) {
output.extend_from_slice(&value.to_le_bytes());
}