use sha1::Sha1;
use sha2::{Digest, Sha256, Sha512};
use crate::error::{LuksError, Result};
fn diffuse<D: Digest>(data: &[u8]) -> Vec<u8> {
let digest_size = <D as Digest>::output_size();
let mut out = vec![0u8; data.len()];
let mut i = 0usize;
let mut offset = 0usize;
while offset < data.len() {
let take = digest_size.min(data.len() - offset);
let mut hasher = D::new();
hasher.update((i as u32).to_be_bytes());
hasher.update(&data[offset..offset + take]);
let h = hasher.finalize();
out[offset..offset + take].copy_from_slice(&h[..take]);
offset += take;
i += 1;
}
out
}
fn xor_into(acc: &mut [u8], other: &[u8]) {
for (a, b) in acc.iter_mut().zip(other) {
*a ^= *b;
}
}
fn af_merge<D: Digest>(material: &[u8], block_size: usize, stripes: usize) -> Vec<u8> {
let mut acc = vec![0u8; block_size];
for i in 0..stripes.saturating_sub(1) {
let start = i * block_size;
if let Some(block) = material.get(start..start + block_size) {
xor_into(&mut acc, block);
acc = diffuse::<D>(&acc);
}
}
let last = (stripes.saturating_sub(1)) * block_size;
if let Some(block) = material.get(last..last + block_size) {
xor_into(&mut acc, block);
}
acc
}
pub fn merge(
hash_spec: &str,
material: &[u8],
block_size: usize,
stripes: usize,
) -> Result<Vec<u8>> {
match hash_spec {
"sha1" => Ok(af_merge::<Sha1>(material, block_size, stripes)),
"sha256" => Ok(af_merge::<Sha256>(material, block_size, stripes)),
"sha512" => Ok(af_merge::<Sha512>(material, block_size, stripes)),
other => Err(LuksError::Unsupported {
what: "hash",
value: other.to_string(),
}),
}
}
#[must_use]
pub fn material_len(block_size: usize, stripes: usize) -> usize {
let raw = block_size * stripes;
raw.div_ceil(512) * 512
}
#[cfg(test)]
pub(crate) fn af_split<D: Digest>(
master: &[u8],
block_size: usize,
stripes: usize,
filler: u8,
) -> Vec<u8> {
let mut material = vec![filler; block_size * stripes];
let mut acc = vec![0u8; block_size];
for i in 0..stripes - 1 {
let start = i * block_size;
xor_into(&mut acc, &material[start..start + block_size]);
acc = diffuse::<D>(&acc);
}
let last = (stripes - 1) * block_size;
for (j, b) in acc.iter().enumerate() {
material[last + j] = master[j] ^ b;
}
material
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_then_merge_roundtrips_sha256() {
let master: Vec<u8> = (0..64u8).collect();
let material = af_split::<Sha256>(&master, 64, 4000, 0x5a);
let back = merge("sha256", &material, 64, 4000).unwrap();
assert_eq!(back, master);
}
#[test]
fn split_then_merge_roundtrips_sha1_partial_chunk() {
let master: Vec<u8> = (0..32u8).map(|x| x.wrapping_mul(3)).collect();
let material = af_split::<Sha1>(&master, 32, 100, 0x33);
let back = merge("sha1", &material, 32, 100).unwrap();
assert_eq!(back, master);
}
#[test]
fn split_then_merge_roundtrips_sha512() {
let master: Vec<u8> = (0..64u8).map(|x| x ^ 0xa5).collect();
let material = af_split::<Sha512>(&master, 64, 500, 0x77);
let back = merge("sha512", &material, 64, 500).unwrap();
assert_eq!(back, master);
}
#[test]
fn unsupported_hash_errors() {
assert!(matches!(
merge("md5", &[0u8; 64], 64, 1),
Err(LuksError::Unsupported { what: "hash", .. })
));
}
#[test]
fn material_len_rounds_to_sector() {
assert_eq!(material_len(64, 4000), 256_000); assert_eq!(material_len(64, 1), 512); }
}