use purecrypto::hash::HashAlgorithm;
use crate::Result;
fn diffuse(alg: HashAlgorithm, buf: &mut [u8]) {
let dlen = alg.output_len();
let mut scratch = Vec::with_capacity(4 + dlen);
for (j, piece) in buf.chunks_mut(dlen).enumerate() {
scratch.clear();
scratch.extend_from_slice(&(j as u32).to_be_bytes());
scratch.extend_from_slice(piece);
let d = alg.digest(&scratch);
piece.copy_from_slice(&d.as_slice()[..piece.len()]);
}
}
fn xor_into(dst: &mut [u8], src: &[u8]) {
for (d, s) in dst.iter_mut().zip(src) {
*d ^= s;
}
}
pub fn merge(alg: HashAlgorithm, src: &[u8], block_size: usize, stripes: u32) -> Result<Vec<u8>> {
let expect = block_size
.checked_mul(stripes as usize)
.ok_or_else(|| crate::Error::InvalidImage("luks: AF material size overflows".into()))?;
if src.len() != expect {
return Err(crate::Error::InvalidImage(format!(
"luks: AF material is {} bytes, expected {block_size} × {stripes} = {expect}",
src.len()
)));
}
if stripes == 0 || block_size == 0 {
return Err(crate::Error::InvalidImage(
"luks: AF stripe count and key size must both be non-zero".into(),
));
}
let mut d = vec![0u8; block_size];
for i in 0..(stripes as usize - 1) {
xor_into(&mut d, &src[i * block_size..(i + 1) * block_size]);
diffuse(alg, &mut d);
}
xor_into(&mut d, &src[(stripes as usize - 1) * block_size..]);
Ok(d)
}
pub fn split(alg: HashAlgorithm, key: &[u8], stripes: u32, random: &[u8]) -> Result<Vec<u8>> {
if stripes == 0 || key.is_empty() {
return Err(crate::Error::InvalidArgument(
"luks: AF stripe count and key size must both be non-zero".into(),
));
}
let block_size = key.len();
let lead = block_size
.checked_mul(stripes as usize - 1)
.ok_or_else(|| crate::Error::InvalidArgument("luks: AF material size overflows".into()))?;
if random.len() != lead {
return Err(crate::Error::InvalidArgument(format!(
"luks: AF needs {lead} random bytes for {stripes} stripes, got {}",
random.len()
)));
}
let mut out = vec![0u8; lead + block_size];
out[..lead].copy_from_slice(random);
let mut d = vec![0u8; block_size];
for i in 0..(stripes as usize - 1) {
xor_into(&mut d, &out[i * block_size..(i + 1) * block_size]);
diffuse(alg, &mut d);
}
let last = &mut out[lead..];
last.copy_from_slice(key);
xor_into(last, &d);
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn pseudo_random(len: usize, seed: u8) -> Vec<u8> {
(0..len)
.map(|i| (i as u8).wrapping_mul(31).wrapping_add(seed))
.collect()
}
#[test]
fn split_then_merge_recovers_the_key() {
for hash in ["sha1", "sha256", "sha512", "ripemd160", "whirlpool"] {
let alg = super::super::hash::parse(hash).unwrap();
for (key_len, stripes) in [(32usize, 4000u32), (64, 4000), (16, 1), (64, 2), (20, 7)] {
let key: Vec<u8> = (0..key_len).map(|i| (i as u8) ^ 0x5a).collect();
let random = pseudo_random(key_len * (stripes as usize - 1), 3);
let split = split(alg, &key, stripes, &random).unwrap();
assert_eq!(split.len(), key_len * stripes as usize);
let merged = merge(alg, &split, key_len, stripes).unwrap();
assert_eq!(merged, key, "{hash} {key_len}×{stripes}");
}
}
}
#[test]
fn single_stripe_is_the_key_verbatim() {
let alg = super::super::hash::parse("sha256").unwrap();
let key = vec![0xabu8; 32];
let split = split(alg, &key, 1, &[]).unwrap();
assert_eq!(split, key);
}
#[test]
fn corrupting_one_stripe_destroys_the_key() {
let alg = super::super::hash::parse("sha256").unwrap();
let key = vec![7u8; 32];
let random = pseudo_random(32 * 9, 11);
let split = split(alg, &key, 10, &random).unwrap();
for stripe in 0..10usize {
let mut damaged = split.clone();
damaged[stripe * 32] ^= 1;
let merged = merge(alg, &damaged, 32, 10).unwrap();
assert_ne!(
merged, key,
"flipping a bit in stripe {stripe} was harmless"
);
}
assert_eq!(merge(alg, &split, 32, 10).unwrap(), key);
}
#[test]
fn rejects_mis_sized_material() {
let alg = super::super::hash::parse("sha256").unwrap();
assert!(merge(alg, &[0u8; 31], 32, 1).is_err());
assert!(split(alg, &[0u8; 32], 4, &[0u8; 10]).is_err());
}
#[test]
fn diffuse_is_position_dependent() {
let alg = super::super::hash::parse("sha256").unwrap();
let mut buf = vec![0u8; 64];
diffuse(alg, &mut buf);
assert_ne!(&buf[..32], &buf[32..]);
}
}