feat(anti-forensics): implement incremental auto-vacuum, chunk shredding, and plausible deniability
This commit is contained in:
@@ -129,6 +129,70 @@ pub fn unwrap_dek(
|
||||
Ok(dek)
|
||||
}
|
||||
|
||||
/// Erzeugt einen Dummy-Header-Slot mit kryptografisch sicherem Zufallsrauschen derselben Länge wie
|
||||
/// ein echter KDF/DEK-Slot. Dadurch sind Standard-Container von Containern mit Hidden Vault
|
||||
/// auf Bitebene und Entropieebene ununterscheidbar (Plausible Deniability).
|
||||
pub fn generate_dummy_slot() -> (Vec<u8>, [u8; 12], [u8; 16], [u8; 16]) {
|
||||
let mut wrapped_dek = vec![0u8; 32];
|
||||
let mut nonce = [0u8; 12];
|
||||
let mut tag = [0u8; 16];
|
||||
let mut salt = [0u8; 16];
|
||||
OsRng.fill_bytes(&mut wrapped_dek);
|
||||
OsRng.fill_bytes(&mut nonce);
|
||||
OsRng.fill_bytes(&mut tag);
|
||||
OsRng.fill_bytes(&mut salt);
|
||||
(wrapped_dek, nonce, tag, salt)
|
||||
}
|
||||
|
||||
/// Verschlüsselt den Dateinamen für Knoten im Hidden Vault mit AES-256-GCM.
|
||||
/// Verhindert, dass unverschlüsselte Dateinamen in der SQLite-Datenbank forensisch auffindbar sind.
|
||||
pub fn encrypt_node_name(dek: &[u8; 32], name: &str) -> String {
|
||||
let mut nonce_bytes = [0u8; 12];
|
||||
OsRng.fill_bytes(&mut nonce_bytes);
|
||||
let cipher = Aes256Gcm::new_from_slice(dek).expect("AES init");
|
||||
let mut buffer = name.as_bytes().to_vec();
|
||||
let tag = cipher
|
||||
.encrypt_in_place_detached(Nonce::from_slice(&nonce_bytes), b"SANCTUM_NODE_NAME", &mut buffer)
|
||||
.expect("Name encryption");
|
||||
format!(
|
||||
"$h${}${}${}",
|
||||
hex::encode(nonce_bytes),
|
||||
hex::encode(tag.as_slice()),
|
||||
hex::encode(&buffer)
|
||||
)
|
||||
}
|
||||
|
||||
/// Entschlüsselt den Dateinamen eines Knotens im Hidden Vault mit AES-256-GCM.
|
||||
pub fn decrypt_node_name(dek: &[u8; 32], stored: &str) -> Option<String> {
|
||||
if let Some(rest) = stored.strip_prefix("$h$") {
|
||||
let parts: Vec<&str> = rest.split('$').collect();
|
||||
if parts.len() == 3 {
|
||||
if let (Ok(nonce_bytes), Ok(tag_bytes), Ok(ct_bytes)) = (
|
||||
hex::decode(parts[0]),
|
||||
hex::decode(parts[1]),
|
||||
hex::decode(parts[2]),
|
||||
) {
|
||||
if nonce_bytes.len() == 12 && tag_bytes.len() == 16 {
|
||||
let cipher = Aes256Gcm::new_from_slice(dek).ok()?;
|
||||
let mut buffer = ct_bytes;
|
||||
if cipher
|
||||
.decrypt_in_place_detached(
|
||||
Nonce::from_slice(&nonce_bytes),
|
||||
b"SANCTUM_NODE_NAME",
|
||||
&mut buffer,
|
||||
Tag::from_slice(&tag_bytes),
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
return String::from_utf8(buffer).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Kodiert den 32-Byte (256-Bit) DEK in eine 24-Wort BIP-39 Notfall-Wiederherstellungsphrase (englisch) mit 8-Bit Checksumme.
|
||||
pub fn dek_to_mnemonic(dek: &[u8; 32]) -> Result<String> {
|
||||
let mnemonic = bip39::Mnemonic::from_entropy(dek)
|
||||
@@ -412,5 +476,28 @@ mod tests {
|
||||
let corrupted_phrase = words.join(" ");
|
||||
assert!(mnemonic_to_dek(&corrupted_phrase).is_err(), "Checksum check must fail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hidden_node_name_encryption_and_dummy_slot() {
|
||||
let dek = generate_dek();
|
||||
let filename = "ultra_geheimes_dokument.pdf";
|
||||
let encrypted = encrypt_node_name(&dek, filename);
|
||||
assert!(encrypted.starts_with("$h$"));
|
||||
assert!(!encrypted.contains(filename));
|
||||
|
||||
let decrypted = decrypt_node_name(&dek, &encrypted).expect("Decrypt name");
|
||||
assert_eq!(decrypted, filename);
|
||||
|
||||
// Mit anderem DEK schlägt Entschlüsselung fehl
|
||||
let other_dek = generate_dek();
|
||||
assert!(decrypt_node_name(&other_dek, &encrypted).is_none());
|
||||
|
||||
// Dummy-Slot hat korrekte Längen
|
||||
let (dummy_dek, dummy_nonce, dummy_tag, dummy_salt) = generate_dummy_slot();
|
||||
assert_eq!(dummy_dek.len(), 32);
|
||||
assert_eq!(dummy_nonce.len(), 12);
|
||||
assert_eq!(dummy_tag.len(), 16);
|
||||
assert_eq!(dummy_salt.len(), 16);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user