use aes_gcm_siv::{Aes256GcmSiv, KeyInit, Nonce};
use aes_gcm_siv::aead::Aead;
use argon2::Argon2;
use zeroize::Zeroizing;
use crate::stego::error::StegoError;
const STRUCTURAL_SALT: &[u8; 16] = b"phasm-ghost-v1\0\0";
const ARMOR_STRUCTURAL_SALT: &[u8; 16] = b"phasm-armor-v1\0\0";
const TEMPLATE_SALT: &[u8; 16] = b"phasm-tmpl-v1\0\0\0";
const FORTRESS_STRUCTURAL_SALT: &[u8; 16] = b"phasm-fort-v1\0\0\0";
pub const NONCE_LEN: usize = 12;
pub const SALT_LEN: usize = 16;
const SHADOW_STRUCTURAL_SALT: &[u8; 16] = b"phasm-shdw-v1\0\0\0";
const H264_MVD_STRUCTURAL_SALT: &[u8; 16] = b"phasm-h264mvd-v1";
pub const FORTRESS_EMPTY_SALT: [u8; SALT_LEN] = *b"phasm-fe-salt00\0";
pub const FORTRESS_EMPTY_NONCE: [u8; NONCE_LEN] = *b"ph-fe-nonce\0";
pub fn derive_structural_key(passphrase: &str) -> Result<Zeroizing<[u8; 64]>, StegoError> {
let mut output = Zeroizing::new([0u8; 64]);
Argon2::default()
.hash_password_into(passphrase.as_bytes(), STRUCTURAL_SALT, &mut *output)
.map_err(|_| StegoError::KeyDerivationFailed)?;
Ok(output)
}
pub fn derive_h264_mvd_structural_key(
passphrase: &str,
) -> Result<Zeroizing<[u8; 64]>, StegoError> {
let mut output = Zeroizing::new([0u8; 64]);
Argon2::default()
.hash_password_into(passphrase.as_bytes(), H264_MVD_STRUCTURAL_SALT, &mut *output)
.map_err(|_| StegoError::KeyDerivationFailed)?;
Ok(output)
}
pub fn derive_per_gop_seed_from_master(
master_seed: &[u8; 32],
gop_idx: u32,
label: &[u8],
) -> [u8; 32] {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(b"phasm-h264-gop-v1");
hasher.update(master_seed);
hasher.update(label);
hasher.update(gop_idx.to_le_bytes());
let digest = hasher.finalize();
let mut out = [0u8; 32];
out.copy_from_slice(&digest);
out
}
pub fn derive_armor_structural_key(passphrase: &str) -> Result<Zeroizing<[u8; 64]>, StegoError> {
let mut output = Zeroizing::new([0u8; 64]);
Argon2::default()
.hash_password_into(passphrase.as_bytes(), ARMOR_STRUCTURAL_SALT, &mut *output)
.map_err(|_| StegoError::KeyDerivationFailed)?;
Ok(output)
}
pub fn derive_template_key(passphrase: &str) -> Result<[u8; 32], StegoError> {
let mut output = [0u8; 32];
Argon2::default()
.hash_password_into(passphrase.as_bytes(), TEMPLATE_SALT, &mut output)
.map_err(|_| StegoError::KeyDerivationFailed)?;
Ok(output)
}
pub fn derive_fortress_structural_key(passphrase: &str) -> Result<[u8; 32], StegoError> {
let mut output = [0u8; 32];
Argon2::default()
.hash_password_into(passphrase.as_bytes(), FORTRESS_STRUCTURAL_SALT, &mut output)
.map_err(|_| StegoError::KeyDerivationFailed)?;
Ok(output)
}
pub fn derive_shadow_structural_key(passphrase: &str) -> Result<Zeroizing<[u8; 32]>, StegoError> {
let mut output = Zeroizing::new([0u8; 32]);
Argon2::default()
.hash_password_into(passphrase.as_bytes(), SHADOW_STRUCTURAL_SALT, &mut *output)
.map_err(|_| StegoError::KeyDerivationFailed)?;
Ok(output)
}
pub fn derive_encryption_key(passphrase: &str, salt: &[u8]) -> Result<Zeroizing<[u8; 32]>, StegoError> {
let mut key = Zeroizing::new([0u8; 32]);
Argon2::default()
.hash_password_into(passphrase.as_bytes(), salt, &mut *key)
.map_err(|_| StegoError::KeyDerivationFailed)?;
Ok(key)
}
pub fn encrypt(plaintext: &[u8], passphrase: &str) -> Result<(Vec<u8>, [u8; NONCE_LEN], [u8; SALT_LEN]), StegoError> {
use rand::RngCore;
let mut rng = rand::thread_rng();
let mut salt = [0u8; SALT_LEN];
rng.fill_bytes(&mut salt);
let mut nonce_bytes = [0u8; NONCE_LEN];
rng.fill_bytes(&mut nonce_bytes);
let key = derive_encryption_key(passphrase, &salt)?;
let cipher = Aes256GcmSiv::new_from_slice(&*key).expect("valid key length");
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher.encrypt(nonce, plaintext).expect("AES-GCM-SIV encrypt should not fail");
Ok((ciphertext, nonce_bytes, salt))
}
pub fn encrypt_with(
plaintext: &[u8],
passphrase: &str,
salt: &[u8; SALT_LEN],
nonce_bytes: &[u8; NONCE_LEN],
) -> Result<Vec<u8>, StegoError> {
let key = derive_encryption_key(passphrase, salt)?;
let cipher = Aes256GcmSiv::new_from_slice(&*key).expect("valid key length");
let nonce = Nonce::from_slice(nonce_bytes);
Ok(cipher.encrypt(nonce, plaintext).expect("AES-GCM-SIV encrypt should not fail"))
}
pub fn decrypt(
ciphertext: &[u8],
passphrase: &str,
salt: &[u8],
nonce_bytes: &[u8; NONCE_LEN],
) -> Result<Vec<u8>, StegoError> {
let key = derive_encryption_key(passphrase, salt)?;
let cipher = Aes256GcmSiv::new_from_slice(&*key).expect("valid key length");
let nonce = Nonce::from_slice(nonce_bytes);
cipher
.decrypt(nonce, ciphertext)
.map_err(|_| StegoError::DecryptionFailed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encrypt_decrypt_roundtrip() {
let msg = b"Hello, steganography!";
let passphrase = "secret123";
let (ct, nonce, salt) = encrypt(msg, passphrase).unwrap();
let pt = decrypt(&ct, passphrase, &salt, &nonce).unwrap();
assert_eq!(pt, msg);
}
#[test]
fn wrong_passphrase_fails() {
let msg = b"secret message";
let (ct, nonce, salt) = encrypt(msg, "correct").unwrap();
let result = decrypt(&ct, "wrong", &salt, &nonce);
assert!(matches!(result, Err(StegoError::DecryptionFailed)));
}
#[test]
fn empty_message_works() {
let msg = b"";
let passphrase = "pass";
let (ct, nonce, salt) = encrypt(msg, passphrase).unwrap();
let pt = decrypt(&ct, passphrase, &salt, &nonce).unwrap();
assert_eq!(pt, msg.to_vec());
}
#[test]
fn structural_key_deterministic() {
let a = derive_structural_key("mypass").unwrap();
let b = derive_structural_key("mypass").unwrap();
assert_eq!(a, b);
}
#[test]
fn structural_key_differs_by_passphrase() {
let a = derive_structural_key("pass1").unwrap();
let b = derive_structural_key("pass2").unwrap();
assert_ne!(a, b);
}
#[test]
fn ghost_and_armor_structural_keys_differ() {
let ghost = derive_structural_key("same_pass").unwrap();
let armor = derive_armor_structural_key("same_pass").unwrap();
assert_ne!(ghost, armor, "Ghost and Armor keys must differ for the same passphrase");
}
#[test]
fn armor_structural_key_deterministic() {
let a = derive_armor_structural_key("mypass").unwrap();
let b = derive_armor_structural_key("mypass").unwrap();
assert_eq!(a, b);
}
#[test]
fn template_key_deterministic() {
let a = derive_template_key("mypass").unwrap();
let b = derive_template_key("mypass").unwrap();
assert_eq!(a, b);
}
#[test]
fn fortress_key_deterministic() {
let a = derive_fortress_structural_key("mypass").unwrap();
let b = derive_fortress_structural_key("mypass").unwrap();
assert_eq!(a, b);
}
#[test]
fn shadow_key_deterministic() {
let a = derive_shadow_structural_key("mypass").unwrap();
let b = derive_shadow_structural_key("mypass").unwrap();
assert_eq!(a, b);
}
#[test]
fn shadow_key_differs_from_others() {
let ghost = derive_structural_key("same_pass").unwrap();
let armor = derive_armor_structural_key("same_pass").unwrap();
let fortress = derive_fortress_structural_key("same_pass").unwrap();
let template = derive_template_key("same_pass").unwrap();
let shadow = derive_shadow_structural_key("same_pass").unwrap();
assert_ne!(&ghost[..32], &shadow[..]);
assert_ne!(&armor[..32], &shadow[..]);
assert_ne!(&fortress[..], &shadow[..]);
assert_ne!(&template[..], &shadow[..]);
}
#[test]
fn fortress_key_differs_from_others() {
let ghost = derive_structural_key("same_pass").unwrap();
let armor = derive_armor_structural_key("same_pass").unwrap();
let fortress = derive_fortress_structural_key("same_pass").unwrap();
let template = derive_template_key("same_pass").unwrap();
assert_ne!(&ghost[..32], &fortress[..]);
assert_ne!(&armor[..32], &fortress[..]);
assert_ne!(&template[..], &fortress[..]);
}
#[test]
fn template_key_differs_from_structural() {
let ghost = derive_structural_key("same_pass").unwrap();
let armor = derive_armor_structural_key("same_pass").unwrap();
let template = derive_template_key("same_pass").unwrap();
assert_ne!(&ghost[..32], &template[..]);
assert_ne!(&armor[..32], &template[..]);
}
#[test]
fn encryption_key_differs_by_salt() {
let key1 = derive_encryption_key("pass", &[0u8; 16]).unwrap();
let key2 = derive_encryption_key("pass", &[1u8; 16]).unwrap();
assert_ne!(key1, key2);
}
#[test]
fn ciphertext_differs_per_encryption() {
let msg = b"same message";
let (ct1, _, _) = encrypt(msg, "pass").unwrap();
let (ct2, _, _) = encrypt(msg, "pass").unwrap();
assert_ne!(ct1, ct2, "repeated encryptions should produce different ciphertext");
}
}