use aes::cipher::block_padding::NoPadding;
use aes::cipher::{
BlockCipher, BlockDecrypt, BlockDecryptMut, BlockEncrypt, BlockSizeUser, KeyInit, KeyIvInit,
};
use aes::{Aes128, Aes256};
use ccm::aead::generic_array::GenericArray;
use ccm::aead::AeadInPlace;
use ccm::consts::{U12, U16};
use ccm::{Ccm, KeyInit as CcmKeyInit};
use sha2::{Digest, Sha256};
use xts_mode::{get_tweak_default, Xts128 as XtsCipher};
const STRETCH_ITERATIONS: u64 = 0x0010_0000;
type BdeCcm = Ccm<aes::Aes256, U16, U12>;
#[must_use]
pub fn password_hash(password: &str) -> [u8; 32] {
let utf16: Vec<u8> = password.encode_utf16().flat_map(u16::to_le_bytes).collect();
let first = Sha256::digest(&utf16);
let second = Sha256::digest(first);
second.into()
}
#[must_use]
pub fn stretch_key_n(password_hash: &[u8; 32], salt: &[u8; 16], iterations: u64) -> [u8; 32] {
let mut buf = [0u8; 88];
buf[32..64].copy_from_slice(password_hash);
buf[64..80].copy_from_slice(salt);
for count in 0..iterations {
buf[80..88].copy_from_slice(&count.to_le_bytes());
let digest = Sha256::digest(buf);
buf[0..32].copy_from_slice(&digest);
}
let mut out = [0u8; 32];
out.copy_from_slice(&buf[0..32]);
out
}
#[must_use]
pub fn stretch_key(password_hash: &[u8; 32], salt: &[u8; 16]) -> [u8; 32] {
stretch_key_n(password_hash, salt, STRETCH_ITERATIONS)
}
pub fn recovery_key_hash(recovery: &str) -> Result<[u8; 32], &'static str> {
let mut key = [0u8; 16];
let mut groups = 0usize;
for (i, group) in recovery.split('-').enumerate() {
if i >= 8 {
return Err("recovery password must be exactly 8 groups");
}
if group.len() != 6 || !group.bytes().all(|b| b.is_ascii_digit()) {
return Err("each recovery group must be 6 digits");
}
let value: u32 = group.parse().map_err(|_| "invalid recovery digits")?;
if value % 11 != 0 {
return Err("recovery group failed the divisible-by-11 checksum");
}
let word = value / 11;
if word > u32::from(u16::MAX) {
return Err("recovery group out of range (value / 11 exceeds 16 bits)");
}
key[i * 2..i * 2 + 2].copy_from_slice(&(word as u16).to_le_bytes());
groups = i + 1;
}
if groups != 8 {
return Err("recovery password must be exactly 8 groups");
}
Ok(Sha256::digest(key).into())
}
#[must_use]
pub fn aes_ccm_unwrap(key: &[u8; 32], value_data: &[u8]) -> Option<Vec<u8>> {
let nonce = value_data.get(0..12)?;
let tag = value_data.get(12..28)?;
let mut buffer = value_data.get(28..)?.to_vec();
let cipher = <BdeCcm as CcmKeyInit>::new(GenericArray::from_slice(key));
cipher
.decrypt_in_place_detached(
GenericArray::from_slice(nonce),
&[],
&mut buffer,
GenericArray::from_slice(tag),
)
.ok()?;
Some(buffer)
}
#[cfg(test)]
#[must_use]
pub fn aes_ccm_wrap(key: &[u8; 32], nonce: &[u8; 12], plaintext: &[u8]) -> Vec<u8> {
let cipher = <BdeCcm as CcmKeyInit>::new(GenericArray::from_slice(key));
let mut buffer = plaintext.to_vec();
let tag = cipher
.encrypt_in_place_detached(GenericArray::from_slice(nonce), &[], &mut buffer)
.unwrap();
let mut out = Vec::with_capacity(12 + 16 + buffer.len());
out.extend_from_slice(nonce);
out.extend_from_slice(&tag);
out.extend_from_slice(&buffer);
out
}
#[allow(clippy::large_enum_variant)]
enum SectorTransform {
Cbc128 {
fvek: [u8; 16],
fvek_ecb: Aes128,
tweak: Option<Aes128>,
},
Cbc256 { fvek: [u8; 32], fvek_ecb: Aes256 },
Xts128 { xts: XtsCipher<Aes128> },
Xts256 { xts: XtsCipher<Aes256> },
}
pub struct SectorCipher {
transform: SectorTransform,
}
impl SectorCipher {
#[must_use]
pub fn new(fvek: [u8; 16], tweak: [u8; 16]) -> SectorCipher {
SectorCipher {
transform: SectorTransform::Cbc128 {
fvek,
fvek_ecb: Aes128::new(GenericArray::from_slice(&fvek)),
tweak: Some(Aes128::new(GenericArray::from_slice(&tweak))),
},
}
}
#[must_use]
pub fn new_cbc(fvek: [u8; 16]) -> SectorCipher {
SectorCipher {
transform: SectorTransform::Cbc128 {
fvek,
fvek_ecb: Aes128::new(GenericArray::from_slice(&fvek)),
tweak: None,
},
}
}
#[must_use]
pub fn new_cbc256(fvek: [u8; 32]) -> SectorCipher {
SectorCipher {
transform: SectorTransform::Cbc256 {
fvek,
fvek_ecb: Aes256::new(GenericArray::from_slice(&fvek)),
},
}
}
#[must_use]
pub fn new_xts128(fvek: [u8; 32]) -> SectorCipher {
let data = Aes128::new(GenericArray::from_slice(&fvek[0..16]));
let tweak = Aes128::new(GenericArray::from_slice(&fvek[16..32]));
SectorCipher {
transform: SectorTransform::Xts128 {
xts: XtsCipher::new(data, tweak),
},
}
}
#[must_use]
pub fn new_xts256(fvek: [u8; 64]) -> SectorCipher {
let data = Aes256::new(GenericArray::from_slice(&fvek[0..32]));
let tweak = Aes256::new(GenericArray::from_slice(&fvek[32..64]));
SectorCipher {
transform: SectorTransform::Xts256 {
xts: XtsCipher::new(data, tweak),
},
}
}
fn ecb<C>(cipher: &C, input: &[u8; 16]) -> [u8; 16]
where
C: BlockEncrypt + BlockSizeUser<BlockSize = U16>,
{
let mut block = GenericArray::clone_from_slice(input);
cipher.encrypt_block(&mut block);
block.into()
}
fn le128(byte_offset: u64) -> [u8; 16] {
let mut iv = [0u8; 16];
iv[0..8].copy_from_slice(&byte_offset.to_le_bytes());
iv
}
fn sector_key(tweak: &Aes128, byte_offset: u64) -> [u8; 32] {
let mut iv = Self::le128(byte_offset);
let lower = Self::ecb(tweak, &iv);
iv[15] = 0x80;
let upper = Self::ecb(tweak, &iv);
let mut key = [0u8; 32];
key[0..16].copy_from_slice(&lower);
key[16..32].copy_from_slice(&upper);
key
}
fn cbc_decrypt_inplace<C>(fvek: &[u8], iv: &[u8; 16], buf: &mut [u8]) -> usize
where
C: BlockCipher + BlockDecrypt + KeyInit,
{
let dec =
cbc::Decryptor::<C>::new(GenericArray::from_slice(fvek), GenericArray::from_slice(iv));
let len = buf.len() - (buf.len() % 16);
match dec.decrypt_padded_mut::<NoPadding>(&mut buf[..len]) {
Ok(plain) => plain.len(),
Err(_) => len, }
}
fn xts_decrypt_sector<C>(xts: &XtsCipher<C>, buf: &mut [u8], byte_offset: u64)
where
C: BlockEncrypt + BlockDecrypt + BlockCipher,
{
if buf.len() >= 16 {
xts.decrypt_sector(buf, get_tweak_default(u128::from(byte_offset / 512)));
}
}
#[cfg(test)]
fn xts_encrypt_sector<C>(xts: &XtsCipher<C>, buf: &mut [u8], byte_offset: u64)
where
C: BlockEncrypt + BlockDecrypt + BlockCipher,
{
xts.encrypt_sector(buf, get_tweak_default(u128::from(byte_offset / 512)));
}
#[must_use]
pub fn decrypt_sector(&self, cipher: &[u8], byte_offset: u64) -> Vec<u8> {
let mut buf = cipher.to_vec();
let iv = Self::le128(byte_offset);
match &self.transform {
SectorTransform::Cbc128 {
fvek,
fvek_ecb,
tweak,
} => {
let cbc_iv = Self::ecb(fvek_ecb, &iv);
let plain_len = Self::cbc_decrypt_inplace::<Aes128>(fvek, &cbc_iv, &mut buf);
if let Some(tweak) = tweak {
let sector_key = Self::sector_key(tweak, byte_offset);
elephant_diffuser::decrypt(&mut buf[..plain_len], §or_key);
}
}
SectorTransform::Cbc256 { fvek, fvek_ecb } => {
let cbc_iv = Self::ecb(fvek_ecb, &iv);
let _ = Self::cbc_decrypt_inplace::<Aes256>(fvek, &cbc_iv, &mut buf);
}
SectorTransform::Xts128 { xts } => Self::xts_decrypt_sector(xts, &mut buf, byte_offset),
SectorTransform::Xts256 { xts } => Self::xts_decrypt_sector(xts, &mut buf, byte_offset),
}
buf
}
#[cfg(test)]
#[must_use]
pub fn encrypt_sector(&self, plain: &[u8], byte_offset: u64) -> Vec<u8> {
use aes::cipher::BlockEncryptMut;
let mut buf = plain.to_vec();
let iv = Self::le128(byte_offset);
let len = buf.len() - (buf.len() % 16);
match &self.transform {
SectorTransform::Cbc128 {
fvek,
fvek_ecb,
tweak,
} => {
if let Some(tweak) = tweak {
let sector_key = Self::sector_key(tweak, byte_offset);
elephant_diffuser::encrypt(&mut buf, §or_key);
}
let cbc_iv = Self::ecb(fvek_ecb, &iv);
cbc::Encryptor::<Aes128>::new(
GenericArray::from_slice(fvek),
GenericArray::from_slice(&cbc_iv),
)
.encrypt_padded_mut::<NoPadding>(&mut buf[..len], len)
.unwrap();
}
SectorTransform::Cbc256 { fvek, fvek_ecb } => {
let cbc_iv = Self::ecb(fvek_ecb, &iv);
cbc::Encryptor::<Aes256>::new(
GenericArray::from_slice(fvek),
GenericArray::from_slice(&cbc_iv),
)
.encrypt_padded_mut::<NoPadding>(&mut buf[..len], len)
.unwrap();
}
SectorTransform::Xts128 { xts } => Self::xts_encrypt_sector(xts, &mut buf, byte_offset),
SectorTransform::Xts256 { xts } => Self::xts_encrypt_sector(xts, &mut buf, byte_offset),
}
buf
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hex(bytes: &[u8]) -> String {
use std::fmt::Write;
bytes.iter().fold(String::new(), |mut s, b| {
let _ = write!(s, "{b:02x}");
s
})
}
#[test]
fn password_hash_matches_independent_python() {
assert_eq!(
hex(&password_hash("bde-TEST")),
"f5acb5bd3c4e31c5c988128fcfc50717da18ca4f7dbaa8bf21e7525bd431ee3f"
);
}
#[test]
fn stretch_two_iterations_matches_independent_python() {
let salt: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
let ph = password_hash("bde-TEST");
assert_eq!(
hex(&stretch_key_n(&ph, &salt, 2)),
"0660651b876f7d3777e292fb96671e57d48a4b34d7e17c3e01f20d6ba6af42e4"
);
}
#[test]
fn recovery_key_hash_matches_independent_python() {
assert_eq!(
hex(
&recovery_key_hash("111111-111111-111111-111111-111111-111111-111111-111111")
.unwrap()
),
"17f2c896b4e802b3668dfe7f8b22ab00b7adbba67097643f3c02767abc72648e"
);
assert_eq!(
hex(
&recovery_key_hash("068002-479633-277629-623568-540826-435039-327756-375705")
.unwrap()
),
"6bb2448ffc833b574bcc0e7c3d9f8e8afd7410692289e7405cbcc42a7fee3de3"
);
}
#[test]
fn recovery_key_hash_rejects_malformed() {
assert!(recovery_key_hash("111111").is_err());
assert!(recovery_key_hash(
"111111-111111-111111-111111-111111-111111-111111-111111-111111"
)
.is_err());
assert!(
recovery_key_hash("111111-111111-111111-111111-111111-111111-111111-11111x").is_err()
);
assert!(
recovery_key_hash("11111-111111-111111-111111-111111-111111-111111-111111").is_err()
);
assert!(
recovery_key_hash("111112-111111-111111-111111-111111-111111-111111-111111").is_err()
);
assert!(
recovery_key_hash("999999-111111-111111-111111-111111-111111-111111-111111").is_err()
);
}
fn sample_sector() -> Vec<u8> {
(0..512u32)
.map(|i| (i.wrapping_mul(31) ^ 0xA5) as u8)
.collect()
}
#[test]
fn ccm_unwrap_roundtrip_ms_layout() {
let key = [7u8; 32];
let nonce = [3u8; 12];
let plaintext = b"volume master key container payload!!".to_vec();
let cipher = <BdeCcm as CcmKeyInit>::new(GenericArray::from_slice(&key));
let mut buf = plaintext.clone();
let tag = cipher
.encrypt_in_place_detached(GenericArray::from_slice(&nonce), &[], &mut buf)
.unwrap();
let mut value_data = Vec::new();
value_data.extend_from_slice(&nonce);
value_data.extend_from_slice(&tag); value_data.extend_from_slice(&buf);
assert_eq!(aes_ccm_unwrap(&key, &value_data).unwrap(), plaintext);
assert!(aes_ccm_unwrap(&[8u8; 32], &value_data).is_none());
assert!(aes_ccm_unwrap(&key, &value_data[..20]).is_none());
}
#[test]
fn sector_cipher_roundtrip() {
let cipher = SectorCipher::new([0x11; 16], [0x22; 16]);
let plain = sample_sector();
let off = 0x0211_0800u64;
let ct = cipher.encrypt_sector(&plain, off);
assert_ne!(ct, plain);
assert_eq!(cipher.decrypt_sector(&ct, off), plain);
}
#[test]
fn sector_cipher_cbc_roundtrip() {
let cipher = SectorCipher::new_cbc([0x13; 16]);
let plain = sample_sector();
let off = 0x0211_0800u64;
let ct = cipher.encrypt_sector(&plain, off);
assert_ne!(ct, plain);
assert_eq!(cipher.decrypt_sector(&ct, off), plain);
}
#[test]
fn sector_cipher_cbc256_roundtrip() {
let cipher = SectorCipher::new_cbc256([0x24; 32]);
let plain = sample_sector();
let off = 0x0211_0800u64;
let ct = cipher.encrypt_sector(&plain, off);
assert_ne!(ct, plain);
assert_eq!(cipher.decrypt_sector(&ct, off), plain);
}
#[test]
fn sector_cipher_xts128_roundtrip() {
let cipher = SectorCipher::new_xts128([0x35; 32]);
let plain = sample_sector();
let off = 0x0211_0800u64;
let ct = cipher.encrypt_sector(&plain, off);
assert_ne!(ct, plain);
assert_eq!(cipher.decrypt_sector(&ct, off), plain);
}
#[test]
fn sector_cipher_xts256_roundtrip() {
let cipher = SectorCipher::new_xts256([0x46; 64]);
let plain = sample_sector();
let off = 0x0211_0800u64;
let ct = cipher.encrypt_sector(&plain, off);
assert_ne!(ct, plain);
assert_eq!(cipher.decrypt_sector(&ct, off), plain);
}
}