From 9ff3fa3b49c16fbb73dcae17051ff82768088fe7 Mon Sep 17 00:00:00 2001 From: M Alghazwi Date: Fri, 1 May 2026 21:11:44 +0300 Subject: [PATCH] impl enc/dec with auth --- README.md | 4 ++-- examples/auth.rs | 34 +++++++++++++++++++++++++++++++--- src/kdf.rs | 8 ++++---- src/lioness.rs | 30 ++++++++++++++++++++++++------ 4 files changed, 61 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7c05928..46cfda0 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,8 @@ fn main() -> anyhow::Result<()> { Some notes: - Encryption and decryption are both in-place for now. -- The block length need to be bigger than `64` bytes because Lioness splits the block into two where the left part is 32-byte, and the right part needs at least 16 bytes. might support small blocks in the future, but for Sphinx use-case, this should work. -- If you need authenticity, make sure to use `encrypt_in_place_auth` which prepends the plaintext with 128-bits zeros. Also use `decrypt_in_place_auth` to check the zeros after decryption. see [authentication example](./examples/auth) +- The block length need to be at least `64` bytes because Lioness splits the block into two where the left part is 32-byte, and the right part needs at least 16 bytes. might support small blocks in the future, but for Sphinx use-case, this should work. +- If you need authenticity, make sure to use `encrypt_auth` which prepends the plaintext with 128-bits zeros. Also use `decrypt_auth` to check the zeros after decryption. see [authentication example](./examples/auth) ### TODO - [x] Add more tests, examples, and benchmarks ... diff --git a/examples/auth.rs b/examples/auth.rs index f66b3cc..5206e69 100644 --- a/examples/auth.rs +++ b/examples/auth.rs @@ -9,7 +9,7 @@ type TestLioness = Lioness::< TurboShake128Kdf >; -fn main() -> Result<()> { +fn prepend_before_enc() -> Result<()>{ let mut key: Key256 = Default::default(); OsRng.fill_bytes(&mut key); let cipher: TestLioness = Lioness::new(&key)?; @@ -26,9 +26,37 @@ fn main() -> Result<()> { cipher.decrypt_in_place(&mut block)?; - if block[..SEC_PARAM].iter().all(|&b| b != 0) { - println!("tampering detected i.e. zero-prefix check failed"); + for b in block[..SEC_PARAM].iter(){ + if *b != 0{ + println!("tampering detected i.e. zero-prefix check failed"); + break + } } Ok(()) } + +fn call_enc_auth() -> Result<()>{ + let mut key: Key256 = Default::default(); + OsRng.fill_bytes(&mut key); + let cipher: TestLioness = Lioness::new(&key)?; + + let mut payload = [0x84u8; 4096]; + + // let mut block = plaintext.clone(); + let mut ciphertext = cipher.encrypt_auth(&mut payload)?; + + // tamper with the ciphertext + ciphertext[21] ^= 0x01; + + assert!(cipher.decrypt_auth(&mut ciphertext).is_err()); + + Ok(()) +} + +fn main() -> Result<()> { + // prepend before calling the lioness encryption + prepend_before_enc()?; + // use built-in functions + call_enc_auth() +} diff --git a/src/kdf.rs b/src/kdf.rs index 5e57910..e092cf0 100644 --- a/src/kdf.rs +++ b/src/kdf.rs @@ -60,10 +60,10 @@ impl LionessKdf for HkdfSha256{ pub struct DomSepSha256Kdf; const LIONESS_ROUND_KEY_DOMAINS: [&[u8]; 4] = [ - b"Lioness-key1", - b"Lioness-key2", - b"Lioness-key3", - b"Lionesskey4", + b"lioness-key1", + b"lioness-key2", + b"lioness-key3", + b"lionesskey4", ]; impl LionessKdf for DomSepSha256Kdf { fn derive_keys(master_key: &Key256) -> anyhow::Result { diff --git a/src/lioness.rs b/src/lioness.rs index b2fa465..9d91fd3 100644 --- a/src/lioness.rs +++ b/src/lioness.rs @@ -121,10 +121,17 @@ impl< } /// Same as `encrypt_in_place` but prepends the plaintext with `SEC_PARAM` bytes of zeros - pub fn encrypt_in_place_auth(&self, _block: &mut [u8]) -> Result<()> { - let mut plaintext = vec![0u8; SEC_PARAM]; - plaintext.extend_from_slice(_block); - todo!() + /// WARNING: The output ciphertext is 16 bytes bigger than the input block + /// Here we can accept 48 bytes since we add 16-bytes zero prefix. + pub fn encrypt_auth(&self, plaintext: &mut [u8]) -> Result> { + // Here we can accept at least 48 bytes since we add 16-bytes zero prefix. + if plaintext.len() < 2*K_256 - SEC_PARAM { + return Err(anyhow!("block must be at least {} bytes", 2*K_256 - SEC_PARAM)); + } + let mut block = vec![0u8; SEC_PARAM + plaintext.len()]; + block[SEC_PARAM..].copy_from_slice(plaintext); + self.encrypt_in_place(&mut block)?; + Ok(block) } /// Decrypt a single wide block in place. @@ -150,8 +157,19 @@ impl< } /// Same as `decrypt_in_place` with added check for `SEC_PARAM`-bytes zero prefix - pub fn decrypt_in_place_auth(&self, _block: &mut [u8]) -> Result<()> { - todo!() + /// returns either the plaintext without the zero prefix or throws an error + pub fn decrypt_auth(&self, block: &mut [u8]) -> Result> { + if block.len() < 2*K_256 { + return Err(anyhow!("blocks must be at least {} bytes", 2*K_256)); + } + self.decrypt_in_place(block)?; + for b in block[..SEC_PARAM].iter(){ + if *b != 0{ + return Err(anyhow!("ciphertext tampering detected!")) + } + } + + Ok(block.to_vec().split_off(SEC_PARAM)) } /// apply the steam cipher round