diff options
author | Igor Matuszewski <igor@sequoia-pgp.org> | 2020-10-05 01:10:30 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2020-12-04 16:10:59 +0100 |
commit | 6de3f7f02ef14d7d84e6d0a7aff6aaee12444392 (patch) | |
tree | fb9544f1e1d54be0bf86ea2a6e4e6edb4b3dac94 | |
parent | 3dacda4a93b1d3b1ef44484ac2691eec0aa759c9 (diff) |
openpgp: Use pure Rust EAX implementation under CNG backend
- Fixes #556.
-rw-r--r-- | Cargo.lock | 86 | ||||
-rw-r--r-- | openpgp/Cargo.toml | 3 | ||||
-rw-r--r-- | openpgp/src/crypto/aead.rs | 28 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/cng/aead.rs | 222 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/cng/symmetric.rs | 180 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/nettle/aead.rs | 3 | ||||
-rw-r--r-- | openpgp/src/packet/skesk.rs | 10 |
7 files changed, 193 insertions, 339 deletions
@@ -16,6 +16,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" [[package]] +name = "aead" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] name = "ahash" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -208,6 +217,15 @@ dependencies = [ ] [[package]] +name = "block-cipher" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f337a3e6da609650eb74e02bc9fac7b735049f7623ab12f2e4c719316fcc7e80" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] name = "block-padding" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -379,6 +397,16 @@ dependencies = [ ] [[package]] +name = "cmac" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5220604fe5c112e2851b00da795c72cbb71bf112f2cbd532bdcfb4106eeb320b" +dependencies = [ + "crypto-mac", + "dbl", +] + +[[package]] name = "colored" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -460,6 +488,17 @@ dependencies = [ ] [[package]] +name = "crypto-mac" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca" +dependencies = [ + "block-cipher", + "generic-array 0.14.4", + "subtle", +] + +[[package]] name = "csv" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -492,6 +531,15 @@ dependencies = [ ] [[package]] +name = "ctr" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc03dee3a2843ac6eb4b5fb39cfcf4cb034d078555d1f4a0afbed418b822f3c2" +dependencies = [ + "stream-cipher", +] + +[[package]] name = "curve25519-dalek" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -505,6 +553,15 @@ dependencies = [ ] [[package]] +name = "dbl" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2735145c3b9ba15f2d7a3ae8cdafcbc8c98a7bef7f62afe9d08bd99fbf7130de" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] name = "diff" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -591,6 +648,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c53dc3a653e0f64081026e4bf048d48fec9fce90c66e8326ca7292df0ff2d82" [[package]] +name = "eax" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f8b7fcdee0a9cc0d80bb9547c4298efddced5744be0018aad97133efeda6474" +dependencies = [ + "aead", + "block-cipher", + "cmac", + "ctr", + "subtle", +] + +[[package]] name = "ed25519" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1894,6 +1964,7 @@ dependencies = [ "bzip2", "chrono", "dyn-clone", + "eax", "ed25519-dalek", "flate2", "idna", @@ -2119,6 +2190,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] +name = "stream-cipher" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c80e15f898d8d8f25db24c253ea615cc14acf418ff307822995814e7d42cfa89" +dependencies = [ + "block-cipher", + "generic-array 0.14.4", +] + +[[package]] name = "string_cache" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2470,10 +2551,11 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "win-crypto-ng" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436ccff9c9ed3aee7ef0224a4944e103d27cba7add587fbdf61bd745288f6029" +checksum = "46f32a81d56493638a7f2d1f63e927f6ca706072b3b6c818a8cb071d65a6c9d8" dependencies = [ + "block-cipher", "doc-comment", "rand_core", "winapi 0.3.9", diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml index 4b1c9463..7edae719 100644 --- a/openpgp/Cargo.toml +++ b/openpgp/Cargo.toml @@ -44,10 +44,11 @@ backtrace = "0.3.46" unicode-normalization = "0.1.9" [target.'cfg(windows)'.dependencies] -win-crypto-ng = { version = "0.2", features = ["rand"], optional = true } +win-crypto-ng = { version = "0.3", features = ["rand", "block-cipher"], optional = true } num-bigint-dig = { version = "0.6", default-features = false, optional = true } ed25519-dalek = { version = "1", default-features = false, features = ["rand", "u64_backend"], optional = true } winapi = { version = "0.3", default-features = false, features = ["bcrypt"], optional = true } +eax = "0.2" [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] chrono = { version = "0.4", default-features = false, features = ["std"] } diff --git a/openpgp/src/crypto/aead.rs b/openpgp/src/crypto/aead.rs index 17153aa1..ac658046 100644 --- a/openpgp/src/crypto/aead.rs +++ b/openpgp/src/crypto/aead.rs @@ -59,6 +59,14 @@ pub trait Aead : seal::Sealed { fn digest_size(&self) -> usize; } +/// Whether AEAD cipher is used for data encryption or decryption. +pub(crate) enum CipherOp { + /// Cipher is used for data encryption. + Encrypt, + /// Cipher is used for data decryption. + Decrypt, +} + impl AEADAlgorithm { /// Returns the digest size of the AEAD algorithm. pub fn digest_size(&self) -> Result<usize> { @@ -168,7 +176,7 @@ impl<'a> Decryptor<'a> { } } - fn make_aead(&mut self) -> Result<Box<dyn Aead>> { + fn make_aead(&mut self, op: CipherOp) -> Result<Box<dyn Aead>> { // The chunk index is XORed into the IV. let chunk_index: [u8; 8] = self.chunk_index.to_be_bytes(); @@ -189,7 +197,7 @@ impl<'a> Decryptor<'a> { } // Instantiate the AEAD cipher. - let aead = self.aead.context(self.sym_algo, &self.key, &self.iv)?; + let aead = self.aead.context(self.sym_algo, &self.key, &self.iv, op)?; // Restore the IV. for (i, o) in &mut self.iv[iv_len - 8..].iter_mut() @@ -265,7 +273,7 @@ impl<'a> Decryptor<'a> { let final_digest_size = self.digest_size; for _ in 0..n_chunks { - let mut aead = self.make_aead()?; + let mut aead = self.make_aead(CipherOp::Decrypt)?; // Digest the associated data. self.hash_associated_data(&mut aead, false); @@ -356,7 +364,7 @@ impl<'a> Decryptor<'a> { if check_final_tag { // We read the whole ciphertext, now check the final digest. - let mut aead = self.make_aead()?; + let mut aead = self.make_aead(CipherOp::Decrypt)?; self.hash_associated_data(&mut aead, true); aead.digest(&mut digest); @@ -583,7 +591,7 @@ impl<W: io::Write> Encryptor<W> { } } - fn make_aead(&mut self) -> Result<Box<dyn Aead>> { + fn make_aead(&mut self, op: CipherOp) -> Result<Box<dyn Aead>> { // The chunk index is XORed into the IV. let chunk_index: [u8; 8] = self.chunk_index.to_be_bytes(); @@ -604,7 +612,7 @@ impl<W: io::Write> Encryptor<W> { } // Instantiate the AEAD cipher. - let aead = self.aead.context(self.sym_algo, &self.key, &self.iv)?; + let aead = self.aead.context(self.sym_algo, &self.key, &self.iv, op)?; // Restore the IV. for (i, o) in &mut self.iv[iv_len - 8..].iter_mut() @@ -636,7 +644,7 @@ impl<W: io::Write> Encryptor<W> { // And possibly encrypt the chunk. if self.buffer.len() == self.chunk_size { - let mut aead = self.make_aead()?; + let mut aead = self.make_aead(CipherOp::Encrypt)?; self.hash_associated_data(&mut aead, false); let inner = self.inner.as_mut().unwrap(); @@ -658,7 +666,7 @@ impl<W: io::Write> Encryptor<W> { for chunk in buf.chunks(self.chunk_size) { if chunk.len() == self.chunk_size { // Complete chunk. - let mut aead = self.make_aead()?; + let mut aead = self.make_aead(CipherOp::Encrypt)?; self.hash_associated_data(&mut aead, false); let inner = self.inner.as_mut().unwrap(); @@ -686,7 +694,7 @@ impl<W: io::Write> Encryptor<W> { pub fn finish(&mut self) -> Result<W> { if let Some(mut inner) = self.inner.take() { if self.buffer.len() > 0 { - let mut aead = self.make_aead()?; + let mut aead = self.make_aead(CipherOp::Encrypt)?; self.hash_associated_data(&mut aead, false); // Encrypt the chunk. @@ -704,7 +712,7 @@ impl<W: io::Write> Encryptor<W> { } // Write final digest. - let mut aead = self.make_aead()?; + let mut aead = self.make_aead(CipherOp::Decrypt)?; self.hash_associated_data(&mut aead, true); aead.digest(&mut self.scratch[..self.digest_size]); inner.write_all(&self.scratch[..self.digest_size])?; diff --git a/openpgp/src/crypto/backend/cng/aead.rs b/openpgp/src/crypto/backend/cng/aead.rs index d30073ca..ff302dad 100644 --- a/openpgp/src/crypto/backend/cng/aead.rs +++ b/openpgp/src/crypto/backend/cng/aead.rs @@ -1,16 +1,22 @@ //! Implementation of AEAD using Windows CNG API. use crate::{Error, Result}; -use crate::crypto::aead::Aead; +use crate::crypto::aead::{Aead, CipherOp}; use crate::seal; use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; -use super::symmetric::Ctr; -use win_crypto_ng::hash::{Hash, HashAlgorithm, MacAlgorithmId}; -use win_crypto_ng::symmetric::SymmetricAlgorithmId; +use eax::online::{EaxOnline, Encrypt, Decrypt}; +use win_crypto_ng::symmetric::{BlockCipherKey, Aes}; +use win_crypto_ng::symmetric::block_cipher::generic_array::{GenericArray, ArrayLength}; +use win_crypto_ng::symmetric::block_cipher::generic_array::typenum::{U128, U192, U256}; -const EAX_BLOCK_SIZE: usize = 16; -const EAX_DIGEST_SIZE: usize = 16; +trait GenericArrayExt { + const LEN: usize; +} + +impl<T, N: ArrayLength<T>> GenericArrayExt for GenericArray<T, N> { + const LEN: usize = N::USIZE; +} impl AEADAlgorithm { pub(crate) fn context( @@ -18,14 +24,40 @@ impl AEADAlgorithm { sym_algo: SymmetricAlgorithm, key: &[u8], nonce: &[u8], + op: CipherOp, ) -> Result<Box<dyn Aead>> { + + let nonce = GenericArray::from_slice(nonce); + match self { AEADAlgorithm::EAX => match sym_algo { - | SymmetricAlgorithm::AES128 - | SymmetricAlgorithm::AES192 - | SymmetricAlgorithm::AES256 => { - Ok(Box::new(EaxAes::with_key_and_nonce(key, nonce)?)) - }, + | SymmetricAlgorithm::AES128 => { + let key = GenericArray::from_slice(key); + Ok(match op { + CipherOp::Encrypt => + Box::new(EaxOnline::<BlockCipherKey<Aes, U128>, Encrypt>::with_key_and_nonce(key, nonce)), + CipherOp::Decrypt => + Box::new(EaxOnline::<BlockCipherKey<Aes, U128>, Decrypt>::with_key_and_nonce(key, nonce)), + }) + } + SymmetricAlgorithm::AES192 => { + let key = GenericArray::from_slice(key); + Ok(match op { + CipherOp::Encrypt => + Box::new(EaxOnline::<BlockCipherKey<Aes, U192>, Encrypt>::with_key_and_nonce(key, nonce)), + CipherOp::Decrypt => + Box::new(EaxOnline::<BlockCipherKey<Aes, U192>, Decrypt>::with_key_and_nonce(key, nonce)), + }) + } + SymmetricAlgorithm::AES256 => { + let key = GenericArray::from_slice(key); + Ok(match op { + CipherOp::Encrypt => + Box::new(EaxOnline::<BlockCipherKey<Aes, U256>, Encrypt>::with_key_and_nonce(key, nonce)), + CipherOp::Decrypt => + Box::new(EaxOnline::<BlockCipherKey<Aes, U256>, Decrypt>::with_key_and_nonce(key, nonce)), + }) + } _ => Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), }, _ => Err(Error::UnsupportedAEADAlgorithm(self.clone()).into()), @@ -33,135 +65,47 @@ impl AEADAlgorithm { } } -/// EAX-AES mode. -/// -/// See https://web.cs.ucdavis.edu/~rogaway/papers/eax.pdf. -struct EaxAes { - omac_nonce: Vec<u8>, - omac_data: Hash, - omac_msg: Hash, - ctr: Ctr, -} - -impl EaxAes { - fn with_key_and_nonce(key: &[u8], nonce: &[u8]) -> Result<Self> { - fn omac_init_with_iv( - prov: &HashAlgorithm<MacAlgorithmId>, - key: &[u8], - iv: u8, - ) -> Result<Hash> { - let mut omac = prov.new_mac(key, None)?; - // Prepend the IV - omac.hash(&[0; EAX_BLOCK_SIZE - 1])?; - omac.hash(&[iv])?; - Ok(omac) +macro_rules! impl_aead { + ($($type: ty),*) => { + $( + impl Aead for EaxOnline<$type, Encrypt> { + fn update(&mut self, ad: &[u8]) { self.update_assoc(ad) } + fn digest_size(&self) -> usize { <eax::Tag as GenericArrayExt>::LEN } + fn digest(&mut self, digest: &mut [u8]) { + let tag = self.tag_clone(); + digest[..tag.len()].copy_from_slice(&tag[..]); + } + fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) { + let len = core::cmp::min(dst.len(), src.len()); + dst[..len].copy_from_slice(&src[..len]); + EaxOnline::<$type, Encrypt>::encrypt(self, &mut dst[..len]) + } + fn decrypt(&mut self, _dst: &mut [u8], _src: &[u8]) { + panic!("AEAD decryption called in the encryption context") + } } - - let provider = HashAlgorithm::open(MacAlgorithmId::AesCmac)?; - // N ← OMAC_0^K(N) - let mut omac_nonce = omac_init_with_iv(&provider, key, 0)?; - omac_nonce.hash(nonce)?; - let omac_nonce = omac_nonce.finish()?.into_inner(); - // H ← OMAC_1^K(H), init with 1 but hash online associated data later - let omac_data = omac_init_with_iv(&provider, key, 1)?; - // C ← OMAC_2^K(C), init with 2 but hash online resulting ciphertext later - let omac_msg = omac_init_with_iv(&provider, key, 2)?; - - let ctr = Ctr::with_cipher_and_iv(SymmetricAlgorithmId::Aes, key, &omac_nonce)?; - - Ok(EaxAes { omac_nonce, omac_data, omac_msg, ctr }) - } -} - - -impl seal::Sealed for EaxAes {} -impl Aead for EaxAes { - /// Adds associated data `ad`. - fn update(&mut self, ad: &[u8]) { - let _ = self.omac_data.hash(ad); - } - - /// Encrypts one block `src` to `dst`. - fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) { - let _ = Ctr::encrypt(&mut self.ctr, dst, src); - let _ = self.omac_msg.hash(dst); - } - /// Decrypts one block `src` to `dst`. - fn decrypt(&mut self, dst: &mut [u8], src: &[u8]) { - let _ = self.omac_msg.hash(src); - let _ = Ctr::decrypt(&mut self.ctr, dst, src); - } - - /// Produce the digest. - fn digest(&mut self, digest: &mut [u8]) { - // TODO: It'd be great if wouldn't have to clone - let omac_data = self.omac_data.clone().finish().unwrap().into_inner(); - let omac_msg = self.omac_msg.clone().finish().unwrap().into_inner(); - - let (nonce, data, msg) = (self.omac_nonce.iter(), omac_data.iter(), omac_msg.iter()); - for (((out, n), d), m) in digest.iter_mut().zip(nonce).zip(data).zip(msg) { - *out = n ^ d ^ m; + impl seal::Sealed for EaxOnline<$type, Encrypt> {} + )* + $( + impl Aead for EaxOnline<$type, Decrypt> { + fn update(&mut self, ad: &[u8]) { self.update_assoc(ad) } + fn digest_size(&self) -> usize { <eax::Tag as GenericArrayExt>::LEN } + fn digest(&mut self, digest: &mut [u8]) { + let tag = self.tag_clone(); + digest[..tag.len()].copy_from_slice(&tag[..]); + } + fn encrypt(&mut self, _dst: &mut [u8], _src: &[u8]) { + panic!("AEAD encryption called in the decryption context") + } + fn decrypt(&mut self, dst: &mut [u8], src: &[u8]) { + let len = core::cmp::min(dst.len(), src.len()); + dst[..len].copy_from_slice(&src[..len]); + self.decrypt_unauthenticated_hazmat(&mut dst[..len]) + } } - } - - /// Length of the digest in bytes. - fn digest_size(&self) -> usize { EAX_DIGEST_SIZE } + impl seal::Sealed for EaxOnline<$type, Decrypt> {} + )* + }; } - -#[cfg(test)] -mod tests { - use super::*; - use crate::crypto::aead::Aead; - - trait HexSlice: std::borrow::Borrow<str> { - fn as_hex(&self) -> Vec<u8> { - let res: Vec<u8> = self.borrow().as_bytes().rchunks(2) - .map(|slice| std::str::from_utf8(slice).unwrap()) - .map(|chr| u8::from_str_radix(chr, 16).unwrap()) - .rev() - .collect(); - res - } - } - impl<'a> HexSlice for &'a str {} - - #[test] - fn eax() { - // Test vectors from https://web.cs.ucdavis.edu/~rogaway/papers/eax.pdf - let mut eax = EaxAes::with_key_and_nonce( - &"233952DEE4D5ED5F9B9C6D6FF80FF478".as_hex(), - &"62EC67F9C3A4A407FCB2A8C49031A8B3".as_hex() - ).unwrap(); - eax.update(&"6BFB914FD07EAE6B".as_hex()); - let mut digest = [0; 16]; - eax.digest(&mut digest); - assert_eq!(&digest, &*"E037830E8389F27B025A2D6527E79D01".as_hex()); - - let mut eax = EaxAes::with_key_and_nonce( - &"91945D3F4DCBEE0BF45EF52255F095A4".as_hex(), - &"BECAF043B0A23D843194BA972C66DEBD".as_hex() - ).unwrap(); - eax.update(&"FA3BFD4806EB53FA".as_hex()); - let mut out = [0; 2]; - eax.encrypt(&mut out, &"F7FB".as_hex()); - let mut digest = [0; 16]; - eax.digest(&mut digest); - let output = [&out[..], &digest[..]].concat(); - assert_eq!(output, &*"19DD5C4C9331049D0BDAB0277408F67967E5".as_hex()); - - let mut eax = EaxAes::with_key_and_nonce( - &"8395FCF1E95BEBD697BD010BC766AAC3".as_hex(), - &"22E7ADD93CFC6393C57EC0B3C17D6B44".as_hex() - ).unwrap(); - eax.update(&"126735FCC320D25A".as_hex()); - let mut out1 = [0; 16]; - eax.encrypt(&mut out1, &"CA40D7446E545FFAED3BD12A740A659F".as_hex()); - let mut out2 = [0; 5]; - eax.encrypt(&mut out2, &"FBBB3CEAB7".as_hex()); - let mut digest = [0; 16]; - eax.digest(&mut digest); - let output = [&out1[..], &out2[..], &digest[..]].concat(); - assert_eq!(output, &*"CB8920F87A6C75CFF39627B56E3ED197C552D295A7CFC46AFC253B4652B1AF3795B124AB6E".as_hex()); - } -} +impl_aead!(BlockCipherKey<Aes, U128>, BlockCipherKey<Aes, U192>, BlockCipherKey<Aes, U256>); diff --git a/openpgp/src/crypto/backend/cng/symmetric.rs b/openpgp/src/crypto/backend/cng/symmetric.rs index a4a140eb..7d8281e7 100644 --- a/openpgp/src/crypto/backend/cng/symmetric.rs +++ b/openpgp/src/crypto/backend/cng/symmetric.rs @@ -68,74 +68,6 @@ impl Mode for cng::SymmetricAlgorithmKey { } } -/// CTR mode using a block cipher. CNG doesn't implement one so roll our own -/// using CNG's ECB mode. -pub struct Ctr { - key: cng::SymmetricAlgorithmKey, - ctr: Option<Box<[u8]>>, -} - -impl Ctr { - pub fn with_cipher_and_iv(algo: cng::SymmetricAlgorithmId, key: &[u8], iv: &[u8]) -> Result<Ctr> { - let algo = cng::SymmetricAlgorithm::open(algo, cng::ChainingMode::Ecb)?; - let key = algo.new_key(key)?; - // TODO: Check iv len - Ok(Ctr { key, ctr: Some(iv.into()) }) - } - - pub fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { - let mut ctr = self.ctr.take().unwrap(); - Mode::encrypt(self, &mut ctr, dst, src)?; - self.ctr = Some(ctr); - Ok(()) - } - - pub fn decrypt(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { - let mut ctr = self.ctr.take().unwrap(); - Mode::decrypt(self, &mut ctr, dst, src)?; - self.ctr = Some(ctr); - Ok(()) - } -} - -impl Mode for Ctr { - fn block_size(&self) -> usize { - self.key.block_size().expect("CNG not to fail internally") - } - - fn encrypt(&mut self, iv: &mut [u8], dst: &mut [u8], src: &[u8]) -> Result<()> { - let block = self.block_size(); - - // Ciphertext_i <- BlockCipher(Counter_i) ⊕ Plaintext_i - for (dst, src) in dst.chunks_mut(block).zip(src.chunks(block)) { - let res = cng::SymmetricAlgorithmKey::encrypt(&self.key, None, iv, None)?; - wrapping_increment_be(iv.as_mut()); - - for (dst, (res, src)) in dst.iter_mut().zip(res.as_slice().iter().zip(src.iter())) { - *dst = res ^ src; - } - } - - Ok(()) - } - - fn decrypt(&mut self, iv: &mut [u8], dst: &mut [u8], src: &[u8]) -> Result<()> { - let block = self.block_size(); - - // Plaintext_i <- BlockCipher(Counter_i) ⊕ Ciphertext_i - for (dst, src) in dst.chunks_mut(block).zip(src.chunks(block)) { - let res = cng::SymmetricAlgorithmKey::encrypt(&self.key, None, iv, None)?; - wrapping_increment_be(iv.as_mut()); - - for (dst, (res, src)) in dst.iter_mut().zip(res.as_slice().iter().zip(src.iter())) { - *dst = res ^ src; - } - } - - Ok(()) - } -} - #[derive(Debug, thiserror::Error)] #[error("Unsupported algorithm: {0}")] pub struct UnsupportedAlgorithm(SymmetricAlgorithm); @@ -253,115 +185,3 @@ impl SymmetricAlgorithm { )) } } - -fn wrapping_increment_be(value: &mut [u8]) -> &mut [u8] { - for val in value.iter_mut().rev() { - *val = val.wrapping_add(1); - // Stop carryover - if *val != 0x00 { - break; - } - } - - value -} - -#[cfg(test)] -mod tests { - use super::*; - use std::borrow::Borrow; - - trait HexSlice: Borrow<str> { - fn as_hex(&self) -> Vec<u8> { - let res: Vec<u8> = self.borrow().as_bytes().rchunks(2) - .map(|slice| std::str::from_utf8(slice).unwrap()) - .map(|chr| u8::from_str_radix(chr, 16).unwrap()) - .rev() - .collect(); - res - } - } - impl<'a> HexSlice for &'a str {} - - #[test] - fn hex_slice() { - assert_eq!("0a".as_hex(), &[0x0A]); - assert_eq!("a".as_hex(), &[0x0A]); - assert_eq!("FE0a".as_hex(), &[0xFE, 0x0A]); - assert_eq!("E0a".as_hex(), &[0x0E, 0x0A]); - } - - #[test] - fn wrapping_increment_be() { - assert_eq!(super::wrapping_increment_be(&mut [0x00]), [0x01]); - assert_eq!(super::wrapping_increment_be(&mut [0xFF, 0xFF]), [0x00, 0x00]); - assert_eq!(super::wrapping_increment_be(&mut [0xFD, 0xFF]), [0xFE, 0x00]); - - let input = &mut "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff".as_hex(); - let expected = &"f0f1f2f3f4f5f6f7f8f9fafbfcfdff00".as_hex(); - - assert_eq!(super::wrapping_increment_be(input).as_ref(), expected.as_slice()); - } - - #[test] - fn ctr_aes_128() { - // NIST SP800-38a test vectors - // F.5.1 CTR-AES128.Encrypt - let key = &"2b7e151628aed2a6abf7158809cf4f3c".as_hex(); - assert_eq!(key.len(), 16); - let mut ctr = Ctr::with_cipher_and_iv( - cng::SymmetricAlgorithmId::Aes, - key, - &"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff".as_hex(), - ).unwrap(); - - let plain = &"6bc1bee22e409f96e93d7e117393172a".as_hex(); - let mut cipher = vec![0u8; 16]; - ctr.encrypt(&mut cipher, plain).unwrap(); - assert_eq!(&cipher, &"874d6191b620e3261bef6864990db6ce".as_hex()); - - let plain = &"ae2d8a571e03ac9c9eb76fac45af8e51".as_hex(); - let mut cipher = vec![0u8; 16]; - ctr.encrypt(&mut cipher, plain).unwrap(); - assert_eq!(&cipher, &"9806f66b7970fdff8617187bb9fffdff".as_hex()); - - let plain = &"30c81c46a35ce411e5fbc1191a0a52ef".as_hex(); - let mut cipher = vec![0u8; 16]; - ctr.encrypt(&mut cipher, plain).unwrap(); - assert_eq!(&cipher, &"5ae4df3edbd5d35e5b4f09020db03eab".as_hex()); - - let plain = &"f69f2445df4f9b17ad2b417be66c3710".as_hex(); - let mut cipher = vec![0u8; 16]; - ctr.encrypt(&mut cipher, plain).unwrap(); - assert_eq!(&cipher, &"1e031dda2fbe03d1792170a0f3009cee".as_hex()); - - // F.5.2 CTR-AES128.Decrypt - let key = &"2b7e151628aed2a6abf7158809cf4f3c".as_hex(); - assert_eq!(key.len(), 16); - let mut ctr = Ctr::with_cipher_and_iv( - cng::SymmetricAlgorithmId::Aes, - key, - &"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff".as_hex(), - ).unwrap(); - |