diff --git a/Cargo.lock b/Cargo.lock index 7e7a691a..069e1fcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -245,6 +245,7 @@ dependencies = [ "aead", "aes", "hex-literal", + "inout", "subtle", "zeroize", ] diff --git a/deoxys/Cargo.toml b/deoxys/Cargo.toml index 7c360207..dbf9bef8 100644 --- a/deoxys/Cargo.toml +++ b/deoxys/Cargo.toml @@ -20,6 +20,7 @@ rust-version = "1.85" [dependencies] aead = { version = "0.6.0-rc.0", default-features = false } aes = { version = "=0.9.0-pre.3", features = ["hazmat"], default-features = false } +inout = { version = "0.2.0-rc.4", default-features = false } subtle = { version = "2", default-features = false } zeroize = { version = "1", optional = true, default-features = false } diff --git a/deoxys/src/lib.rs b/deoxys/src/lib.rs index 63df0f7b..f7c78841 100644 --- a/deoxys/src/lib.rs +++ b/deoxys/src/lib.rs @@ -118,6 +118,7 @@ use aead::{ consts::U16, }; use core::marker::PhantomData; +use inout::{InOut, InOutBuf}; /// Deoxys-I with 128-bit keys pub type DeoxysI128 = Deoxys, deoxys_bc::DeoxysBc256>; @@ -156,19 +157,19 @@ where /// Encrypts the data in place with the specified parameters /// Returns the tag - fn encrypt_in_place( + fn encrypt_inout( nonce: &Array, associated_data: &[u8], - buffer: &mut [u8], + buffer: InOutBuf<'_, '_, u8>, subkeys: &Array, ) -> Tag; /// Decrypts the data in place with the specified parameters /// Returns an error if the tag verification fails - fn decrypt_in_place( + fn decrypt_inout( nonce: &Array, associated_data: &[u8], - buffer: &mut [u8], + buffer: InOutBuf<'_, '_, u8>, tag: &Tag, subkeys: &Array, ) -> Result<(), aead::Error>; @@ -184,25 +185,23 @@ pub trait DeoxysBcType: deoxys_bc::DeoxysBcInternal { fn precompute_subkeys(key: &Array) -> Array; /// Encrypts a block of data in place. - fn encrypt_in_place( - block: &mut Block, + fn encrypt_inout( + mut block: InOut<'_, '_, Block>, tweak: &Tweak, subkeys: &Array, ) { let keys = Self::key_schedule(tweak, subkeys); - for (b, k) in block.iter_mut().zip(keys[0].iter()) { - *b ^= k; - } + block.xor_in2out(&keys[0]); for k in &keys[1..] { - aes::hazmat::cipher_round(block, k); + aes::hazmat::cipher_round(block.get_out(), k); } } /// Decrypts a block of data in place. - fn decrypt_in_place( - block: &mut Block, + fn decrypt_inout( + mut block: InOut<'_, '_, Block>, tweak: &Tweak, subkeys: &Array, ) { @@ -210,18 +209,16 @@ pub trait DeoxysBcType: deoxys_bc::DeoxysBcInternal { let r = keys.len(); - for (b, k) in block.iter_mut().zip(keys[r - 1].iter()) { - *b ^= k; - } + block.xor_in2out(&keys[r - 1]); - aes::hazmat::inv_mix_columns(block); + aes::hazmat::inv_mix_columns(block.get_out()); for k in keys[..r - 1].iter_mut().rev() { aes::hazmat::inv_mix_columns(k); - aes::hazmat::equiv_inv_cipher_round(block, k); + aes::hazmat::equiv_inv_cipher_round(block.get_out(), k); } - aes::hazmat::mix_columns(block); + aes::hazmat::mix_columns(block.get_out()); } } @@ -285,10 +282,10 @@ where associated_data: &[u8], buffer: &mut [u8], ) -> Result { - Ok(Tag::from(M::encrypt_in_place( + Ok(Tag::from(M::encrypt_inout( nonce, associated_data, - buffer, + buffer.into(), &self.subkeys, ))) } @@ -300,7 +297,7 @@ where buffer: &mut [u8], tag: &Tag, ) -> Result<(), Error> { - M::decrypt_in_place(nonce, associated_data, buffer, tag, &self.subkeys) + M::decrypt_inout(nonce, associated_data, buffer.into(), tag, &self.subkeys) } } @@ -327,3 +324,114 @@ where B: DeoxysBcType, { } + +#[cfg(test)] +mod tests { + //! this module is here to test the inout behavior which is not currently exposed. + //! it will be once we port over to the API made in RustCrypto/traits#1793. + //! + //! This is to drop once https://github.com/RustCrypto/traits/pull/1797 is made available. + //! + //! It duplicates test vectors from `tests/deoxys_i_128.rs` and provides a mock buffer backing + //! for InOut. + + use hex_literal::hex; + + use super::*; + + struct MockBuffer { + in_buf: [u8; 33], + out_buf: [u8; 33], + } + + impl From<&[u8]> for MockBuffer { + fn from(buf: &[u8]) -> Self { + let mut in_buf = [0u8; 33]; + in_buf.copy_from_slice(buf); + Self { + in_buf, + out_buf: [0u8; 33], + } + } + } + + impl MockBuffer { + /// Get an [`InOutBuf`] from a [`MockBuffer`] + pub fn to_in_out_buf(&mut self) -> InOutBuf<'_, '_, u8> { + InOutBuf::new(self.in_buf.as_slice(), self.out_buf.as_mut_slice()) + .expect("Invariant violation") + } + } + + impl AsRef<[u8]> for MockBuffer { + fn as_ref(&self) -> &[u8] { + &self.out_buf + } + } + + #[test] + fn test_deoxys_i_128_5() { + let plaintext = hex!("5a4c652cb880808707230679224b11799b5883431292973215e9bd03cf3bc32fe4"); + let mut buffer = MockBuffer::from(&plaintext[..]); + + let aad = []; + + let key = hex!("101112131415161718191a1b1c1d1e1f"); + let key = Array(key); + + let nonce = hex!("202122232425262728292a2b2c2d2e2f"); + let nonce = Array::try_from(&nonce[..8]).unwrap(); + + let ciphertext_expected = + hex!("cded5a43d3c76e942277c2a1517530ad66037897c985305ede345903ed7585a626"); + + let tag_expected: [u8; 16] = hex!("cbf5faa6b8398c47f4278d2019161776"); + + type M = modes::DeoxysI; + let cipher = DeoxysI128::new(&key); + let tag: Tag = M::encrypt_inout(&nonce, &aad, buffer.to_in_out_buf(), &cipher.subkeys); + + let ciphertext = buffer.as_ref(); + assert_eq!(ciphertext, ciphertext_expected); + assert_eq!(tag, tag_expected); + + let mut buffer = MockBuffer::from(buffer.as_ref()); + M::decrypt_inout(&nonce, &aad, buffer.to_in_out_buf(), &tag, &cipher.subkeys) + .expect("decryption failed"); + + assert_eq!(&plaintext[..], buffer.as_ref()); + } + + #[test] + fn test_deoxys_ii_128_5() { + let plaintext = hex!("06ac1756eccece62bd743fa80c299f7baa3872b556130f52265919494bdc136db3"); + let mut buffer = MockBuffer::from(&plaintext[..]); + + let aad = []; + + let key = hex!("101112131415161718191a1b1c1d1e1f"); + let key = Array(key); + + let nonce = hex!("202122232425262728292a2b2c2d2e2f"); + let nonce = Array::try_from(&nonce[..15]).unwrap(); + + let ciphertext_expected = + hex!("82bf241958b324ed053555d23315d3cc20935527fc970ff34a9f521a95e302136d"); + + let tag_expected: [u8; 16] = hex!("0eadc8612d5208c491e93005195e9769"); + + type M = modes::DeoxysII; + let cipher = DeoxysII128::new(&key); + let tag: Tag = M::encrypt_inout(&nonce, &aad, buffer.to_in_out_buf(), &cipher.subkeys); + + let ciphertext = buffer.as_ref(); + assert_eq!(ciphertext, ciphertext_expected); + assert_eq!(tag, tag_expected); + + let mut buffer = MockBuffer::from(buffer.as_ref()); + M::decrypt_inout(&nonce, &aad, buffer.to_in_out_buf(), &tag, &cipher.subkeys) + .expect("decryption failed"); + + assert_eq!(&plaintext[..], buffer.as_ref()); + } +} diff --git a/deoxys/src/modes.rs b/deoxys/src/modes.rs index ccb2b117..0f91ec36 100644 --- a/deoxys/src/modes.rs +++ b/deoxys/src/modes.rs @@ -4,6 +4,7 @@ use aead::{ consts::{U8, U15, U16}, }; use core::marker::PhantomData; +use inout::InOutBuf; use subtle::ConstantTimeEq; const TWEAK_AD: u8 = 0x20; @@ -47,7 +48,7 @@ where let mut block = Block::default(); block.copy_from_slice(ad); - B::encrypt_in_place(&mut block, tweak, subkeys); + B::encrypt_inout((&mut block).into(), tweak, subkeys); for (t, b) in tag.iter_mut().zip(block.iter()) { *t ^= b; @@ -61,7 +62,7 @@ where block[ad.len()] = 0x80; - B::encrypt_in_place(&mut block, tweak, subkeys); + B::encrypt_inout((&mut block).into(), tweak, subkeys); for (t, b) in tag.iter_mut().zip(block.iter()) { *t ^= b; @@ -82,15 +83,16 @@ where { type NonceSize = U8; - fn encrypt_in_place( + fn encrypt_inout( nonce: &Array, associated_data: &[u8], - buffer: &mut [u8], + buffer: InOutBuf<'_, '_, u8>, subkeys: &Array, ) -> Tag { let mut tag = Tag::default(); let mut checksum = Checksum::default(); let mut tweak = Tweak::default(); + let buffer_len = buffer.len(); // Associated Data >::compute_ad_tag( @@ -112,66 +114,75 @@ where if !buffer.is_empty() { tweak[0] = (tweak[0] & 0xf) | TWEAK_M; - for (index, data) in buffer.chunks_mut(16).enumerate() { + let (data_blocks, tail) = buffer.into_chunks(); + let data_blocks_len = data_blocks.len(); + + for (index, data) in data_blocks.into_iter().enumerate() { // Copy block number let tmp = tweak[8] & 0xf0; tweak[8..].copy_from_slice(&(index as u64).to_be_bytes()); tweak[8] = (tweak[8] & 0xf) | tmp; - if data.len() == 16 { - for (c, d) in checksum.iter_mut().zip(data.iter()) { - *c ^= d; - } + for (c, d) in checksum.iter_mut().zip(data.get_in().iter()) { + *c ^= d; + } - let data: &mut Block = data.try_into().unwrap(); - B::encrypt_in_place(data, tweak.as_ref(), subkeys); - } else { - // Last block checksum - tweak[0] = (tweak[0] & 0xf) | TWEAK_M_LAST; + B::encrypt_inout(data, &tweak, subkeys); + } - let mut block = Block::default(); - block[0..data.len()].copy_from_slice(data); + let mut data = tail; + let index = data_blocks_len; + if !data.is_empty() { + // Last block, incomplete - block[data.len()] = 0x80; + // Copy block number + let tmp = tweak[8] & 0xf0; + tweak[8..].copy_from_slice(&(index as u64).to_be_bytes()); + tweak[8] = (tweak[8] & 0xf) | tmp; - for (c, d) in checksum.iter_mut().zip(block.iter()) { - *c ^= d; - } + // Last block checksum + tweak[0] = (tweak[0] & 0xf) | TWEAK_M_LAST; - block.fill(0); + let mut block = Block::default(); + block[0..data.len()].copy_from_slice(data.get_in()); - // Last block encryption - B::encrypt_in_place(&mut block, tweak.as_ref(), subkeys); + block[data.len()] = 0x80; - for (d, b) in data.iter_mut().zip(block.iter()) { - *d ^= b; - } + for (c, d) in checksum.iter_mut().zip(block.iter()) { + *c ^= d; + } - // Tag computing. - tweak[0] = (tweak[0] & 0xf) | TWEAK_CHKSUM; + block.fill(0); - let tmp = tweak[8] & 0xf0; - tweak[8..].copy_from_slice(&((index + 1) as u64).to_be_bytes()); - tweak[8] = (tweak[8] & 0xf) | tmp; + // Last block encryption + B::encrypt_inout((&mut block).into(), &tweak, subkeys); - B::encrypt_in_place(&mut checksum, tweak.as_ref(), subkeys); + data.xor_in2out((block[..data.len()]).into()); - for (t, c) in tag.iter_mut().zip(checksum.iter()) { - *t ^= c; - } + // Tag computing. + tweak[0] = (tweak[0] & 0xf) | TWEAK_CHKSUM; + + let tmp = tweak[8] & 0xf0; + tweak[8..].copy_from_slice(&((index + 1) as u64).to_be_bytes()); + tweak[8] = (tweak[8] & 0xf) | tmp; + + B::encrypt_inout((&mut checksum).into(), tweak.as_ref(), subkeys); + + for (t, c) in tag.iter_mut().zip(checksum.iter()) { + *t ^= c; } } } - if buffer.len() % 16 == 0 { + if buffer_len % 16 == 0 { // Tag computing without last block tweak[0] = (tweak[0] & 0xf) | TWEAK_TAG; let tmp = tweak[8] & 0xf0; - tweak[8..].copy_from_slice(&((buffer.len() / 16) as u64).to_be_bytes()); + tweak[8..].copy_from_slice(&((buffer_len / 16) as u64).to_be_bytes()); tweak[8] = (tweak[8] & 0xf) | tmp; - B::encrypt_in_place(&mut checksum, tweak.as_ref(), subkeys); + B::encrypt_inout((&mut checksum).into(), tweak.as_ref(), subkeys); for (t, c) in tag.iter_mut().zip(checksum.iter()) { *t ^= c; @@ -181,16 +192,17 @@ where tag } - fn decrypt_in_place( + fn decrypt_inout( nonce: &Array, associated_data: &[u8], - buffer: &mut [u8], + buffer: InOutBuf<'_, '_, u8>, tag: &Tag, subkeys: &Array, ) -> Result<(), aead::Error> { let mut computed_tag = Tag::default(); let mut checksum = Checksum::default(); let mut tweak = Tweak::default(); + let buffer_len = buffer.len(); // Associated Data >::compute_ad_tag( @@ -212,64 +224,71 @@ where if !buffer.is_empty() { tweak[0] = (tweak[0] & 0xf) | TWEAK_M; - for (index, data) in buffer.chunks_mut(16).enumerate() { + let (data_blocks, tail) = buffer.into_chunks(); + let data_blocks_len = data_blocks.len(); + + for (index, mut data) in data_blocks.into_iter().enumerate() { // Copy block number let tmp = tweak[8] & 0xf0; tweak[8..].copy_from_slice(&(index as u64).to_be_bytes()); tweak[8] = (tweak[8] & 0xf) | tmp; - if data.len() == 16 { - let data: &mut Block = data.try_into().unwrap(); - B::decrypt_in_place(data, tweak.as_ref(), subkeys); + B::decrypt_inout(data.reborrow(), tweak.as_ref(), subkeys); - for (c, d) in checksum.iter_mut().zip(data.iter()) { - *c ^= d; - } - } else { - // Last block checksum - tweak[0] = (tweak[0] & 0xf) | TWEAK_M_LAST; + for (c, d) in checksum.iter_mut().zip(data.get_out().iter()) { + *c ^= d; + } + } - let mut block = Block::default(); - B::encrypt_in_place(&mut block, tweak.as_ref(), subkeys); + let mut data = tail; + let index = data_blocks_len; + if !data.is_empty() { + // Copy block number + let tmp = tweak[8] & 0xf0; + tweak[8..].copy_from_slice(&(index as u64).to_be_bytes()); + tweak[8] = (tweak[8] & 0xf) | tmp; - for (d, b) in data.iter_mut().zip(block.iter()) { - *d ^= b; - } + // Last block checksum + tweak[0] = (tweak[0] & 0xf) | TWEAK_M_LAST; - block.fill(0); + let mut block = Block::default(); + B::encrypt_inout((&mut block).into(), tweak.as_ref(), subkeys); - block[0..data.len()].copy_from_slice(data); - block[data.len()] = 0x80; + data.xor_in2out((block[..data.len()]).into()); - for (c, d) in checksum.iter_mut().zip(block.iter()) { - *c ^= d; - } + block.fill(0); + + block[0..data.len()].copy_from_slice(data.get_out()); + block[data.len()] = 0x80; - // Tag computing. - tweak[0] = (tweak[0] & 0xf) | TWEAK_CHKSUM; + for (c, d) in checksum.iter_mut().zip(block.iter()) { + *c ^= d; + } - let tmp = tweak[8] & 0xf0; - tweak[8..].copy_from_slice(&((index + 1) as u64).to_be_bytes()); - tweak[8] = (tweak[8] & 0xf) | tmp; + // Tag computing. + tweak[0] = (tweak[0] & 0xf) | TWEAK_CHKSUM; - B::encrypt_in_place(&mut checksum, tweak.as_ref(), subkeys); + let tmp = tweak[8] & 0xf0; + tweak[8..].copy_from_slice(&((index + 1) as u64).to_be_bytes()); + tweak[8] = (tweak[8] & 0xf) | tmp; - for (t, c) in computed_tag.iter_mut().zip(checksum.iter()) { - *t ^= c; - } + B::encrypt_inout((&mut checksum).into(), tweak.as_ref(), subkeys); + + for (t, c) in computed_tag.iter_mut().zip(checksum.iter()) { + *t ^= c; } } } - if buffer.len() % 16 == 0 { + if buffer_len % 16 == 0 { // Tag computing without last block tweak[0] = (tweak[0] & 0xf) | TWEAK_TAG; let tmp = tweak[8] & 0xf0; - tweak[8..].copy_from_slice(&((buffer.len() / 16) as u64).to_be_bytes()); + tweak[8..].copy_from_slice(&((buffer_len / 16) as u64).to_be_bytes()); tweak[8] = (tweak[8] & 0xf) | tmp; - B::encrypt_in_place(&mut checksum, tweak.as_ref(), subkeys); + B::encrypt_inout((&mut checksum).into(), tweak.as_ref(), subkeys); for (t, c) in computed_tag.iter_mut().zip(checksum.iter()) { *t ^= c; @@ -306,7 +325,8 @@ where tweak[8..].copy_from_slice(&(index as u64).to_be_bytes()); let mut block = *data; - B::encrypt_in_place(&mut block, tweak, subkeys); + + B::encrypt_inout((&mut block).into(), tweak, subkeys); for (t, b) in tag.iter_mut().zip(block.iter()) { *t ^= b; @@ -330,7 +350,7 @@ where block[data.len()] = 0x80; - B::encrypt_in_place(&mut block, tweak, subkeys); + B::encrypt_inout((&mut block).into(), tweak, subkeys); for (t, b) in tag.iter_mut().zip(block.iter()) { *t ^= b; @@ -338,39 +358,60 @@ where } fn encrypt_decrypt_message( - buffer: &mut [u8], + buffer: InOutBuf<'_, '_, u8>, tweak: &mut Tweak, subkeys: &Array, tag: &Tag, nonce: &Array, ) { - if !buffer.is_empty() { - tweak.copy_from_slice(tag); - tweak[0] |= 0x80; + #[inline] + fn encrypt_decrypt_block( + index: usize, + tweak: &mut Tweak, + subkeys: &Array, + nonce: &Array, + xor: F, + ) { + let index_array = (index as u64).to_be_bytes(); + + // XOR in block numbers + for (t, i) in tweak[8..].iter_mut().zip(&index_array) { + *t ^= i + } - for (index, data) in buffer.chunks_mut(16).enumerate() { - let index_array = (index as u64).to_be_bytes(); + let mut block = Block::default(); + block[1..].copy_from_slice(nonce); - // XOR in block numbers - for (t, i) in tweak[8..].iter_mut().zip(&index_array) { - *t ^= i - } + B::encrypt_inout((&mut block).into(), tweak, subkeys); - let mut block = Block::default(); - block[1..].copy_from_slice(nonce); + xor(&block); - B::encrypt_in_place(&mut block, tweak, subkeys); + // XOR out block numbers + for (t, i) in tweak[8..].iter_mut().zip(&index_array) { + *t ^= i + } + } - for (t, b) in data.iter_mut().zip(block.iter()) { - *t ^= b; - } + if buffer.is_empty() { + return; + } - // XOR out block numbers - for (t, i) in tweak[8..].iter_mut().zip(&index_array) { - *t ^= i - } - } + tweak.copy_from_slice(tag); + tweak[0] |= 0x80; + + let (blocks, tail) = buffer.into_chunks::(); + let blocks_len = blocks.len(); + for (index, mut data) in blocks.into_iter().enumerate() { + encrypt_decrypt_block::(index, tweak, subkeys, nonce, |block| { + data.xor_in2out(block) + }); } + let mut data = tail; + let index = blocks_len; + + encrypt_decrypt_block::(index, tweak, subkeys, nonce, |block| { + data.xor_in2out((block[..data.len()]).into()) + }); } } @@ -380,10 +421,10 @@ where { type NonceSize = U15; - fn encrypt_in_place( + fn encrypt_inout( nonce: &Array, associated_data: &[u8], - buffer: &mut [u8], + buffer: InOutBuf<'_, '_, u8>, subkeys: &Array, ) -> Tag { let mut tag = Tag::default(); @@ -398,11 +439,11 @@ where ); // Message authentication - Self::authenticate_message(buffer, &mut tweak, subkeys, &mut tag); + Self::authenticate_message(buffer.get_in(), &mut tweak, subkeys, &mut tag); tweak[0] = TWEAK_TAG; tweak[1..].copy_from_slice(nonce); - B::encrypt_in_place(&mut tag, &tweak, subkeys); + B::encrypt_inout((&mut tag).into(), &tweak, subkeys); // Message encryption Self::encrypt_decrypt_message(buffer, &mut tweak, subkeys, &tag, nonce); @@ -410,10 +451,10 @@ where tag } - fn decrypt_in_place( + fn decrypt_inout( nonce: &Array, associated_data: &[u8], - buffer: &mut [u8], + mut buffer: InOutBuf<'_, '_, u8>, tag: &Tag, subkeys: &Array, ) -> Result<(), aead::Error> { @@ -429,16 +470,16 @@ where ); // Message decryption - Self::encrypt_decrypt_message(buffer, &mut tweak, subkeys, tag, nonce); + Self::encrypt_decrypt_message(buffer.reborrow(), &mut tweak, subkeys, tag, nonce); tweak.fill(0); // Message authentication - Self::authenticate_message(buffer, &mut tweak, subkeys, &mut computed_tag); + Self::authenticate_message(buffer.get_out(), &mut tweak, subkeys, &mut computed_tag); tweak[0] = TWEAK_TAG; tweak[1..].copy_from_slice(nonce); - B::encrypt_in_place(&mut computed_tag, &tweak, subkeys); + B::encrypt_inout((&mut computed_tag).into(), &tweak, subkeys); if tag.ct_eq(&computed_tag).into() { Ok(())