From 0b0621406f90d00deddf6269624ac7040d21bcb1 Mon Sep 17 00:00:00 2001 From: David Hook Date: Mon, 7 Sep 2026 09:22:01 +1000 Subject: [PATCH 01/14] aes-lowmemory: the AES_CBC_* and AES_ECB_* aliases take a padding scheme as well as a direction, via a PaddedMode projection, so the two block modes name their padding in the type --- alpha_0.1.3_release_notes.md | 8 +- crypto/aes-lowmemory/Cargo.toml | 2 + crypto/aes-lowmemory/src/cbc.rs | 210 ++++++++++++++---- crypto/aes-lowmemory/src/ecb.rs | 169 +++++++++----- crypto/aes-lowmemory/src/lib.rs | 51 +++-- crypto/aes-lowmemory/src/padded_mode.rs | 64 ++++++ crypto/aes-lowmemory/tests/cbc_alias_tests.rs | 136 ++++++++++++ crypto/aes-lowmemory/tests/ecb_alias_tests.rs | 110 +++++++++ crypto/modes/src/lib.rs | 4 +- 9 files changed, 630 insertions(+), 124 deletions(-) create mode 100644 crypto/aes-lowmemory/src/padded_mode.rs create mode 100644 crypto/aes-lowmemory/tests/cbc_alias_tests.rs create mode 100644 crypto/aes-lowmemory/tests/ecb_alias_tests.rs diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index 099ee868..d091b992 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -39,7 +39,13 @@ permutation (NIST FIPS 197), re-exported from the umbrella crate. `AES_CFB_192` / `AES_CFB_256`, `AES_CFB8_128` / `AES_CFB8_192` / `AES_CFB8_256`, `AES_CTR_128` / `AES_CTR_192` / `AES_CTR_256` (12-byte nonce, 4-byte counter) and `AES_ECB_128` / `AES_ECB_192` / `AES_ECB_256`, which fill in the - const parameters of `bouncycastle-modes`' `Cbc`, `Cfb`, `Cfb8`, `Ctr` and `Ecb` and leave the direction as the type parameter. They are aliases only -- no new engine + const parameters of `bouncycastle-modes`' `Cbc`, `Cfb`, `Cfb8`, `Ctr` and `Ecb`. The three stream + modes leave the direction as the only type parameter; the two **block** modes, CBC and ECB, take + a padding scheme as well -- `AES_CBC_128` -- because neither is defined on data + that is not a whole number of blocks, so the scheme is a choice the caller has to make and one + both ends must agree on. Naming it in the type makes a mismatched pair a compile error instead of + a decryption that returns plausible rubbish. `PaddedMode` is the projection that lets a single + alias carry both parameters, `PaddedEncryptor` and `PaddedDecryptor` being distinct types. They are aliases only -- no new engine code, and each one's doctest round-trips and shows that a misaligned length fails to compile. New crate `bouncycastle-modes` (`bouncycastle::modes`): cipher modes of operation diff --git a/crypto/aes-lowmemory/Cargo.toml b/crypto/aes-lowmemory/Cargo.toml index f6cbff4d..c0afefae 100644 --- a/crypto/aes-lowmemory/Cargo.toml +++ b/crypto/aes-lowmemory/Cargo.toml @@ -8,6 +8,8 @@ bouncycastle-core.workspace = true bouncycastle-utils.workspace = true # Only for the AES-CBC type aliases in `cbc.rs`; the engine itself does not use it. bouncycastle-modes.workspace = true +# Only for the padded AES-CBC aliases in `cbc.rs`; the engine itself does not use it. +bouncycastle-padding.workspace = true [dev-dependencies] bouncycastle-core-test-framework.workspace = true diff --git a/crypto/aes-lowmemory/src/cbc.rs b/crypto/aes-lowmemory/src/cbc.rs index d68f6e2a..337f9ee1 100644 --- a/crypto/aes-lowmemory/src/cbc.rs +++ b/crypto/aes-lowmemory/src/cbc.rs @@ -1,93 +1,207 @@ -//! Type aliases for AES in CBC mode (NIST SP 800-38A Sec 6.2). +//! Type aliases for AES in CBC mode (NIST SP 800-38A Sec 6.2), with padding. //! //! `bouncycastle-modes` is deliberately cipher-agnostic, so `Cbc` takes the permutation, the -//! direction, and the `KEY_LEN` / `BLOCK_LEN` const parameters. These aliases pin the AES values so -//! callers never spell them out. They add nothing to the engine: the permutation still implements -//! none of the data-encryption traits itself (see the crate docs), the mode does. +//! direction, and the `KEY_LEN` / `BLOCK_LEN` const parameters, and `bouncycastle-padding`'s +//! adapters take five more. These aliases pin all of them except the two choices a caller actually +//! makes: the direction and the padding scheme. They add nothing to the engine -- the permutation +//! still implements none of the data-encryption traits itself (see the crate docs), the mode does. +//! +//! ```text +//! AES_CBC_128 // AES-128, CBC, PKCS#7 padded, encrypting +//! AES_CBC_256 +//! ``` +//! +//! # Why the padding is part of the alias +//! +//! CBC is defined only on whole blocks (SP 800-38A Sec 5.2), and the recommendation puts the +//! formatting of anything else outside its scope (Appendix A). So CBC on real data is always CBC +//! *plus a padding scheme*, and the scheme is not an implementation detail: it changes the +//! ciphertext, and both ends must agree on it. Naming it in the type makes that choice explicit at +//! every use, and makes a mismatched pair a compile error rather than a decryption that returns +//! plausible-looking rubbish. +//! +//! The two schemes `bouncycastle-padding` provides are [`PKCS7`], which is what almost everyone +//! means by "padded CBC" (RFC 5652 s. 6.3), and [`NoPadding`], which adds nothing and instead +//! *rejects* a message that is not a whole number of blocks -- useful for formats already defined +//! on block boundaries, where silently padding would be wrong. +//! +//! # These are the arbitrary-length API +//! +//! A padded alias implements [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`], not the +//! block traits: `encrypt_out` / `decrypt_out` and the streaming `do_update_out` / `do_final`, all +//! taking a `&[u8]` of any length. The block-aligned API, with its compile-time length checks and +//! its in-place data methods, is `bouncycastle_modes::Cbc` itself, which these wrap: +//! +//! ```text +//! bouncycastle_modes::Cbc // block-aligned, in place +//! AES_CBC_128 // any length, padded +//! ``` +//! +//! # How one alias covers both directions +//! +//! `PaddedEncryptor` and `PaddedDecryptor` are two distinct types, so a plain type alias cannot +//! select between them on a `Dir` parameter. [`PaddedMode`] does it instead: it is implemented for +//! each direction marker and projects to the right adapter, and the aliases are written as that +//! projection. The only visible consequence is that `Dir` must be +//! [`Encrypting`](bouncycastle_modes::Encrypting) or +//! [`Decrypting`](bouncycastle_modes::Decrypting), which was already true. +use crate::padded_mode::PaddedMode; use crate::{Aes128, Aes192, Aes256, BLOCK_LEN}; -use bouncycastle_modes::Cbc; +use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; + +// Imports needed for docs +#[allow(unused_imports)] +use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +#[allow(unused_imports)] +use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; +// end of imports needed for docs -/// AES-128 in CBC mode. `Dir` is [`bouncycastle_modes::Encrypting`] or -/// [`bouncycastle_modes::Decrypting`]; the wrong direction is a compile error, not a runtime check. +/// AES-128 in CBC mode with a padding scheme. /// -/// The IV is generated by encryption and returned; it is never supplied. Encryption and decryption -/// work in place. +/// `Dir` is [`Encrypting`] or [`Decrypting`] and `Pad` is [`PKCS7`] or [`NoPadding`]; the wrong +/// direction is a compile error, not a runtime check. The IV is generated by encryption and +/// returned; it is never supplied. /// /// ``` /// use bouncycastle_aes_lowmemory::AES_CBC_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; +/// use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; +/// use bouncycastle_padding::PKCS7; +/// +/// type Enc = AES_CBC_128; +/// type Dec = AES_CBC_128; /// /// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) /// .expect("a 16-byte symmetric cipher key"); -/// // 48 bytes: three whole blocks. The length is checked at compile time. -/// let message = [0u8; 48]; -/// let mut data = message; -/// let iv = AES_CBC_128::::encrypt(&key, &mut data).unwrap(); -/// assert_ne!(data, message); -/// AES_CBC_128::::decrypt(&key, &iv, &mut data).unwrap(); -/// assert_eq!(data, message); -/// -/// // Streaming, a few blocks at a time: -/// let (mut enc, iv) = AES_CBC_128::::do_encrypt_init(&key).unwrap(); -/// let mut first = [0u8; 16]; -/// let mut rest = [1u8; 32]; -/// enc.do_encrypt(&mut first).unwrap(); -/// enc.do_encrypt(&mut rest).unwrap(); -/// let mut dec = AES_CBC_128::::do_decrypt_init(&key, &iv).unwrap(); -/// dec.do_decrypt(&mut first).unwrap(); -/// dec.do_decrypt(&mut rest).unwrap(); -/// assert_eq!(first, [0u8; 16]); -/// assert_eq!(rest, [1u8; 32]); +/// +/// // 5 bytes: PKCS#7 pads it to one block, so the padding does the work CBC cannot. +/// let message = b"hello"; +/// let mut ciphertext = [0u8; 16]; +/// let (iv, written) = Enc::encrypt_out(&key, message, &mut ciphertext).expect("encryption"); +/// assert_eq!(written, 16); +/// +/// let mut plaintext = [0u8; 16]; +/// let n = Dec::decrypt_out(&key, &iv, &ciphertext, &mut plaintext).expect("decryption"); +/// assert_eq!(&plaintext[..n], message); +/// ``` +/// +/// With [`NoPadding`] nothing is added, and a message that is not a whole number of blocks is an +/// error at `do_final` rather than something silently padded: +/// +/// ``` +/// use bouncycastle_aes_lowmemory::AES_CBC_128; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::traits::SymmetricCipherEncryptor; +/// use bouncycastle_modes::Encrypting; +/// use bouncycastle_padding::NoPadding; +/// +/// type Enc = AES_CBC_128; +/// +/// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +/// +/// // A whole block is fine, and comes out the same length. +/// let mut out = [0u8; 16]; +/// let (_iv, written) = Enc::encrypt_out(&key, &[0u8; 16], &mut out).expect("aligned"); +/// assert_eq!(written, 16); +/// +/// // Five bytes is not, and is refused rather than padded. +/// let mut out = [0u8; 16]; +/// assert!(Enc::encrypt_out(&key, b"hello", &mut out).is_err()); /// ``` /// -/// A length that is not a whole number of blocks is a **compile** error, not a runtime one: +/// The padding scheme is part of the type, so the two schemes are different types and cannot be +/// interchanged. A value built with one will not satisfy a binding annotated with the other: /// /// ```compile_fail /// use bouncycastle_aes_lowmemory::AES_CBC_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::BlockCipherEncryptor; +/// use bouncycastle_core::traits::SymmetricCipherEncryptor; /// use bouncycastle_modes::Encrypting; +/// use bouncycastle_padding::{NoPadding, PKCS7}; /// /// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); -/// // 47 bytes is not a multiple of 16: the inline const assertion in `encrypt` fails to compile. -/// let _ = AES_CBC_128::::encrypt(&key, &mut [0u8; 47]); +/// +/// // Built as NoPadding, annotated as PKCS7: mismatched types. +/// let (enc, _iv) = AES_CBC_128::::do_encrypt_init(&key).unwrap(); +/// let _mismatched: AES_CBC_128 = enc; +/// ``` +/// +/// The same code with the annotation corrected does compile, which is what makes the failure above +/// meaningful rather than incidental: +/// +/// ``` +/// use bouncycastle_aes_lowmemory::AES_CBC_128; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::traits::SymmetricCipherEncryptor; +/// use bouncycastle_modes::Encrypting; +/// use bouncycastle_padding::NoPadding; +/// +/// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +/// +/// let (enc, _iv) = AES_CBC_128::::do_encrypt_init(&key).unwrap(); +/// let _matched: AES_CBC_128 = enc; /// ``` #[allow(non_camel_case_types)] -pub type AES_CBC_128 = Cbc; +pub type AES_CBC_128 = , + Cbc, + Pad, + 16, + BLOCK_LEN, +>>::Mode; -/// AES-192 in CBC mode. See [`AES_CBC_128`]. +/// AES-192 in CBC mode with a padding scheme. See [`AES_CBC_128`]. /// /// ``` /// use bouncycastle_aes_lowmemory::AES_CBC_192; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; +/// use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; +/// use bouncycastle_padding::PKCS7; /// /// let key = KeyMaterial::<24>::from_bytes_as_type(&[0x42; 24], KeyType::SymmetricCipherKey).unwrap(); -/// let mut data = [0u8; 32]; -/// let iv = AES_CBC_192::::encrypt(&key, &mut data).unwrap(); -/// AES_CBC_192::::decrypt(&key, &iv, &mut data).unwrap(); -/// assert_eq!(data, [0u8; 32]); +/// let message = b"a message of no particular length"; +/// +/// let (iv, ciphertext) = +/// AES_CBC_192::::encrypt(&key, message).expect("encryption"); +/// let recovered = +/// AES_CBC_192::::decrypt(&key, &iv, &ciphertext).expect("decryption"); +/// assert_eq!(recovered, message); /// ``` #[allow(non_camel_case_types)] -pub type AES_CBC_192 = Cbc; +pub type AES_CBC_192 = , + Cbc, + Pad, + 24, + BLOCK_LEN, +>>::Mode; -/// AES-256 in CBC mode. See [`AES_CBC_128`]. +/// AES-256 in CBC mode with a padding scheme. See [`AES_CBC_128`]. /// /// ``` /// use bouncycastle_aes_lowmemory::AES_CBC_256; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; +/// use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; +/// use bouncycastle_padding::PKCS7; /// /// let key = KeyMaterial::<32>::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey).unwrap(); -/// let mut data = [0u8; 32]; -/// let iv = AES_CBC_256::::encrypt(&key, &mut data).unwrap(); -/// AES_CBC_256::::decrypt(&key, &iv, &mut data).unwrap(); -/// assert_eq!(data, [0u8; 32]); +/// let message = b"another message"; +/// +/// let (iv, ciphertext) = +/// AES_CBC_256::::encrypt(&key, message).expect("encryption"); +/// let recovered = +/// AES_CBC_256::::decrypt(&key, &iv, &ciphertext).expect("decryption"); +/// assert_eq!(recovered, message); /// ``` #[allow(non_camel_case_types)] -pub type AES_CBC_256 = Cbc; +pub type AES_CBC_256 = , + Cbc, + Pad, + 32, + BLOCK_LEN, +>>::Mode; diff --git a/crypto/aes-lowmemory/src/ecb.rs b/crypto/aes-lowmemory/src/ecb.rs index d9902f8f..a8605bdf 100644 --- a/crypto/aes-lowmemory/src/ecb.rs +++ b/crypto/aes-lowmemory/src/ecb.rs @@ -1,101 +1,162 @@ -//! Type aliases for AES in ECB mode (NIST SP 800-38A Sec 6.1). +//! Type aliases for AES in ECB mode (NIST SP 800-38A Sec 6.1), with padding. //! //! `bouncycastle-modes` is deliberately cipher-agnostic, so `Ecb` takes the permutation, the -//! direction, and the `KEY_LEN` / `BLOCK_LEN` const parameters. These aliases pin the AES values so -//! callers never spell them out. +//! direction, and the `KEY_LEN` / `BLOCK_LEN` const parameters, and `bouncycastle-padding`'s +//! adapters take five more. These aliases pin all of them except the two choices a caller actually +//! makes: the direction and the padding scheme. +//! +//! ```text +//! AES_ECB_128 // AES-128, ECB, PKCS#7 padded, encrypting +//! AES_ECB_256 +//! ``` //! //! **ECB is not a confidentiality mode for data.** Under a given key every plaintext block maps to //! the same ciphertext block (Sec 6.1), so the structure of the plaintext shows through, and blocks -//! can be reordered, repeated or removed undetectably. These aliases exist for interoperability with -//! systems that use ECB and for driving test vectors; for data, use CBC or CFB under authentication, -//! or better an AEAD. See the crate docs, "A block permutation is not a cipher". +//! can be reordered, repeated or removed undetectably. Padding does not change that in the least: +//! it makes ECB accept any length, not make it safe. These aliases exist for interoperability with +//! systems that use ECB and for driving test vectors; for data, use CBC or CFB under +//! authentication, or better an AEAD. See the crate docs, "A block permutation is not a cipher". +//! +//! # Why the padding is part of the alias +//! +//! ECB is defined only on whole blocks (SP 800-38A Sec 5.2), so ECB on data of any other length is +//! always ECB *plus a padding scheme*, and the scheme changes the ciphertext. Naming it in the type +//! makes the choice explicit and makes a mismatched pair a compile error. [`PKCS7`] is the usual +//! one (this is Java's `AES/ECB/PKCS5Padding`); [`NoPadding`] adds nothing and instead rejects a +//! message that is not a whole number of blocks. +//! +//! # These are the arbitrary-length API +//! +//! A padded alias implements [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`], not the +//! block traits. The block-aligned API, with compile-time length checks and in-place data methods, +//! is `bouncycastle_modes::Ecb` itself, which these wrap. ECB has no IV, so `INIT_DATA_LEN` is 0: +//! encryption returns an empty array and decryption takes one, and the ciphertext is exactly the +//! padded plaintext with nothing prepended. +//! +//! # How one alias covers both directions +//! +//! See [`PaddedMode`], which is the projection that lets `Dir` select between the encryptor and the +//! decryptor adapter. `Dir` must be [`Encrypting`] or [`Decrypting`], as before. +use crate::padded_mode::PaddedMode; use crate::{Aes128, Aes192, Aes256, BLOCK_LEN}; -use bouncycastle_modes::Ecb; +use bouncycastle_modes::{Decrypting, Ecb, Encrypting}; + +// Imports needed for docs +#[allow(unused_imports)] +use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +#[allow(unused_imports)] +use bouncycastle_padding::{NoPadding, PKCS7}; +// end of imports needed for docs -/// AES-128 in ECB mode. `Dir` is [`bouncycastle_modes::Encrypting`] or -/// [`bouncycastle_modes::Decrypting`]; the wrong direction is a compile error, not a runtime check. +/// AES-128 in ECB mode with a padding scheme. /// -/// There is no IV: `encrypt` returns an empty array and `decrypt` takes one. Encryption and -/// decryption work in place. **Not confidential for data** -- see the module docs. +/// `Dir` is [`Encrypting`] or [`Decrypting`] and `Pad` is [`PKCS7`] or [`NoPadding`]; the wrong +/// direction is a compile error, not a runtime check. There is no IV: encryption returns an empty +/// array and decryption takes one. +/// +/// **Not confidential for data** -- see the module docs. Padding makes ECB accept any length; it +/// does not make it safe. /// /// ``` /// use bouncycastle_aes_lowmemory::AES_ECB_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; +/// use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; +/// use bouncycastle_padding::PKCS7; +/// +/// type Enc = AES_ECB_128; +/// type Dec = AES_ECB_128; /// /// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) /// .expect("a 16-byte symmetric cipher key"); -/// // 48 bytes: three whole blocks. The length is checked at compile time. -/// let message = [0u8; 48]; -/// let mut data = message; -/// let no_iv: [u8; 0] = AES_ECB_128::::encrypt(&key, &mut data).unwrap(); -/// assert_ne!(data, message); -/// // The codebook property: three equal plaintext blocks give three equal ciphertext blocks. -/// assert_eq!(data[..16], data[16..32]); -/// assert_eq!(data[..16], data[32..]); -/// AES_ECB_128::::decrypt(&key, &no_iv, &mut data).unwrap(); -/// assert_eq!(data, message); -/// -/// // Streaming, a few blocks at a time: -/// let (mut enc, _) = AES_ECB_128::::do_encrypt_init(&key).unwrap(); -/// let mut first = [0u8; 16]; -/// let mut rest = [1u8; 32]; -/// enc.do_encrypt(&mut first).unwrap(); -/// enc.do_encrypt(&mut rest).unwrap(); -/// let mut dec = AES_ECB_128::::do_decrypt_init(&key, &[]).unwrap(); -/// dec.do_decrypt(&mut first).unwrap(); -/// dec.do_decrypt(&mut rest).unwrap(); -/// assert_eq!(first, [0u8; 16]); -/// assert_eq!(rest, [1u8; 32]); +/// +/// // 5 bytes: PKCS#7 pads it to one block. The init data is empty, ECB having no IV. +/// let (no_iv, ciphertext) = Enc::encrypt(&key, b"hello").expect("encryption"); +/// assert_eq!(no_iv, [0u8; 0]); +/// assert_eq!(ciphertext.len(), 16); +/// +/// let recovered = Dec::decrypt(&key, &no_iv, &ciphertext).expect("decryption"); +/// assert_eq!(recovered, b"hello"); /// ``` /// -/// A length that is not a whole number of blocks is a **compile** error, not a runtime one: +/// The codebook property survives padding, which is the whole objection to ECB: two identical +/// plaintext blocks still give two identical ciphertext blocks. /// -/// ```compile_fail +/// ``` /// use bouncycastle_aes_lowmemory::AES_ECB_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::BlockCipherEncryptor; +/// use bouncycastle_core::traits::SymmetricCipherEncryptor; /// use bouncycastle_modes::Encrypting; +/// use bouncycastle_padding::NoPadding; /// /// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); -/// // 47 bytes is not a multiple of 16: the inline const assertion in `encrypt` fails to compile. -/// let _ = AES_ECB_128::::encrypt(&key, &mut [0u8; 47]); +/// +/// // Two identical blocks in... +/// let (_, ciphertext) = +/// AES_ECB_128::::encrypt(&key, &[0x5Au8; 32]).expect("encryption"); +/// // ...two identical blocks out. No mode here chains, so nothing hides the repetition. +/// assert_eq!(ciphertext[..16], ciphertext[16..]); /// ``` #[allow(non_camel_case_types)] -pub type AES_ECB_128 = Ecb; +pub type AES_ECB_128 = , + Ecb, + Pad, + 16, + 0, +>>::Mode; -/// AES-192 in ECB mode. See [`AES_ECB_128`]. +/// AES-192 in ECB mode with a padding scheme. See [`AES_ECB_128`], and its warning. /// /// ``` /// use bouncycastle_aes_lowmemory::AES_ECB_192; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; +/// use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; +/// use bouncycastle_padding::PKCS7; /// /// let key = KeyMaterial::<24>::from_bytes_as_type(&[0x42; 24], KeyType::SymmetricCipherKey).unwrap(); -/// let mut data = [0u8; 32]; -/// let no_iv = AES_ECB_192::::encrypt(&key, &mut data).unwrap(); -/// AES_ECB_192::::decrypt(&key, &no_iv, &mut data).unwrap(); -/// assert_eq!(data, [0u8; 32]); +/// let message = b"a message of no particular length"; +/// +/// let (no_iv, ciphertext) = +/// AES_ECB_192::::encrypt(&key, message).expect("encryption"); +/// let recovered = +/// AES_ECB_192::::decrypt(&key, &no_iv, &ciphertext).expect("decryption"); +/// assert_eq!(recovered, message); /// ``` #[allow(non_camel_case_types)] -pub type AES_ECB_192 = Ecb; +pub type AES_ECB_192 = , + Ecb, + Pad, + 24, + 0, +>>::Mode; -/// AES-256 in ECB mode. See [`AES_ECB_128`]. +/// AES-256 in ECB mode with a padding scheme. See [`AES_ECB_128`], and its warning. /// /// ``` /// use bouncycastle_aes_lowmemory::AES_ECB_256; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; +/// use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; +/// use bouncycastle_padding::PKCS7; /// /// let key = KeyMaterial::<32>::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey).unwrap(); -/// let mut data = [0u8; 32]; -/// let no_iv = AES_ECB_256::::encrypt(&key, &mut data).unwrap(); -/// AES_ECB_256::::decrypt(&key, &no_iv, &mut data).unwrap(); -/// assert_eq!(data, [0u8; 32]); +/// let message = b"a message of no particular length"; +/// +/// let (no_iv, ciphertext) = +/// AES_ECB_256::::encrypt(&key, message).expect("encryption"); +/// let recovered = +/// AES_ECB_256::::decrypt(&key, &no_iv, &ciphertext).expect("decryption"); +/// assert_eq!(recovered, message); /// ``` #[allow(non_camel_case_types)] -pub type AES_ECB_256 = Ecb; +pub type AES_ECB_256 = , + Ecb, + Pad, + 32, + 0, +>>::Mode; diff --git a/crypto/aes-lowmemory/src/lib.rs b/crypto/aes-lowmemory/src/lib.rs index eed751d6..16a304ef 100644 --- a/crypto/aes-lowmemory/src/lib.rs +++ b/crypto/aes-lowmemory/src/lib.rs @@ -59,40 +59,48 @@ //! ## Modes of operation //! //! To encrypt more than one block, use a mode of operation from `bouncycastle-modes`. This crate -//! provides aliases that fill in the const parameters, with the direction left as the type -//! parameter: [`AES_CBC_128`], [`AES_CBC_192`] and [`AES_CBC_256`] for CBC (SP 800-38A Sec 6.2), -//! and [`AES_CFB_128`], [`AES_CFB_192`] and [`AES_CFB_256`] for CFB128 (Sec 6.3). +//! provides aliases that fill in the const parameters, leaving only the choices a caller actually +//! makes: [`AES_CBC_128`], [`AES_CBC_192`] and [`AES_CBC_256`] for CBC (SP 800-38A Sec 6.2), which +//! take the direction **and a padding scheme**, and [`AES_CFB_128`], [`AES_CFB_192`] and +//! [`AES_CFB_256`] for CFB128 (Sec 6.3), which take only the direction. //! [`AES_CFB8_128`], [`AES_CFB8_192`] and [`AES_CFB8_256`] give CFB8, the `s = 8` segment size, //! which is a different and non-interoperable mode costing one AES call per byte. //! [`AES_CTR_128`], [`AES_CTR_192`] and [`AES_CTR_256`] give CTR (Sec 6.5) with a 12-byte nonce //! and a 4-byte counter. -//! [`AES_ECB_128`], [`AES_ECB_192`] and [`AES_ECB_256`] give ECB (Sec 6.1) the same shape with no -//! IV, for interoperability and test vectors only -- see +//! [`AES_ECB_128`], [`AES_ECB_192`] and [`AES_ECB_256`] give ECB (Sec 6.1), which takes a padding +//! scheme like CBC and has no IV, for interoperability and test vectors only -- see //! [A block permutation is not a cipher](#a-block-permutation-is-not-a-cipher). //! -//! CBC is a block cipher and needs whole blocks; the two CFB modes are stream ciphers and take any -//! length. See the `bouncycastle-modes` crate docs for the comparison. +//! CBC is a block cipher, so it is defined only on whole blocks and the alias carries a padding +//! scheme to bridge the difference; the CFB modes and CTR are stream ciphers and take any length +//! with no padding at all. See the `bouncycastle-modes` crate docs for the comparison, and +//! [`AES_CBC_128`] for why the scheme is named in the type. //! //! ``` //! use bouncycastle_aes_lowmemory::AES_CBC_256; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; +//! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; //! use bouncycastle_modes::{Decrypting, Encrypting}; +//! use bouncycastle_padding::PKCS7; //! //! let key = KeyMaterial::<32>::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) //! .expect("a 32-byte symmetric cipher key"); -//! // 48 bytes: three whole blocks. A length that is not a multiple of 16 would not compile. -//! let plaintext = [0x5Au8; 48]; -//! -//! // Encryption is in place. The IV is generated for you and returned; there is no API for -//! // supplying one. -//! let mut data = plaintext; -//! let iv = AES_CBC_256::::encrypt(&key, &mut data).unwrap(); -//! assert_ne!(data, plaintext); -//! AES_CBC_256::::decrypt(&key, &iv, &mut data).unwrap(); -//! assert_eq!(data, plaintext); +//! // Any length: PKCS#7 pads it out to whole blocks, so 50 bytes is as good as 48. +//! let plaintext = [0x5Au8; 50]; +//! +//! // The IV is generated for you and returned; there is no API for supplying one. +//! let (iv, ciphertext) = +//! AES_CBC_256::::encrypt(&key, &plaintext).expect("encryption"); +//! assert_eq!(ciphertext.len(), 64, "50 bytes padded out to four blocks"); +//! +//! let recovered = +//! AES_CBC_256::::decrypt(&key, &iv, &ciphertext).expect("decryption"); +//! assert_eq!(recovered, plaintext); //! ``` //! +//! For the block-aligned API -- whole blocks in place, with the length checked at compile time -- +//! name `bouncycastle_modes::Cbc` directly; that is what these aliases wrap. +//! //! There is no one-shot static on the permutation, because `Aes128::new(&key)?.encrypt_block(..)` //! already *is* the one shot. Data-level one-shots belong to the modes of operation, which take //! arbitrary-length input and generate their own initialisation data. @@ -164,8 +172,9 @@ //! //! The [`AES_ECB_128`] / [`AES_ECB_192`] / [`AES_ECB_256`] aliases give that same block-by-block //! operation the mode API, so that systems and specifications which require ECB -- and test-vector -//! harnesses -- can use it through the same interface as the other modes. They do not make it -//! confidential; the warning above applies to them unchanged. +//! harnesses -- can use it through the same interface as the other modes. Like the CBC aliases they +//! carry a padding scheme, which is what lets them accept data of any length. Neither the mode API +//! nor the padding makes ECB confidential; the warning above applies to them unchanged. //! //! ## Constant-time properties //! @@ -213,6 +222,7 @@ mod cfb; mod cfb8; mod ctr; mod ecb; +mod padded_mode; mod round; mod sbox; mod schedule; @@ -224,4 +234,5 @@ pub use cfb::{AES_CFB_128, AES_CFB_192, AES_CFB_256}; pub use cfb8::{AES_CFB8_128, AES_CFB8_192, AES_CFB8_256}; pub use ctr::{AES_CTR_128, AES_CTR_192, AES_CTR_256, CTR_NONCE_LEN}; pub use ecb::{AES_ECB_128, AES_ECB_192, AES_ECB_256}; +pub use padded_mode::PaddedMode; pub use schedule::{Aes128Params, Aes192Params, Aes256Params, AesParams}; diff --git a/crypto/aes-lowmemory/src/padded_mode.rs b/crypto/aes-lowmemory/src/padded_mode.rs new file mode 100644 index 00000000..da4914d5 --- /dev/null +++ b/crypto/aes-lowmemory/src/padded_mode.rs @@ -0,0 +1,64 @@ +//! The projection that lets a padded mode alias take its direction *and* its padding scheme. +//! +//! `bouncycastle-padding` splits its adapters by direction: [`PaddedEncryptor`] wraps a +//! [`BlockCipherEncryptor`] and [`PaddedDecryptor`] a [`BlockCipherDecryptor`]. They are two +//! distinct types, and a plain type alias cannot choose between two types based on one of its own +//! parameters, so `AES_CBC_128` cannot be written directly. +//! +//! [`PaddedMode`] does it instead. It is implemented for each direction marker, and its associated +//! type is the adapter for that direction, so an alias can be written as a projection through it: +//! +//! ```text +//! pub type AES_CBC_128 = , // what Encrypting resolves to +//! Cbc, // what Decrypting resolves to +//! Pad, 16, 16, +//! >>::Mode; +//! ``` +//! +//! One trait serves every block mode, since it is parameterised by the encryptor and decryptor +//! types rather than by the mode: CBC passes its two directions and `INIT_DATA_LEN = BLOCK_LEN`, +//! ECB passes its two and `INIT_DATA_LEN = 0`. + +use crate::BLOCK_LEN; +use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, Padding}; +use bouncycastle_modes::{Decrypting, Encrypting}; +use bouncycastle_padding::{PaddedDecryptor, PaddedEncryptor}; + +/// Projects a direction marker onto the padded adapter for that direction. +/// +/// Implemented for [`Encrypting`] and [`Decrypting`] and for nothing else, so those remain the only +/// usable values of a `Dir` parameter. See the module docs for why it exists. +/// +/// `Enc` and `Dec` are the two directions of the underlying block mode, `Pad` is the padding +/// scheme, and `INIT_DATA_LEN` is the mode's: the block length for a mode with an IV, 0 for ECB. +pub trait PaddedMode +where + Enc: BlockCipherEncryptor, + Dec: BlockCipherDecryptor, + Pad: Padding, +{ + /// The padded type for this direction: a [`PaddedEncryptor`] over `Enc`, or a + /// [`PaddedDecryptor`] over `Dec`. + type Mode; +} + +impl + PaddedMode for Encrypting +where + Enc: BlockCipherEncryptor, + Dec: BlockCipherDecryptor, + Pad: Padding, +{ + type Mode = PaddedEncryptor; +} + +impl + PaddedMode for Decrypting +where + Enc: BlockCipherEncryptor, + Dec: BlockCipherDecryptor, + Pad: Padding, +{ + type Mode = PaddedDecryptor; +} diff --git a/crypto/aes-lowmemory/tests/cbc_alias_tests.rs b/crypto/aes-lowmemory/tests/cbc_alias_tests.rs new file mode 100644 index 00000000..debc0842 --- /dev/null +++ b/crypto/aes-lowmemory/tests/cbc_alias_tests.rs @@ -0,0 +1,136 @@ +//! Tests for the padded AES-CBC aliases. +//! +//! The aliases are only type aliases, so what is worth testing is that they name the *right* types +//! and that both parameters actually select: the direction picks the encryptor or the decryptor, and +//! the padding scheme changes the behaviour rather than being decorative. The mode and the padding +//! layer are tested in their own crates; this checks the wiring between them. + +use bouncycastle_aes_lowmemory::{AES_CBC_128, AES_CBC_192, AES_CBC_256, Aes128}; +use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; +use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; + +fn key() -> KeyMaterial { + let bytes: [u8; N] = core::array::from_fn(|i| (i as u8).wrapping_mul(7).wrapping_add(1)); + KeyMaterial::::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey).expect("a valid key") +} + +/// The aliases must resolve to exactly the adapters they claim to, at both directions. +/// +/// A type alias that quietly resolved to something else -- the wrong padding, the wrong direction, +/// the wrong key length -- would still compile everywhere it is used, so this pins the projection +/// itself by asserting the layouts coincide with the fully spelled-out types. +#[test] +fn the_aliases_name_the_expected_types() { + use core::mem::size_of; + + assert_eq!( + size_of::>(), + size_of::, PKCS7, 16, 16, 16>>() + ); + assert_eq!( + size_of::>(), + size_of::, PKCS7, 16, 16, 16>>() + ); + + // The two directions are genuinely different types, so the encryptor and the decryptor do not + // have to agree in size -- and here they do not, which is itself evidence the projection + // selected two different adapters rather than one. + assert_ne!( + size_of::>(), + size_of::>() + ); +} + +/// Every key length round-trips through its alias, at a length that needs padding and one that does +/// not. +#[test] +fn every_key_length_round_trips() { + fn check(name: &str) + where + Enc: SymmetricCipherEncryptor, + Dec: SymmetricCipherDecryptor, + { + for len in [0usize, 1, 15, 16, 17, 63, 64] { + let plaintext: Vec = (0..len).map(|i| (i * 11 + 3) as u8).collect(); + let (iv, ciphertext) = Enc::encrypt(&key::(), &plaintext).expect("encryption"); + + // PKCS#7 always adds at least one byte, and rounds up to a whole block. + assert_eq!( + ciphertext.len(), + (len / 16 + 1) * 16, + "{name}, len {len}: PKCS7 pads up to the next whole block" + ); + + let recovered = Dec::decrypt(&key::(), &iv, &ciphertext).expect("decryption"); + assert_eq!(recovered, plaintext, "{name}, len {len}: round trip"); + } + } + + check::<16, AES_CBC_128, AES_CBC_128>("AES-128"); + check::<24, AES_CBC_192, AES_CBC_192>("AES-192"); + check::<32, AES_CBC_256, AES_CBC_256>("AES-256"); +} + +/// The padding parameter must actually select the scheme, not merely be carried around. +/// +/// `PKCS7` accepts any length and always grows the message; `NoPadding` accepts only whole blocks +/// and never grows it. Checking both against the same alias, key and plaintext is what proves the +/// parameter reaches the behaviour. +#[test] +fn the_padding_parameter_selects_the_scheme() { + type Pkcs7Enc = AES_CBC_128; + type NoPadEnc = AES_CBC_128; + + // A whole block: both schemes accept it, and they disagree about the length. + let aligned = [0x5Au8; 16]; + let (_, pkcs7) = Pkcs7Enc::encrypt(&key::<16>(), &aligned).expect("PKCS7 accepts aligned data"); + let (_, nopad) = NoPadEnc::encrypt(&key::<16>(), &aligned).expect("NoPadding accepts it too"); + assert_eq!(pkcs7.len(), 32, "PKCS7 adds a whole block of padding to aligned data"); + assert_eq!(nopad.len(), 16, "NoPadding adds nothing"); + + // Five bytes: PKCS7 pads it, NoPadding refuses rather than silently padding. + let unaligned = b"hello"; + assert!(Pkcs7Enc::encrypt(&key::<16>(), unaligned).is_ok(), "PKCS7 pads a partial block"); + assert!( + NoPadEnc::encrypt(&key::<16>(), unaligned).is_err(), + "NoPadding must refuse a message that is not a whole number of blocks" + ); +} + +/// A ciphertext made under one scheme must not decrypt cleanly under the other. +/// +/// This is the practical reason the scheme is named in the type: the two are not interchangeable, +/// and without the type parameter nothing would stop a caller pairing them. +#[test] +fn the_two_schemes_are_not_interchangeable() { + let aligned = [0x5Au8; 16]; + let (iv, pkcs7) = + AES_CBC_128::::encrypt(&key::<16>(), &aligned).expect("encryption"); + + // NoPadding will hand back the padded block as if it were data, so it "succeeds" with the + // wrong answer -- exactly the silent mismatch the type parameter is there to prevent. + let as_nopad = AES_CBC_128::::decrypt(&key::<16>(), &iv, &pkcs7) + .expect("NoPadding cannot tell that the trailing block is padding"); + assert_ne!(as_nopad, aligned, "the recovered data must not match the original"); + assert_eq!(as_nopad.len(), 32, "it keeps the padding block as data"); + + // ...and the matching scheme gets it right. + let correct = + AES_CBC_128::::decrypt(&key::<16>(), &iv, &pkcs7).expect("decryption"); + assert_eq!(correct, aligned); +} + +/// The IV is generated per encryption, so the same plaintext gives different ciphertext. +#[test] +fn each_encryption_gets_a_fresh_iv() { + let plaintext = [0x77u8; 32]; + let mut seen = std::collections::BTreeSet::new(); + for _ in 0..16 { + let (iv, ct) = AES_CBC_128::::encrypt(&key::<16>(), &plaintext).unwrap(); + assert!(seen.insert(iv), "IV repeated across encryptions"); + let back = AES_CBC_128::::decrypt(&key::<16>(), &iv, &ct).unwrap(); + assert_eq!(back, plaintext); + } +} diff --git a/crypto/aes-lowmemory/tests/ecb_alias_tests.rs b/crypto/aes-lowmemory/tests/ecb_alias_tests.rs new file mode 100644 index 00000000..4db0300b --- /dev/null +++ b/crypto/aes-lowmemory/tests/ecb_alias_tests.rs @@ -0,0 +1,110 @@ +//! Tests for the padded AES-ECB aliases. +//! +//! As with the CBC aliases, these are only type aliases, so what is worth testing is that both +//! parameters select: the direction picks the encryptor or the decryptor, and the padding scheme +//! reaches the behaviour. ECB's own properties are tested in `bouncycastle-modes`; what is specific +//! here is that its `INIT_DATA_LEN` is 0, so the projection must carry a different value than CBC's +//! and the aliases must still resolve correctly. + +use bouncycastle_aes_lowmemory::{AES_ECB_128, AES_ECB_192, AES_ECB_256, Aes128}; +use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +use bouncycastle_modes::{Decrypting, Ecb, Encrypting}; +use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; + +fn key() -> KeyMaterial { + let bytes: [u8; N] = core::array::from_fn(|i| (i as u8).wrapping_mul(7).wrapping_add(1)); + KeyMaterial::::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey).expect("a valid key") +} + +/// The aliases must resolve to exactly the adapters they claim to, with `INIT_DATA_LEN = 0`. +#[test] +fn the_aliases_name_the_expected_types() { + use core::mem::size_of; + + assert_eq!( + size_of::>(), + size_of::, PKCS7, 16, 0, 16>>() + ); + assert_eq!( + size_of::>(), + size_of::, PKCS7, 16, 0, 16>>() + ); +} + +/// ECB has no IV, so the init data is an empty array and the ciphertext is exactly the padded +/// plaintext with nothing prepended. That is the difference from the CBC aliases, and it comes from +/// the `INIT_DATA_LEN = 0` the projection is given. +#[test] +fn there_is_no_iv() { + let (no_iv, ciphertext) = + AES_ECB_128::::encrypt(&key::<16>(), b"hello").expect("encryption"); + assert_eq!(no_iv, [0u8; 0], "ECB has no IV, so the init data is empty"); + assert_eq!(ciphertext.len(), 16, "five bytes padded to one block, nothing prepended"); + + let recovered = + AES_ECB_128::::decrypt(&key::<16>(), &no_iv, &ciphertext).unwrap(); + assert_eq!(recovered, b"hello"); +} + +/// Every key length round-trips through its alias, at lengths that need padding and lengths that do +/// not. +#[test] +fn every_key_length_round_trips() { + fn check(name: &str) + where + Enc: SymmetricCipherEncryptor, + Dec: SymmetricCipherDecryptor, + { + for len in [0usize, 1, 15, 16, 17, 64] { + let plaintext: Vec = (0..len).map(|i| (i * 11 + 3) as u8).collect(); + let (no_iv, ciphertext) = Enc::encrypt(&key::(), &plaintext).expect("encryption"); + assert_eq!(no_iv, [0u8; 0], "{name}: no IV"); + assert_eq!( + ciphertext.len(), + (len / 16 + 1) * 16, + "{name}, len {len}: PKCS7 pads up to the next whole block" + ); + + let recovered = Dec::decrypt(&key::(), &no_iv, &ciphertext).expect("decryption"); + assert_eq!(recovered, plaintext, "{name}, len {len}: round trip"); + } + } + + check::<16, AES_ECB_128, AES_ECB_128>("AES-128"); + check::<24, AES_ECB_192, AES_ECB_192>("AES-192"); + check::<32, AES_ECB_256, AES_ECB_256>("AES-256"); +} + +/// The padding parameter must select the scheme here too. +#[test] +fn the_padding_parameter_selects_the_scheme() { + let aligned = [0x5Au8; 16]; + let (_, pkcs7) = + AES_ECB_128::::encrypt(&key::<16>(), &aligned).expect("PKCS7"); + let (_, nopad) = + AES_ECB_128::::encrypt(&key::<16>(), &aligned).expect("NoPadding"); + assert_eq!(pkcs7.len(), 32, "PKCS7 adds a whole block to aligned data"); + assert_eq!(nopad.len(), 16, "NoPadding adds nothing"); + + assert!( + AES_ECB_128::::encrypt(&key::<16>(), b"hello").is_err(), + "NoPadding must refuse a partial block" + ); +} + +/// Padding does not fix ECB: identical plaintext blocks still give identical ciphertext blocks, and +/// the same message under the same key always gives the same ciphertext. The aliases carry the +/// warning; this is the test that it is warranted. +#[test] +fn padding_does_not_hide_the_codebook_property() { + // Two identical blocks give two identical ciphertext blocks. + let (_, ciphertext) = + AES_ECB_128::::encrypt(&key::<16>(), &[0x5Au8; 32]).unwrap(); + assert_eq!(ciphertext[..16], ciphertext[16..], "ECB is a codebook, padded or not"); + + // ...and encryption is deterministic, there being no IV to vary. + let (_, a) = AES_ECB_128::::encrypt(&key::<16>(), b"hello").unwrap(); + let (_, b) = AES_ECB_128::::encrypt(&key::<16>(), b"hello").unwrap(); + assert_eq!(a, b, "the same message encrypts the same way every time"); +} diff --git a/crypto/modes/src/lib.rs b/crypto/modes/src/lib.rs index 3f65074a..1ca6112f 100644 --- a/crypto/modes/src/lib.rs +++ b/crypto/modes/src/lib.rs @@ -31,7 +31,9 @@ //! The crate is deliberately cipher-agnostic: it depends on no concrete block cipher, only on the //! trait. Define a one-line alias for the combination you use -- or use the ready-made //! `AES_CBC_128` / `AES_CFB_128` / `AES_CFB8_128` / `AES_CTR_128` / `AES_ECB_128` and friends from -//! `bouncycastle-aes-lowmemory`: +//! `bouncycastle-aes-lowmemory`. Those aliases are not all the same shape: the two block modes take +//! a padding scheme as well as a direction, since neither is usable on data of arbitrary length +//! without one, while the three stream modes take only the direction: //! //! ``` //! use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; From 5e43a9d9194bfa5e9c9daa65b89ab005e3c81db5 Mon Sep 17 00:00:00 2001 From: David Hook Date: Mon, 7 Sep 2026 10:00:12 +1000 Subject: [PATCH 02/14] core: stream ciphers also implement SymmetricCipherEncryptor / SymmetricCipherDecryptor with FINAL_LEN = 0, by blanket impls over the in-place methods, so any of the five modes can be held through one trait --- alpha_0.1.3_release_notes.md | 20 ++ crypto/core/src/traits.rs | 151 ++++++++- crypto/modes/src/lib.rs | 10 +- .../modes/tests/symmetric_cipher_api_tests.rs | 291 ++++++++++++++++++ 4 files changed, 470 insertions(+), 2 deletions(-) create mode 100644 crypto/modes/tests/symmetric_cipher_api_tests.rs diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index d091b992..fd49db73 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -386,6 +386,26 @@ bound, checked before any work is done) and the `std` `Vec` one-shots are provid methods, so an implementor writes six methods. The older one-shot-only `SymmetricCipher` trait is unchanged for now; `AEADCipher` still builds on it and is the next to migrate. +Stream ciphers also reach the arbitrary-length API: `StreamCipherEncryptor` and +`StreamCipherDecryptor` get blanket impls of `SymmetricCipherEncryptor` / `SymmetricCipherDecryptor` +with `FINAL_LEN = 0`, written in terms of the in-place `do_encrypt` / `do_decrypt`. An implementor +still writes only the in-place methods, but a caller can use `encrypt_out`, `do_update_out` and the +`std` one-shots, and can hold a stream mode through the same trait as a padded block mode -- which +is what makes "any of the five modes behind one trait" true rather than aspirational. For a stream +cipher the length predictions are exact rather than upper bounds, and `do_final` has nothing to +produce. The one cost is that both traits then spell `do_encrypt_init` identically, so code with +both in scope must qualify the call; `crypto/modes/tests/symmetric_cipher_api_tests.rs` is written +that way deliberately, to show it is workable. That file also runs all three stream modes through +`TestFrameworkSymmetricCipher::test_encryptor_decryptor`, the same conformance suite the padded +adapters run, and checks the separate-output API against the in-place one byte for byte. + +Mutation-tested with `--test-workspace`, which is what these blanket impls need: run against core's +own tests alone they look untested, because core has no implementors of its own traits. Scoped to +the change, 45 mutants, 22 caught, 19 unviable, 4 missed -- all four the same equivalent mutant, +`[]` against `[0; 0]` and `[1; 0]` for a zero-length array, which no test can distinguish because +they are the same value; both sites carry a comment saying so. The one genuinely uncovered mutant +the run found, the decryptor's output-buffer length comparison, is now covered. + `StreamCipher` is **replaced** by the split pair `StreamCipherEncryptor` / `StreamCipherDecryptor`, shaped like `BlockCipherEncryptor` / `BlockCipherDecryptor` and for the same reasons: the direction is encoded in the type, and a policy can permit decryption of an algorithm while forbidding new diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index cfc77a29..5cb42fb1 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -1094,7 +1094,8 @@ pub trait Signer, const SK_LEN: usize, const SIG } /// The decryption half of a stream cipher's streaming API; see [`StreamCipherEncryptor`], whose -/// notes on in-place operation, arbitrary lengths and the `Result` all apply here too. +/// notes on in-place operation, arbitrary lengths, the `Result` and the free +/// [`SymmetricCipherDecryptor`] impl all apply here too. pub trait StreamCipherDecryptor: Algorithm + Sized { @@ -1130,6 +1131,14 @@ pub trait StreamCipherDecryptor as StreamCipherEncryptor<..>>::do_encrypt_init(&key)` -- though either resolves to the +/// same function. +impl + SymmetricCipherEncryptor for T +where + T: StreamCipherEncryptor, +{ + fn do_encrypt_init( + key: &KeyMaterial, + ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError> { + >::do_encrypt_init(key) + } + + fn do_encrypt_init_rng( + key: &KeyMaterial, + rng: &mut dyn RNG, + ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError> { + >::do_encrypt_init_rng(key, rng) + } + + /// A stream cipher buffers nothing, so every input byte produces exactly one output byte. + fn update_out_len(&self, input_len: usize) -> usize { + input_len + } + + /// Copies the plaintext into the output buffer and encrypts it there, so the caller's input is + /// left untouched -- the one thing the in-place [`StreamCipherEncryptor::do_encrypt`] cannot + /// offer. + /// + /// # Errors + /// [`SymmetricCipherError::IncorrectOutputBufferLength`] if `ciphertext` is shorter than + /// `plaintext`, checked before anything is consumed; otherwise whatever `do_encrypt` returns. + fn do_update_out( + &mut self, + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result { + if ciphertext.len() < plaintext.len() { + return Err(SymmetricCipherError::IncorrectOutputBufferLength( + "ciphertext", + plaintext.len(), + )); + } + let out = &mut ciphertext[..plaintext.len()]; + out.copy_from_slice(plaintext); + self.do_encrypt(out)?; + Ok(plaintext.len()) + } + + /// Nothing is held back, so there is nothing to finish: an empty buffer, none of it output. + /// + /// `cargo mutants` reports the `[]` here as a surviving mutant against `[0; 0]` and `[1; 0]`. + /// Those are the same value: a zero-length array has no element to differ in, so the three + /// spellings are indistinguishable and no test can separate them. The mutants that *do* change + /// behaviour -- returning 1 rather than 0 for the data length -- are caught. + fn do_final(self) -> Result<([u8; 0], usize), SymmetricCipherError> { + Ok(([], 0)) + } + + /// A stream cipher never changes the length of its data. + fn encrypt_out_len(plaintext_len: usize) -> usize { + plaintext_len + } +} + +/// Every stream cipher is also a [`SymmetricCipherDecryptor`] with `FINAL_LEN = 0`. The mirror of +/// the [`StreamCipherEncryptor`] blanket impl above; see it for why this exists. +impl + SymmetricCipherDecryptor for T +where + T: StreamCipherDecryptor, +{ + fn do_decrypt_init( + key: &KeyMaterial, + init_data: &[u8; INIT_DATA_LEN], + ) -> Result { + >::do_decrypt_init(key, init_data) + } + + /// A stream cipher holds nothing back, so every input byte can be released immediately. + fn update_out_len(&self, input_len: usize) -> usize { + input_len + } + + /// Copies the ciphertext into the output buffer and decrypts it there, leaving the caller's + /// input untouched. + /// + /// # Errors + /// [`SymmetricCipherError::IncorrectOutputBufferLength`] if `plaintext` is shorter than + /// `ciphertext`, checked before anything is consumed; otherwise whatever `do_decrypt` returns. + fn do_update_out( + &mut self, + ciphertext: &[u8], + plaintext: &mut [u8], + ) -> Result { + if plaintext.len() < ciphertext.len() { + return Err(SymmetricCipherError::IncorrectOutputBufferLength( + "plaintext", + ciphertext.len(), + )); + } + let out = &mut plaintext[..ciphertext.len()]; + out.copy_from_slice(ciphertext); + self.do_decrypt(out)?; + Ok(ciphertext.len()) + } + + /// Nothing is held back, and there is no padding or tag to check. + /// + /// `cargo mutants` reports the `[]` here as a surviving mutant against `[0; 0]` and `[1; 0]`. + /// Those are the same value: a zero-length array has no element to differ in, so the three + /// spellings are indistinguishable and no test can separate them. The mutants that *do* change + /// behaviour -- returning 1 rather than 0 for the data length -- are caught. + fn do_final(self) -> Result<([u8; 0], usize), SymmetricCipherError> { + Ok(([], 0)) + } + + /// Exact rather than an upper bound: a stream cipher never changes the length of its data. + fn decrypt_out_max_len(ciphertext_len: usize) -> usize { + ciphertext_len + } +} + /// Extensible Output Functions (XOFs) are similar to hash functions, except that they can produce output of arbitrary length. /// The naming used for the functions of this trait are borrowed from the SHA3-style sponge constructions that split XOF operation /// into two phases: an absorb phase in which an arbitrary amount of input is provided to the XOF, diff --git a/crypto/modes/src/lib.rs b/crypto/modes/src/lib.rs index 1ca6112f..bb13e4c3 100644 --- a/crypto/modes/src/lib.rs +++ b/crypto/modes/src/lib.rs @@ -18,6 +18,14 @@ //! [`StreamCipherDecryptor`]): any length in, the same length out, no padding, no finalization -- //! see [Block alignment, and which modes need it](#block-alignment-and-which-modes-need-it). //! +//! **All five reach the same arbitrary-length API**, so code can be written against one trait and +//! handed any mode. A block mode gets there by being wrapped in `bouncycastle-padding`'s adapters, +//! which are [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`] with the padded block as +//! their final output; a stream mode implements those traits directly, with `FINAL_LEN = 0` because +//! it has no final output at all. The `bouncycastle-aes-lowmemory` aliases show the difference in +//! one line each: `AES_CBC_128` names a padding scheme, `AES_CTR_128` +//! has nothing to name. +//! //! CBC, CFB, CFB8 and CTR all generate their own init data: an IV for the first three, a nonce for //! CTR, which is shorter than a block because the rest of the counter block is the counter. ECB has //! none at all (`INIT_DATA_LEN = 0`) and is the raw permutation applied block by block -- see @@ -525,7 +533,7 @@ pub use ecb::Ecb; #[allow(unused_imports)] use bouncycastle_core::traits::{ BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook, StreamCipherDecryptor, - StreamCipherEncryptor, + StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; // end of imports needed for docs diff --git a/crypto/modes/tests/symmetric_cipher_api_tests.rs b/crypto/modes/tests/symmetric_cipher_api_tests.rs new file mode 100644 index 00000000..9a7e28a1 --- /dev/null +++ b/crypto/modes/tests/symmetric_cipher_api_tests.rs @@ -0,0 +1,291 @@ +//! The stream modes through the [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`] API. +//! +//! `Cfb`, `Cfb8` and `Ctr` implement the stream traits directly and get the symmetric-cipher traits +//! from the blanket impls in `bouncycastle-core`, with `FINAL_LEN = 0`. That is what lets a caller +//! hold any of the five modes through one trait: a padded `Cbc` or `Ecb` with the padded block as +//! its final output, and a stream mode with nothing. +//! +//! What is worth testing here is the bridge, not the ciphers, which their own suites cover: +//! +//! * that the modes really do satisfy the shared conformance suite for those traits, the same one +//! the padding adapters run; +//! * that the separate-output API agrees byte for byte with the in-place one, since the blanket +//! impl is written in terms of it; +//! * that it leaves the caller's input alone, which is the one thing the in-place API cannot offer +//! and therefore the reason to have both; +//! * and that the length predictions are exact, not upper bounds. +//! +//! # Both traits in scope at once +//! +//! This file imports the stream traits *and* the symmetric ones, so `do_encrypt_init` is ambiguous +//! here and every call has to name the trait it means. That is the one ergonomic cost of a mode +//! implementing both, so it is worth having a file that demonstrates it is workable; the two +//! resolve to the same function. + +mod common; + +use bouncycastle_aes_lowmemory::Aes128; +use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::{ + StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, + SymmetricCipherEncryptor, +}; +use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkSymmetricCipher; +use bouncycastle_modes::{Cfb, Cfb8, Ctr, Decrypting, Encrypting}; +use common::{TOY_LEN, Toy, toy_key}; + +type ToyCfb = Cfb; +type ToyCfb8 = Cfb8; +type ToyCtr = Ctr; + +/// All three stream modes must satisfy the shared conformance suite for the symmetric-cipher +/// traits -- the same suite the padded adapters run, with `required_alignment` left at 1 because a +/// stream cipher accepts every length. +/// +/// It pins the whole contract: one-shot round trips at every length, the `std` one-shots against +/// the `_out` ones, streaming in eight chunkings with `update_out_len` exact on every call, +/// `do_final_out` against `do_final`, a driven RNG reproducing its init data, corruption detection, +/// short output buffers refused with the required length, and the key-type and security-strength +/// policy. +#[test] +fn the_stream_modes_conform_to_the_symmetric_cipher_suite() { + let framework = TestFrameworkSymmetricCipher::new(); + framework + .test_encryptor_decryptor::, ToyCfb>(); + framework + .test_encryptor_decryptor::, ToyCfb8>( + ); + framework.test_encryptor_decryptor::, ToyCtr>(); +} + +/// The separate-output API must produce exactly what the in-place API produces, for the same key +/// and init data. The blanket impl is written in terms of `do_encrypt`, so this is the check that +/// the bridge adds nothing and loses nothing. +#[test] +fn the_two_apis_agree_byte_for_byte() { + fn check( + name: &str, + key: &KeyMaterial, + ) where + E: StreamCipherEncryptor + + SymmetricCipherEncryptor, + D: StreamCipherDecryptor + + SymmetricCipherDecryptor, + { + for len in [0usize, 1, 15, 16, 17, 63, 64, 171] { + let plaintext: Vec = (0..len).map(|i| (i * 7 + 1) as u8).collect(); + + // The in-place API, which the mode implements directly. + let (mut enc, init) = + >::do_encrypt_init(key).unwrap(); + let mut in_place = plaintext.clone(); + enc.do_encrypt(&mut in_place).unwrap(); + + // The separate-output API, under the same init data, reached through the blanket impl. + let mut dec_as_sym = + >::do_decrypt_init( + key, &init, + ) + .unwrap(); + let mut out = vec![0u8; plaintext.len()]; + let n = dec_as_sym.do_update_out(&in_place, &mut out).unwrap(); + let (last, last_len) = dec_as_sym.do_final().unwrap(); + assert_eq!(n, plaintext.len(), "{name}, len {len}: everything is released immediately"); + assert_eq!(last, [0u8; 0], "{name}: a stream cipher has no final output"); + assert_eq!(last_len, 0, "{name}: ...and none of it is data"); + assert_eq!(out, plaintext, "{name}, len {len}: the two APIs must agree"); + } + } + + check::, ToyCfb, TOY_LEN, TOY_LEN>("Cfb", &toy_key()); + check::, ToyCfb8, TOY_LEN, TOY_LEN>("Cfb8", &toy_key()); + check::, ToyCtr, TOY_LEN, 12>("Ctr", &toy_key()); +} + +/// The separate-output API must leave the caller's input untouched. That is the whole reason a +/// stream cipher wants it as well as the in-place one, so it is worth asserting rather than +/// assuming. +#[test] +fn the_input_buffer_is_not_modified() { + let key = toy_key(); + let plaintext: Vec = (0..100u8).collect(); + let original = plaintext.clone(); + + let (mut enc, _init) = + as SymmetricCipherEncryptor>::do_encrypt_init( + &key, + ) + .unwrap(); + let mut ciphertext = vec![0u8; plaintext.len()]; + enc.do_update_out(&plaintext, &mut ciphertext).unwrap(); + + assert_eq!(plaintext, original, "the plaintext must be left alone"); + assert_ne!(ciphertext, original, "...and the ciphertext must actually be encrypted"); +} + +/// The length predictions are exact for a stream cipher, not upper bounds: what goes in comes out. +#[test] +fn the_length_predictions_are_exact() { + let key = toy_key(); + for len in [0usize, 1, 15, 16, 17, 1000] { + assert_eq!( + as SymmetricCipherEncryptor>::encrypt_out_len(len), + len, + "encrypt_out_len is the identity" + ); + assert_eq!( + as SymmetricCipherDecryptor>::decrypt_out_max_len( + len + ), + len, + "decrypt_out_max_len is exact, not an upper bound" + ); + + let (enc, _) = + as SymmetricCipherEncryptor>::do_encrypt_init(&key) + .unwrap(); + assert_eq!(enc.update_out_len(len), len, "update_out_len is the identity"); + } +} + +/// A short output buffer is refused with the length it needed, and nothing is consumed -- so the +/// same call with a big enough buffer then succeeds and gives the answer it would have given. +#[test] +fn a_short_output_buffer_is_refused_without_consuming_anything() { + use bouncycastle_core::errors::SymmetricCipherError; + + let key = toy_key(); + let plaintext: Vec = (0..32u8).collect(); + + let (mut enc, init) = + as SymmetricCipherEncryptor>::do_encrypt_init( + &key, + ) + .unwrap(); + + let mut too_small = vec![0u8; plaintext.len() - 1]; + match enc.do_update_out(&plaintext, &mut too_small) { + Err(SymmetricCipherError::IncorrectOutputBufferLength(what, needed)) => { + assert_eq!(what, "ciphertext"); + assert_eq!(needed, plaintext.len(), "the error carries the required length"); + } + other => panic!("expected IncorrectOutputBufferLength, got {other:?}"), + } + + // Nothing was consumed, so the keystream has not advanced: the retry must give exactly what a + // fresh encryptor under the same init data would. + let mut big_enough = vec![0u8; plaintext.len()]; + enc.do_update_out(&plaintext, &mut big_enough).unwrap(); + + let (mut fresh, _) = + as StreamCipherEncryptor>::do_encrypt_init_rng( + &key, + &mut bouncycastle_core_test_framework::FixedSeedRNG::::new(init), + ) + .unwrap(); + let mut reference = plaintext.clone(); + fresh.do_encrypt(&mut reference).unwrap(); + assert_eq!(big_enough, reference, "the refused call must not have advanced the keystream"); +} + +/// The decrypt side refuses a short output buffer too, with the length it needed. +/// +/// The mirror of the encryptor test above. Worth having separately rather than assuming symmetry: +/// the two are separate blanket impls with their own buffer check, and mutation testing showed the +/// decryptor's comparison was unexercised until this existed. +#[test] +fn a_short_output_buffer_is_refused_when_decrypting_too() { + use bouncycastle_core::errors::SymmetricCipherError; + + let key = toy_key(); + let plaintext: Vec = (0..32u8).collect(); + + // Encrypt normally, then try to decrypt into a buffer one byte too small. + let (mut enc, init) = + as StreamCipherEncryptor>::do_encrypt_init(&key) + .unwrap(); + let mut ciphertext = plaintext.clone(); + enc.do_encrypt(&mut ciphertext).unwrap(); + + let mut dec = + as SymmetricCipherDecryptor>::do_decrypt_init( + &key, &init, + ) + .unwrap(); + + let mut too_small = vec![0u8; ciphertext.len() - 1]; + match dec.do_update_out(&ciphertext, &mut too_small) { + Err(SymmetricCipherError::IncorrectOutputBufferLength(what, needed)) => { + assert_eq!(what, "plaintext"); + assert_eq!(needed, ciphertext.len(), "the error carries the required length"); + } + other => panic!("expected IncorrectOutputBufferLength, got {other:?}"), + } + + // Nothing was consumed, so the retry recovers the plaintext exactly. + let mut big_enough = vec![0u8; ciphertext.len()]; + let n = dec.do_update_out(&ciphertext, &mut big_enough).unwrap(); + assert_eq!(n, ciphertext.len()); + assert_eq!(big_enough, plaintext, "the refused call must not have advanced the keystream"); + + // An oversized buffer is fine, and only the leading bytes are written: the check is "too + // short", not "not exactly equal". + let mut oversized = vec![0xAAu8; ciphertext.len() + 8]; + let mut dec = + as SymmetricCipherDecryptor>::do_decrypt_init( + &key, &init, + ) + .unwrap(); + let n = dec.do_update_out(&ciphertext, &mut oversized).expect("an oversized buffer is fine"); + assert_eq!(n, ciphertext.len()); + assert_eq!(&oversized[..n], &plaintext[..], "the data lands in the leading bytes"); + assert!(oversized[n..].iter().all(|&b| b == 0xAA), "the rest is left alone"); +} + +/// The one-shots work with real AES, at a length that is not a whole number of blocks, for all +/// three stream modes -- the shape a caller most often wants from this API. +#[test] +fn the_one_shots_round_trip_with_real_aes() { + let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) + .expect("a valid AES-128 key"); + let message = b"a message of no particular length at all"; + + // CFB128 + let (iv, ct) = + as SymmetricCipherEncryptor<16, 16, 0>>::encrypt( + &key, message, + ) + .unwrap(); + assert_eq!(ct.len(), message.len(), "a stream cipher does not change the length"); + let back = as SymmetricCipherDecryptor<16, 16, 0>>::decrypt( + &key, &iv, &ct, + ) + .unwrap(); + assert_eq!(back, message); + + // CFB8 + let (iv, ct) = + as SymmetricCipherEncryptor<16, 16, 0>>::encrypt( + &key, message, + ) + .unwrap(); + let back = as SymmetricCipherDecryptor<16, 16, 0>>::decrypt( + &key, &iv, &ct, + ) + .unwrap(); + assert_eq!(back, message); + + // CTR + let (nonce, ct) = + as SymmetricCipherEncryptor<16, 12, 0>>::encrypt( + &key, message, + ) + .unwrap(); + assert_eq!(nonce.len(), 12, "CTR's init data is its 12-byte nonce"); + let back = + as SymmetricCipherDecryptor<16, 12, 0>>::decrypt( + &key, &nonce, &ct, + ) + .unwrap(); + assert_eq!(back, message); +} From fe5c29126ee60059d83351d1295f4a7a96aac5c7 Mon Sep 17 00:00:00 2001 From: David Hook Date: Mon, 7 Sep 2026 10:12:54 +1000 Subject: [PATCH 03/14] core: delete the SymmetricCipher trait and move its four one-shots onto AEADCipher, its only remaining user; the framework suite follows, and both AEAD security-strength loops gain the key-length guard the other suites already had --- alpha_0.1.3_release_notes.md | 23 +- crypto/aes-lowmemory/summary.md | 2 +- .../src/symmetric_ciphers.rs | 211 ++++++++++-------- crypto/core-test-framework/summary.md | 14 +- crypto/core/src/traits.rs | 113 +++++----- 5 files changed, 205 insertions(+), 158 deletions(-) diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index fd49db73..101c7522 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -383,8 +383,25 @@ bytes are output -- always `FINAL_LEN` except for a padding scheme that adds not and, for the decryptor, how many of them are data. `do_final_out`, the `_out` one-shots (`encrypt_out[_rng]`, `decrypt_out`, with `encrypt_out_len` exact and `decrypt_out_max_len` an upper bound, checked before any work is done) and the `std` `Vec` one-shots are provided over the streaming -methods, so an implementor writes six methods. The older one-shot-only `SymmetricCipher` trait is -unchanged for now; `AEADCipher` still builds on it and is the next to migrate. +methods, so an implementor writes six methods. + +The older one-shot-only `SymmetricCipher` trait is **deleted**, and its four methods -- `encrypt`, +`encrypt_out`, `decrypt`, `decrypt_out` -- move onto `AEADCipher`, which was its only remaining +user. Every other kind of cipher now reaches an arbitrary-length one-shot some other way: a block +mode through `SymmetricCipherEncryptor` / `SymmetricCipherDecryptor` and the padding adapters, a +stream mode through those same traits directly. `AEADCipher` therefore drops the supertrait and +declares the four itself, against `NONCE_LEN`, with the documentation saying what they mean for an +AEAD: no additional authenticated data, and a ciphertext layout that is the implementation's +business because the tag has to go somewhere. `TestFrameworkSymmetricCipher::test`, which was that +trait's suite, moves to `TestFrameworkAEADCipher::test_plain_one_shots` and is called from +`TestFrameworkAEADCipher::test`, so an AEAD implementor keeps the coverage without asking for it. + +That move also closed the last of a latent bug recorded in `core-test-framework/summary.md`: two +security-strength loops unwrapped `set_security_strength` at all five strengths, which a key shorter +than 32 bytes cannot carry, so they would have panicked for the first AEAD implementor — ASCON-128 +and AES-128-GCM among them. Relocating one of them into a method the AEAD suite calls would have +made that worse, so both now carry the same key-length guard the block and stream suites already +had. Every strength loop in the file is guarded. Stream ciphers also reach the arbitrary-length API: `StreamCipherEncryptor` and `StreamCipherDecryptor` get blanket impls of `SymmetricCipherEncryptor` / `SymmetricCipherDecryptor` @@ -568,7 +585,7 @@ Block cipher traits (PR #96): * The single `BlockCipher` streaming trait is split into `BlockCipherEncryptor` and `BlockCipherDecryptor` (mirroring `KEMEncapsulator` / `KEMDecapsulator`) so the direction is encoded in the implementing type. Both, and `ElectronicCodeBook`, are bounded on `Algorithm`, whose `MAX_SECURITY_STRENGTH` is the strength the `_init` - constructors enforce (a mode reports its permutation's name and strength); the `SymmetricCipher` one-shot API is no + constructors enforce (a mode reports its permutation's name and strength); the one-shot API is no longer a supertrait. * The single-block `do_{en,de}crypt_block[_out]` methods are replaced by multi-block `do_{en,de}crypt_blocks[_out]`, taking `&[[u8; BLOCK_LEN]; N]` so the block count is compile-time and diff --git a/crypto/aes-lowmemory/summary.md b/crypto/aes-lowmemory/summary.md index 0a512b65..4978e696 100644 --- a/crypto/aes-lowmemory/summary.md +++ b/crypto/aes-lowmemory/summary.md @@ -22,7 +22,7 @@ Consistent with the earlier scoping decision for the AES engine, the crate delib * **no CLI subcommand** — a bare permutation can only offer ECB, * **no factory registration**, -* **no `core` cipher-trait implementations** (`SymmetricCipher` / `BlockCipherEncryptor` / +* **no `core` cipher-trait implementations** (`BlockCipherEncryptor` / `BlockCipherDecryptor`) — those traits are about encrypting *data* and generating initialisation data, which are mode-of-operation concerns, * **no `AlgorithmOID`** — NIST CSOR assigns AES OIDs per mode, never to the bare cipher. diff --git a/crypto/core-test-framework/src/symmetric_ciphers.rs b/crypto/core-test-framework/src/symmetric_ciphers.rs index 2aa5f8d4..98bb5e75 100644 --- a/crypto/core-test-framework/src/symmetric_ciphers.rs +++ b/crypto/core-test-framework/src/symmetric_ciphers.rs @@ -7,7 +7,7 @@ use bouncycastle_core::key_material::{ }; use bouncycastle_core::traits::{ AEADCipher, BlockCipherDecryptor, BlockCipherEncryptor, SecurityStrength, - StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipher, SymmetricCipherDecryptor, + StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, }; @@ -27,95 +27,6 @@ impl TestFrameworkSymmetricCipher { Self { required_alignment: 1 } } - /// Test all the members of trait SymmetricCipher against the given input-output pair. - /// This gives good baseline test coverage, but is not exhaustive. - pub fn test< - const KEY_LEN: usize, - const INIT_DATA_LEN: usize, - C: SymmetricCipher, - >( - &self, - ) { - let msg = b"The quick brown fox jumps over the lazy dog"; - - let key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); - - // one-shot API - let mut ct = [0u8; 1024]; - let (iv, ct_bytes_written) = C::encrypt_out(&key, msg, &mut ct).unwrap(); - assert_ne!(ct_bytes_written, 0); - - let mut pt = [0u8; 1024]; - let pt_bytes_written = C::decrypt_out(&key, iv, &ct[..ct_bytes_written], &mut pt).unwrap(); - assert_ne!(pt_bytes_written, 0); - assert_eq!(msg, &pt[..pt_bytes_written]); - - // todo -- add tests for encrypt() / decrypt() wrapped in a #[cfg(std)] - - // messing with the ciphertext does not give back the same plaintext (or failing to decrypt is also ok) - ct[17] ^= 0xFF; - match C::decrypt_out(&key, iv, &ct[..ct_bytes_written], &mut pt) { - Ok(bytes_written) => { - // so it decrypted something, but it had better not match the original plaintext - assert_eq!(bytes_written, pt_bytes_written); - assert_ne!(&pt[..bytes_written], msg); - } - Err(SymmetricCipherError::DecryptionFailed) => { /* also ok */ } - _ => panic!("Unexpected error"), - }; - - // error case: KeyMaterial of wrong type - let mac_key = - KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KEY_LEN], KeyType::MACKey) - .unwrap(); - match C::encrypt_out(&mac_key, msg, &mut ct) { - Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } - _ => panic!("Unexpected error"), - }; - - // error case: security strengths too weak and too strong - let mut key = KeyMaterial::::from_bytes_as_type( - &DUMMY_SEED[..KEY_LEN], - KeyType::SymmetricCipherKey, - ) - .unwrap(); - let security_strengths = [ - SecurityStrength::None, - SecurityStrength::_112bit, - SecurityStrength::_128bit, - SecurityStrength::_192bit, - SecurityStrength::_256bit, - ]; - for ss in security_strengths.iter() { - // Tag the key at an arbitrary strength for the purpose of this test. Inside a - // do_hazardous_operations() closure, set_security_strength() raises the strength - // (and bypasses the key-length guard) without complaining. - do_hazardous_operations(&mut key, |key| key.set_security_strength(ss.clone())).unwrap(); - - match C::encrypt_out(&key, msg, &mut ct) { - Ok(_) => { - if ss >= &C::MAX_SECURITY_STRENGTH { /* good */ - } else { - panic!("Should have been a strong enough key"); - } - } - Err(SymmetricCipherError::KeyMaterialError(_)) => { - if ss < &C::MAX_SECURITY_STRENGTH { /* good */ - } else { - panic!("Should not have accepted a key weaker than algorithm"); - } - } - _ => panic!("Unexpected error"), - }; - } - } -} - -impl TestFrameworkSymmetricCipher { /// Exercises the [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`] contract for a /// paired implementor. /// @@ -542,6 +453,107 @@ impl TestFrameworkAEADCipher { Self {} } + /// Tests the plain one-shots -- [`AEADCipher::encrypt_out`] and + /// [`AEADCipher::decrypt_out`], which take no additional authenticated data. + /// + /// These four methods were the former `SymmetricCipher` trait, and this was its suite; they now + /// belong to `AEADCipher`, so the suite comes with them. Called by + /// [`test`](Self::test), so an implementor gets it without asking, and public so it can be run + /// on its own. + pub fn test_plain_one_shots< + const KEY_LEN: usize, + const NONCE_LEN: usize, + const TAG_LEN: usize, + C: AEADCipher, + >( + &self, + ) { + let msg = b"The quick brown fox jumps over the lazy dog"; + + let key = KeyMaterial::::from_bytes_as_type( + &DUMMY_SEED[..KEY_LEN], + KeyType::SymmetricCipherKey, + ) + .unwrap(); + + // one-shot API + let mut ct = [0u8; 1024]; + let (iv, ct_bytes_written) = C::encrypt_out(&key, msg, &mut ct).unwrap(); + assert_ne!(ct_bytes_written, 0); + + let mut pt = [0u8; 1024]; + let pt_bytes_written = C::decrypt_out(&key, iv, &ct[..ct_bytes_written], &mut pt).unwrap(); + assert_ne!(pt_bytes_written, 0); + assert_eq!(msg, &pt[..pt_bytes_written]); + + // todo -- add tests for encrypt() / decrypt() wrapped in a #[cfg(std)] + + // messing with the ciphertext does not give back the same plaintext (or failing to decrypt is also ok) + ct[17] ^= 0xFF; + match C::decrypt_out(&key, iv, &ct[..ct_bytes_written], &mut pt) { + Ok(bytes_written) => { + // so it decrypted something, but it had better not match the original plaintext + assert_eq!(bytes_written, pt_bytes_written); + assert_ne!(&pt[..bytes_written], msg); + } + Err(SymmetricCipherError::DecryptionFailed) => { /* also ok */ } + _ => panic!("Unexpected error"), + }; + + // error case: KeyMaterial of wrong type + let mac_key = + KeyMaterial::::from_bytes_as_type(&DUMMY_SEED[..KEY_LEN], KeyType::MACKey) + .unwrap(); + match C::encrypt_out(&mac_key, msg, &mut ct) { + Err(SymmetricCipherError::KeyMaterialError(_)) => { /* good */ } + _ => panic!("Unexpected error"), + }; + + // error case: security strengths too weak and too strong + let mut key = KeyMaterial::::from_bytes_as_type( + &DUMMY_SEED[..KEY_LEN], + KeyType::SymmetricCipherKey, + ) + .unwrap(); + let security_strengths = [ + SecurityStrength::None, + SecurityStrength::_112bit, + SecurityStrength::_128bit, + SecurityStrength::_192bit, + SecurityStrength::_256bit, + ]; + for ss in security_strengths.iter() { + // `set_security_strength` enforces its key-length guard even inside a + // do_hazardous_operations() closure -- a KEY_LEN-byte key cannot be tagged at a + // strength above `from_bytes(KEY_LEN)` -- so skip the strengths this key cannot carry + // rather than unwrapping an error. (A 16-byte key can reach 128-bit and no higher.) + // Do NOT "fix" this by relaxing that guard in `KeyMaterial`: core's + // `test_hazardous_ops_error_handling` requires it to stay enforced. + if ss > &SecurityStrength::from_bytes(KEY_LEN) { + continue; + } + + // Tag the key at an arbitrary strength for the purpose of this test. + do_hazardous_operations(&mut key, |key| key.set_security_strength(ss.clone())).unwrap(); + + match C::encrypt_out(&key, msg, &mut ct) { + Ok(_) => { + if ss >= &C::MAX_SECURITY_STRENGTH { /* good */ + } else { + panic!("Should have been a strong enough key"); + } + } + Err(SymmetricCipherError::KeyMaterialError(_)) => { + if ss < &C::MAX_SECURITY_STRENGTH { /* good */ + } else { + panic!("Should not have accepted a key weaker than algorithm"); + } + } + _ => panic!("Unexpected error"), + }; + } + } + /// Test all the members of trait AEADCipher against the given input-output pair. /// This gives good baseline test coverage, but is not exhaustive. pub fn test< @@ -552,6 +564,9 @@ impl TestFrameworkAEADCipher { >( &self, ) { + // The plain one-shots this trait absorbed from the former `SymmetricCipher`. + self.test_plain_one_shots::(); + let msg = b"The quick brown fox jumps over the lazy dog"; let aad = b"some associated data"; @@ -645,13 +660,21 @@ impl TestFrameworkAEADCipher { SecurityStrength::_256bit, ]; for ss in security_strengths.iter() { - // Tag the key at an arbitrary strength for the purpose of this test. Inside a - // do_hazardous_operations() closure, set_security_strength() raises the strength - // (and bypasses the key-length guard) without complaining. + // `set_security_strength` enforces its key-length guard even inside a + // do_hazardous_operations() closure -- a KEY_LEN-byte key cannot be tagged at a + // strength above `from_bytes(KEY_LEN)` -- so skip the strengths this key cannot carry + // rather than unwrapping an error. (A 16-byte key can reach 128-bit and no higher.) + // Do NOT "fix" this by relaxing that guard in `KeyMaterial`: core's + // `test_hazardous_ops_error_handling` requires it to stay enforced. + if ss > &SecurityStrength::from_bytes(KEY_LEN) { + continue; + } + + // Tag the key at an arbitrary strength for the purpose of this test. do_hazardous_operations(&mut key, |key| key.set_security_strength(ss.clone())).unwrap(); // The key-strength requirement must be enforced both by the AEAD one-shot and by the - // inherited SymmetricCipher one-shot (encrypt_out), so exercise both. + // plain one (encrypt_out), so exercise both. let check_strength = |result: Result<(), SymmetricCipherError>| match result { Ok(_) => { if ss >= &C::MAX_SECURITY_STRENGTH { /* good */ diff --git a/crypto/core-test-framework/summary.md b/crypto/core-test-framework/summary.md index 40176e7c..51e5baa4 100644 --- a/crypto/core-test-framework/summary.md +++ b/crypto/core-test-framework/summary.md @@ -124,15 +124,15 @@ The identical loop appears in two other suites in | Suite | Loop at | Implementors in tree | Status | |---|---|---|---| -| `TestFrameworkSymmetricCipher` | line 87 | 0 | latent, unfixed | +| `TestFrameworkSymmetricCipher::test` | line 87 | 0 | **gone**: the `SymmetricCipher` trait was deleted and its suite moved to `TestFrameworkAEADCipher::test_plain_one_shots`, guarded on the way | | `TestFrameworkBlockCipher` | line 240 | 1 (`crypto/modes`) | **fixed** | -| `TestFrameworkAEADCipher` | line 386 | 0 | latent, unfixed | +| `TestFrameworkAEADCipher` | line 386 | 0 | **fixed** | | `TestFrameworkStreamCipher` | in `test` | 2 (`crypto/modes`: `Cfb`, `Cfb8`) | **fixed** (written later, with the guard) | -Both unfixed suites will panic the first time anything implements their trait with a key shorter -than 32 bytes — which for `AEADCipher` includes ASCON-128 and AES-128-GCM. They were left alone to -keep this change scoped to what CBC needed; the fix is the same three lines in each. Worth doing -before the next implementor arrives rather than after. +Both were later fixed, when `SymmetricCipher` was deleted and its suite moved onto `AEADCipher`. +Until then they would have panicked the first time anything implemented their trait with a key +shorter than 32 bytes — which for `AEADCipher` includes ASCON-128 and AES-128-GCM. Every +security-strength loop in the file now carries the same key-length guard. Note that `TestFrameworkStreamCipher` was a different case when this was written: its `test` was a `todo!()` with no security-strength handling at all, so there was nothing to fix and nothing being @@ -180,7 +180,7 @@ new suites are exercised by: ## 6. Open items -1. **Fix the same loop in `TestFrameworkSymmetricCipher` and `TestFrameworkAEADCipher`** (§3). +1. ~~**Fix the same loop in `TestFrameworkSymmetricCipher` and `TestFrameworkAEADCipher`** (§3).~~ Done. Three lines each, and the next implementor of either trait will otherwise hit the panic. 2. **Decide whether the `Default` impl added to `TestFrameworkElectronicCodeBook` should be added to the other suites** for consistency — they all have `new()` and no `Default`, which clippy diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 5cb42fb1..2b3518ce 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -14,8 +14,67 @@ use crate::key_material::KeyType; /// The basic functions of an Authenticated Encryption with Addititional Data cipher. pub trait AEADCipher: - SymmetricCipher + Sized + Algorithm + Sized { + #[cfg(feature = "std")] + /// A one-shot API to encrypt some plaintext with the given key, with no additional + /// authenticated data. + /// + /// This and the three that follow were the whole of the former `SymmetricCipher` trait, which + /// every symmetric cipher was once expected to implement. They now live here, because an AEAD + /// is the only kind of cipher left that needs them: a block mode reaches the same shape through + /// [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`] and the padding adapters, and a + /// stream mode gets those traits directly. + /// + /// These are meant to be simple, easy to use, secure and fool-proof, at the cost of producing a + /// ciphertext whose layout is this implementation's business: an AEAD has a tag to put + /// somewhere, and where it goes is not fixed here. See the documentation of the underlying + /// implementation before assuming another one will read it. + /// + /// Returns the generated nonce and the ciphertext as a `Vec`, so it needs the `std` + /// feature. For AAD, use [`aead_encrypt`](Self::aead_encrypt). + fn encrypt( + key: &KeyMaterial, + plaintext: &[u8], + ) -> Result<([u8; NONCE_LEN], Vec), SymmetricCipherError>; + + /// As [`encrypt`](Self::encrypt), writing into a caller-supplied buffer so it is available + /// without `std`. + /// + /// See the documentation for the underlying implementation for how big the ciphertext buffer + /// must be; an AEAD needs room for the tag as well as the data. Returns the generated nonce and + /// the number of bytes written. + fn encrypt_out( + key: &KeyMaterial, + plaintext: &[u8], + ciphertext: &mut [u8], + ) -> Result<([u8; NONCE_LEN], usize), SymmetricCipherError>; + + #[cfg(feature = "std")] + /// A one-shot API to decrypt what [`encrypt`](Self::encrypt) produced, with no additional + /// authenticated data. Returns the plaintext as a `Vec`, so it needs the `std` feature. + /// + /// # Errors + /// [`SymmetricCipherError::AEADTagCheckFailed`] if the tag does not verify. The caller learns + /// only that decryption failed. + fn decrypt( + key: &KeyMaterial, + init_data: [u8; NONCE_LEN], + ciphertext: &[u8], + ) -> Result, SymmetricCipherError>; + + /// As [`decrypt`](Self::decrypt), writing into a caller-supplied buffer so it is available + /// without `std`. Returns the number of bytes written. + /// + /// # Errors + /// As [`decrypt`](Self::decrypt). + fn decrypt_out( + key: &KeyMaterial, + init_data: [u8; NONCE_LEN], + ciphertext: &[u8], + plaintext: &mut [u8], + ) -> Result; + #[cfg(feature = "std")] /// A one-shot API to encrypt some plaintext with the given key. /// A distinguishing feature of AEAD ciphers is the ability to provide additional authenticated data (AAD) @@ -1270,58 +1329,6 @@ pub trait SuspendableKeyed: Sized { ) -> Result; } -// todo -- migrate AEADCipher onto SymmetricCipherEncryptor / SymmetricCipherDecryptor (below), -// which are the split form of this trait, and retire this one. (StreamCipher has already gone: -// its split form is StreamCipherEncryptor / StreamCipherDecryptor.) -/// The basic one-shot encrypt and decrypt that all types of symmetric ciphers must implement. -/// These are meant to be simple, easy to use, secure, and fool-proof APIs, but they may result in -/// ciphertexts that are incompatible with other implementations as ciphers in more complex modes, such -/// as AEADs or stream ciphers may need to stick extra data either at the beginning or end of the ciphertext. -/// See the documentation of the underlying implementation for more details. -pub trait SymmetricCipher: Algorithm { - #[cfg(feature = "std")] - /// A one-shot API to encrypt some plaintext with the given key. - /// This function returns the ciphertext as a `Vec`, and therefore is only available when compiling with std. - /// Returns a tuple containing the initialization data and the ciphertext. - /// This is not available if building for no_std. - fn encrypt( - key: &KeyMaterial, - plaintext: &[u8], - ) -> Result<([u8; INIT_DATA_LEN], Vec), SymmetricCipherError>; - /// A one-shot API to encrypt some plaintext with the given key. - /// This function takes a reference to the output buffer for the ciphertext, and is therefore available in no_std. - /// See the documentation for the underlying implementation for details on providing a ciphertext buffer of sufficient size; - /// typically the ciphertext is the same length as the plaintext, but some ciphers may have an expansion factor or require - /// extra space for a nonce or tag. - /// Returns a tuple containing the initialization data and the number of bytes written to the ciphertext buffer. - fn encrypt_out( - key: &KeyMaterial, - plaintext: &[u8], - ciphertext: &mut [u8], - ) -> Result<([u8; INIT_DATA_LEN], usize), SymmetricCipherError>; - #[cfg(feature = "std")] - /// A one-shot API to decrypt some ciphertext with the given key. - /// This function returns the ciphertext as a `Vec`, and therefore is only available when compiling with std. - /// This is not available if building for no_std. - fn decrypt( - key: &KeyMaterial, - init_data: [u8; INIT_DATA_LEN], - ciphertext: &[u8], - ) -> Result, SymmetricCipherError>; - /// A one-shot API to decrypt some ciphertext with the given key. - /// This function takes a reference to the output buffer for the plaintext, and is therefore available in no_std. - /// See the documentation for the underlying implementation for details on providing a plaintext buffer of sufficient size; - /// typically the ciphertext is the same length as the plaintext, but some ciphers may have an expansion factor or require - /// extra space for a nonce or tag. - /// Returns a tuple containing the initialization data and the number of bytes written to the plaintext buffer. - fn decrypt_out( - key: &KeyMaterial, - init_data: [u8; INIT_DATA_LEN], - ciphertext: &[u8], - plaintext: &mut [u8], - ) -> Result; -} - /// The decryption half of a symmetric cipher's arbitrary-length API. See /// [`SymmetricCipherEncryptor`] for the shape of the API and the meaning of `FINAL_LEN`; this is /// its mirror image, and the two are implemented by paired types. From 09866292a8195194cd3d2f8801199c18c7ec4860 Mon Sep 17 00:00:00 2001 From: David Hook Date: Mon, 7 Sep 2026 13:02:34 +1000 Subject: [PATCH 04/14] aes: rename bouncycastle-aes-lowmemory to bouncycastle-aes --- Cargo.toml | 4 +-- alpha_0.1.3_release_notes.md | 6 ++-- cli/src/aes_cbc_cmd.rs | 2 +- cli/src/aes_cfb8_cmd.rs | 2 +- cli/src/aes_cfb_cmd.rs | 2 +- cli/src/aes_ctr_cmd.rs | 2 +- cli/src/aes_ecb_cmd.rs | 2 +- cli/tests/aes_cfb_cli_tests.rs | 2 +- crypto/{aes-lowmemory => aes}/Cargo.toml | 2 +- .../benches/aes_benches.rs | 10 +++---- crypto/{aes-lowmemory => aes}/src/aes.rs | 0 crypto/{aes-lowmemory => aes}/src/bitslice.rs | 0 crypto/{aes-lowmemory => aes}/src/cbc.rs | 12 ++++---- crypto/{aes-lowmemory => aes}/src/cfb.rs | 6 ++-- crypto/{aes-lowmemory => aes}/src/cfb8.rs | 6 ++-- crypto/{aes-lowmemory => aes}/src/ctr.rs | 6 ++-- crypto/{aes-lowmemory => aes}/src/ecb.rs | 8 ++--- crypto/{aes-lowmemory => aes}/src/lib.rs | 6 ++-- .../{aes-lowmemory => aes}/src/padded_mode.rs | 0 crypto/{aes-lowmemory => aes}/src/round.rs | 0 crypto/{aes-lowmemory => aes}/src/sbox.rs | 0 crypto/{aes-lowmemory => aes}/src/schedule.rs | 0 crypto/{aes-lowmemory => aes}/summary.md | 30 +++++++++---------- .../tests/acvp_tests.rs | 2 +- .../tests/cbc_alias_tests.rs | 2 +- .../tests/ecb_alias_tests.rs | 2 +- .../tests/electronic_code_book_tests.rs | 2 +- .../tests/fips197_tests.rs | 2 +- .../tests/sp800_38a_tests.rs | 2 +- crypto/core-test-framework/summary.md | 8 ++--- crypto/core/src/traits.rs | 2 +- crypto/modes/Cargo.toml | 2 +- crypto/modes/benches/modes_benches.rs | 2 +- crypto/modes/src/ctr.rs | 6 ++-- crypto/modes/src/lib.rs | 24 +++++++-------- crypto/modes/tests/acvp_cfb8_tests.rs | 6 ++-- crypto/modes/tests/acvp_cfb_tests.rs | 6 ++-- crypto/modes/tests/acvp_ctr_tests.rs | 2 +- crypto/modes/tests/acvp_ecb_tests.rs | 4 +-- crypto/modes/tests/acvp_tests.rs | 6 ++-- crypto/modes/tests/cbc_tests.rs | 2 +- crypto/modes/tests/cfb8_tests.rs | 2 +- crypto/modes/tests/cfb_tests.rs | 2 +- crypto/modes/tests/ctr_bc_java_tests.rs | 2 +- crypto/modes/tests/ctr_tests.rs | 2 +- crypto/modes/tests/ctr_vector_tests.rs | 2 +- crypto/modes/tests/ecb_tests.rs | 2 +- crypto/modes/tests/sp800_38a_cfb8_tests.rs | 2 +- crypto/modes/tests/sp800_38a_cfb_tests.rs | 2 +- crypto/modes/tests/sp800_38a_ecb_tests.rs | 2 +- crypto/modes/tests/sp800_38a_tests.rs | 2 +- .../modes/tests/symmetric_cipher_api_tests.rs | 2 +- mem_usage_benches/bench_aes_mem_usage.rs | 2 +- src/lib.rs | 2 +- 54 files changed, 108 insertions(+), 108 deletions(-) rename crypto/{aes-lowmemory => aes}/Cargo.toml (94%) rename crypto/{aes-lowmemory => aes}/benches/aes_benches.rs (94%) rename crypto/{aes-lowmemory => aes}/src/aes.rs (100%) rename crypto/{aes-lowmemory => aes}/src/bitslice.rs (100%) rename crypto/{aes-lowmemory => aes}/src/cbc.rs (96%) rename crypto/{aes-lowmemory => aes}/src/cfb.rs (96%) rename crypto/{aes-lowmemory => aes}/src/cfb8.rs (96%) rename crypto/{aes-lowmemory => aes}/src/ctr.rs (96%) rename crypto/{aes-lowmemory => aes}/src/ecb.rs (97%) rename crypto/{aes-lowmemory => aes}/src/lib.rs (98%) rename crypto/{aes-lowmemory => aes}/src/padded_mode.rs (100%) rename crypto/{aes-lowmemory => aes}/src/round.rs (100%) rename crypto/{aes-lowmemory => aes}/src/sbox.rs (100%) rename crypto/{aes-lowmemory => aes}/src/schedule.rs (100%) rename crypto/{aes-lowmemory => aes}/summary.md (96%) rename crypto/{aes-lowmemory => aes}/tests/acvp_tests.rs (99%) rename crypto/{aes-lowmemory => aes}/tests/cbc_alias_tests.rs (98%) rename crypto/{aes-lowmemory => aes}/tests/ecb_alias_tests.rs (98%) rename crypto/{aes-lowmemory => aes}/tests/electronic_code_book_tests.rs (93%) rename crypto/{aes-lowmemory => aes}/tests/fips197_tests.rs (99%) rename crypto/{aes-lowmemory => aes}/tests/sp800_38a_tests.rs (98%) diff --git a/Cargo.toml b/Cargo.toml index 557468b9..63f0d999 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ version = "0.1.3" # *** Internal Dependencies *** bouncycastle = { path = "./" } -bouncycastle-aes-lowmemory = { path = "./crypto/aes-lowmemory" } +bouncycastle-aes = { path = "./crypto/aes" } bouncycastle-base64 = { path = "./crypto/base64" } bouncycastle-modes = { path = "./crypto/modes" } bouncycastle-core = { path = "crypto/core" } @@ -45,7 +45,7 @@ version.workspace = true edition.workspace = true [dependencies] -bouncycastle-aes-lowmemory.workspace = true +bouncycastle-aes.workspace = true bouncycastle-base64.workspace = true bouncycastle-core.workspace = true bouncycastle-factory.workspace = true diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index 101c7522..d3347c61 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -11,7 +11,7 @@ * Test vectors are the GB/T 32905-2016 Appendix A examples plus the bc-java `SM3DigestTest` / `HMac` vectors, with additional digests cross-checked against OpenSSL and bc-java. -New crate `bouncycastle-aes-lowmemory` (`bouncycastle::aes_lowmemory`): AES-128/192/256 as a raw keyed block +New crate `bouncycastle-aes` (`bouncycastle::aes`): AES-128/192/256 as a raw keyed block permutation (NIST FIPS 197), re-exported from the umbrella crate. * **Constant-time and table-free.** The S-box is evaluated as a Boolean circuit -- the 113-gate Boyar-Peralta @@ -358,7 +358,7 @@ ECB (`Ecb`), SP 800-38A Sec 6.1: * Verified against all six SP 800-38A **Appendix F.1** vectors (ECB-AES128/192/256, Encrypt and Decrypt) in five groupings each -- and, since there is no IV, `encrypt` is checked against the published ciphertext too, through the streaming API and the one-shot. Each tabulated ciphertext block is also checked to be `CIPH_K` of its plaintext block - through the raw permutation. The **NIST ACVP `ACVP-AES-ECB`** set (2138 AFT cases) already used by `aes-lowmemory` + through the raw permutation. The **NIST ACVP `ACVP-AES-ECB`** set (2138 AFT cases) already used by `aes` is run again through the mode API, both directions, in three groupings including one that reaches the eight-block path. Structural tests pin the Sec 6.1 equations against a reference over the toy permutation, determinism and the codebook property, Appendix D error propagation (a corrupted block randomises itself and nothing else, checked over @@ -369,7 +369,7 @@ keyed permutation -- `CIPH_K` / `CIPH^-1_K` of SP 800-38A Sec 5.1 -- that a mode `new`, `encrypt_block`, `decrypt_block`, plus provided `encrypt_blocks2` / `decrypt_blocks2` that default to two single-block calls and `encrypt_blocks8` / `decrypt_blocks8` that default to four pair calls, all of which bit-sliced implementations override (AES the pair form, SM4 both). The block methods -are infallible; only `new` can fail, and only on the key. `bouncycastle-aes-lowmemory` implements +are infallible; only `new` can fail, and only on the key. `bouncycastle-aes` implements it for all three key lengths (the data-encryption traits are still deliberately not implemented there). diff --git a/cli/src/aes_cbc_cmd.rs b/cli/src/aes_cbc_cmd.rs index d8f4a72c..1fc288ee 100644 --- a/cli/src/aes_cbc_cmd.rs +++ b/cli/src/aes_cbc_cmd.rs @@ -10,7 +10,7 @@ //! separately. use crate::block_mode_cmd::{BLOCK_LEN, BlockModeAction, decrypt_stream, encrypt_stream, load_key}; -use bouncycastle::aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle::aes::{Aes128, Aes192, Aes256}; use bouncycastle::core::key_material::KeyMaterial; use bouncycastle::core::traits::ElectronicCodeBook; use bouncycastle::modes::{Cbc, Decrypting, Encrypting}; diff --git a/cli/src/aes_cfb8_cmd.rs b/cli/src/aes_cfb8_cmd.rs index 29a9e474..74eacce5 100644 --- a/cli/src/aes_cfb8_cmd.rs +++ b/cli/src/aes_cfb8_cmd.rs @@ -27,7 +27,7 @@ use crate::block_mode_cmd::{BLOCK_LEN, BlockModeAction, load_key}; use crate::stream_mode_cmd::run_stream_mode; -use bouncycastle::aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle::aes::{Aes128, Aes192, Aes256}; use bouncycastle::core::key_material::KeyMaterial; use bouncycastle::core::traits::ElectronicCodeBook; use bouncycastle::modes::{Cfb8, Decrypting, Encrypting}; diff --git a/cli/src/aes_cfb_cmd.rs b/cli/src/aes_cfb_cmd.rs index dde4491a..4b40690d 100644 --- a/cli/src/aes_cfb_cmd.rs +++ b/cli/src/aes_cfb_cmd.rs @@ -28,7 +28,7 @@ use crate::block_mode_cmd::{BLOCK_LEN, BlockModeAction, load_key}; use crate::stream_mode_cmd::run_stream_mode; -use bouncycastle::aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle::aes::{Aes128, Aes192, Aes256}; use bouncycastle::core::key_material::KeyMaterial; use bouncycastle::core::traits::ElectronicCodeBook; use bouncycastle::modes::{Cfb, Decrypting, Encrypting}; diff --git a/cli/src/aes_ctr_cmd.rs b/cli/src/aes_ctr_cmd.rs index 611b64c0..b125c5cd 100644 --- a/cli/src/aes_ctr_cmd.rs +++ b/cli/src/aes_ctr_cmd.rs @@ -35,7 +35,7 @@ use crate::block_mode_cmd::{BLOCK_LEN, BlockModeAction, load_key}; use crate::stream_mode_cmd::run_stream_mode; -use bouncycastle::aes_lowmemory::{Aes128, Aes192, Aes256, CTR_NONCE_LEN}; +use bouncycastle::aes::{Aes128, Aes192, Aes256, CTR_NONCE_LEN}; use bouncycastle::core::key_material::KeyMaterial; use bouncycastle::core::traits::ElectronicCodeBook; use bouncycastle::modes::{Ctr, Decrypting, Encrypting}; diff --git a/cli/src/aes_ecb_cmd.rs b/cli/src/aes_ecb_cmd.rs index d4dc6f4a..d21af91d 100644 --- a/cli/src/aes_ecb_cmd.rs +++ b/cli/src/aes_ecb_cmd.rs @@ -16,7 +16,7 @@ //! `aes*-cbc` or `aes*-cfb` under separate authentication, or better an AEAD. use crate::block_mode_cmd::{BLOCK_LEN, BlockModeAction, decrypt_stream, encrypt_stream, load_key}; -use bouncycastle::aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle::aes::{Aes128, Aes192, Aes256}; use bouncycastle::core::key_material::KeyMaterial; use bouncycastle::core::traits::ElectronicCodeBook; use bouncycastle::modes::{Decrypting, Ecb, Encrypting}; diff --git a/cli/tests/aes_cfb_cli_tests.rs b/cli/tests/aes_cfb_cli_tests.rs index 337d815a..e402fca8 100644 --- a/cli/tests/aes_cfb_cli_tests.rs +++ b/cli/tests/aes_cfb_cli_tests.rs @@ -386,7 +386,7 @@ fn an_unaligned_message_matches_the_library() { use bouncycastle::core::traits::StreamCipherDecryptor; use bouncycastle::modes::{Cfb, Decrypting}; - type Aes128Cfb = Cfb; + type Aes128Cfb = Cfb; for len in [5usize, 17, 1000, 1024, 1025, 4099] { let plaintext = pseudo_random(len, len as u32); diff --git a/crypto/aes-lowmemory/Cargo.toml b/crypto/aes/Cargo.toml similarity index 94% rename from crypto/aes-lowmemory/Cargo.toml rename to crypto/aes/Cargo.toml index c0afefae..f1bd1678 100644 --- a/crypto/aes-lowmemory/Cargo.toml +++ b/crypto/aes/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bouncycastle-aes-lowmemory" +name = "bouncycastle-aes" version.workspace = true edition.workspace = true diff --git a/crypto/aes-lowmemory/benches/aes_benches.rs b/crypto/aes/benches/aes_benches.rs similarity index 94% rename from crypto/aes-lowmemory/benches/aes_benches.rs rename to crypto/aes/benches/aes_benches.rs index 82d81003..22b9f51b 100644 --- a/crypto/aes-lowmemory/benches/aes_benches.rs +++ b/crypto/aes/benches/aes_benches.rs @@ -6,7 +6,7 @@ //! argument for modes of operation using the two-block entry points wherever their blocks are //! independent (CTR, and the decrypt direction of CBC and CFB). -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256, BLOCK_LEN}; +use bouncycastle_aes::{Aes128, Aes192, Aes256, BLOCK_LEN}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::RNG; use bouncycastle_rng as rng; @@ -33,7 +33,7 @@ fn key() -> KeyMaterial { } fn bench_key_expansion(c: &mut Criterion) { - let mut group = c.benchmark_group("aes_lowmemory::key expansion"); + let mut group = c.benchmark_group("aes::key expansion"); let key128 = key::<16>(); group.bench_function("Aes128::new()", |b| { @@ -57,7 +57,7 @@ fn bench_aes128(c: &mut Criterion) { let aes = Aes128::new(&key::<16>()).unwrap(); let blocks = random_blocks(); - let mut group = c.benchmark_group("aes_lowmemory::Aes128"); + let mut group = c.benchmark_group("aes::Aes128"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); group.bench_function("16KiB -- .encrypt_block() x1024", |b| { @@ -110,7 +110,7 @@ fn bench_aes192(c: &mut Criterion) { let aes = Aes192::new(&key::<24>()).unwrap(); let blocks = random_blocks(); - let mut group = c.benchmark_group("aes_lowmemory::Aes192"); + let mut group = c.benchmark_group("aes::Aes192"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); group.bench_function("16KiB -- .encrypt_block() x1024", |b| { @@ -141,7 +141,7 @@ fn bench_aes256(c: &mut Criterion) { let aes = Aes256::new(&key::<32>()).unwrap(); let blocks = random_blocks(); - let mut group = c.benchmark_group("aes_lowmemory::Aes256"); + let mut group = c.benchmark_group("aes::Aes256"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); group.bench_function("16KiB -- .encrypt_block() x1024", |b| { diff --git a/crypto/aes-lowmemory/src/aes.rs b/crypto/aes/src/aes.rs similarity index 100% rename from crypto/aes-lowmemory/src/aes.rs rename to crypto/aes/src/aes.rs diff --git a/crypto/aes-lowmemory/src/bitslice.rs b/crypto/aes/src/bitslice.rs similarity index 100% rename from crypto/aes-lowmemory/src/bitslice.rs rename to crypto/aes/src/bitslice.rs diff --git a/crypto/aes-lowmemory/src/cbc.rs b/crypto/aes/src/cbc.rs similarity index 96% rename from crypto/aes-lowmemory/src/cbc.rs rename to crypto/aes/src/cbc.rs index 337f9ee1..c8486ced 100644 --- a/crypto/aes-lowmemory/src/cbc.rs +++ b/crypto/aes/src/cbc.rs @@ -64,7 +64,7 @@ use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; /// returned; it is never supplied. /// /// ``` -/// use bouncycastle_aes_lowmemory::AES_CBC_128; +/// use bouncycastle_aes::AES_CBC_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; @@ -91,7 +91,7 @@ use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; /// error at `do_final` rather than something silently padded: /// /// ``` -/// use bouncycastle_aes_lowmemory::AES_CBC_128; +/// use bouncycastle_aes::AES_CBC_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::SymmetricCipherEncryptor; /// use bouncycastle_modes::Encrypting; @@ -115,7 +115,7 @@ use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; /// interchanged. A value built with one will not satisfy a binding annotated with the other: /// /// ```compile_fail -/// use bouncycastle_aes_lowmemory::AES_CBC_128; +/// use bouncycastle_aes::AES_CBC_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::SymmetricCipherEncryptor; /// use bouncycastle_modes::Encrypting; @@ -132,7 +132,7 @@ use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; /// meaningful rather than incidental: /// /// ``` -/// use bouncycastle_aes_lowmemory::AES_CBC_128; +/// use bouncycastle_aes::AES_CBC_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::SymmetricCipherEncryptor; /// use bouncycastle_modes::Encrypting; @@ -155,7 +155,7 @@ pub type AES_CBC_128 = = = Cfb; /// AES-192 in CFB128 mode. See [`AES_CFB_128`]. /// /// ``` -/// use bouncycastle_aes_lowmemory::AES_CFB_192; +/// use bouncycastle_aes::AES_CFB_192; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; @@ -71,7 +71,7 @@ pub type AES_CFB_192 = Cfb; /// AES-256 in CFB128 mode. See [`AES_CFB_128`]. /// /// ``` -/// use bouncycastle_aes_lowmemory::AES_CFB_256; +/// use bouncycastle_aes::AES_CFB_256; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; diff --git a/crypto/aes-lowmemory/src/cfb8.rs b/crypto/aes/src/cfb8.rs similarity index 96% rename from crypto/aes-lowmemory/src/cfb8.rs rename to crypto/aes/src/cfb8.rs index 505e7c35..cea63042 100644 --- a/crypto/aes-lowmemory/src/cfb8.rs +++ b/crypto/aes/src/cfb8.rs @@ -21,7 +21,7 @@ use bouncycastle_modes::Cfb8; /// returned; it is never supplied. Encryption and decryption work in place. /// /// ``` -/// use bouncycastle_aes_lowmemory::{AES_CFB8_128, AES_CFB_128}; +/// use bouncycastle_aes::{AES_CFB8_128, AES_CFB_128}; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; @@ -61,7 +61,7 @@ pub type AES_CFB8_128 = Cfb8; /// AES-192 in CFB8 mode. See [`AES_CFB8_128`]. /// /// ``` -/// use bouncycastle_aes_lowmemory::AES_CFB8_192; +/// use bouncycastle_aes::AES_CFB8_192; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; @@ -78,7 +78,7 @@ pub type AES_CFB8_192 = Cfb8; /// AES-256 in CFB8 mode. See [`AES_CFB8_128`]. /// /// ``` -/// use bouncycastle_aes_lowmemory::AES_CFB8_256; +/// use bouncycastle_aes::AES_CFB8_256; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; diff --git a/crypto/aes-lowmemory/src/ctr.rs b/crypto/aes/src/ctr.rs similarity index 96% rename from crypto/aes-lowmemory/src/ctr.rs rename to crypto/aes/src/ctr.rs index 5c6dc40a..73be8e40 100644 --- a/crypto/aes-lowmemory/src/ctr.rs +++ b/crypto/aes/src/ctr.rs @@ -27,7 +27,7 @@ pub const CTR_NONCE_LEN: usize = 12; /// supplied. Encryption and decryption work in place, and are the same operation. /// /// ``` -/// use bouncycastle_aes_lowmemory::AES_CTR_128; +/// use bouncycastle_aes::AES_CTR_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; @@ -60,7 +60,7 @@ pub type AES_CTR_128 = Ctr; /// AES-192 in CTR mode with a 12-byte nonce. See [`AES_CTR_128`]. /// /// ``` -/// use bouncycastle_aes_lowmemory::AES_CTR_192; +/// use bouncycastle_aes::AES_CTR_192; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; @@ -77,7 +77,7 @@ pub type AES_CTR_192 = Ctr; /// AES-256 in CTR mode with a 12-byte nonce. See [`AES_CTR_128`]. /// /// ``` -/// use bouncycastle_aes_lowmemory::AES_CTR_256; +/// use bouncycastle_aes::AES_CTR_256; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; diff --git a/crypto/aes-lowmemory/src/ecb.rs b/crypto/aes/src/ecb.rs similarity index 97% rename from crypto/aes-lowmemory/src/ecb.rs rename to crypto/aes/src/ecb.rs index a8605bdf..e24da5c5 100644 --- a/crypto/aes-lowmemory/src/ecb.rs +++ b/crypto/aes/src/ecb.rs @@ -59,7 +59,7 @@ use bouncycastle_padding::{NoPadding, PKCS7}; /// does not make it safe. /// /// ``` -/// use bouncycastle_aes_lowmemory::AES_ECB_128; +/// use bouncycastle_aes::AES_ECB_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; @@ -84,7 +84,7 @@ use bouncycastle_padding::{NoPadding, PKCS7}; /// plaintext blocks still give two identical ciphertext blocks. /// /// ``` -/// use bouncycastle_aes_lowmemory::AES_ECB_128; +/// use bouncycastle_aes::AES_ECB_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::SymmetricCipherEncryptor; /// use bouncycastle_modes::Encrypting; @@ -110,7 +110,7 @@ pub type AES_ECB_128 = = ::from_bytes_as_type( @@ -43,7 +43,7 @@ //! [`Aes::encrypt_block`] calls: //! //! ``` -//! use bouncycastle_aes_lowmemory::Aes256; +//! use bouncycastle_aes::Aes256; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! //! let key = KeyMaterial::<32>::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) @@ -77,7 +77,7 @@ //! [`AES_CBC_128`] for why the scheme is named in the type. //! //! ``` -//! use bouncycastle_aes_lowmemory::AES_CBC_256; +//! use bouncycastle_aes::AES_CBC_256; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; //! use bouncycastle_modes::{Decrypting, Encrypting}; diff --git a/crypto/aes-lowmemory/src/padded_mode.rs b/crypto/aes/src/padded_mode.rs similarity index 100% rename from crypto/aes-lowmemory/src/padded_mode.rs rename to crypto/aes/src/padded_mode.rs diff --git a/crypto/aes-lowmemory/src/round.rs b/crypto/aes/src/round.rs similarity index 100% rename from crypto/aes-lowmemory/src/round.rs rename to crypto/aes/src/round.rs diff --git a/crypto/aes-lowmemory/src/sbox.rs b/crypto/aes/src/sbox.rs similarity index 100% rename from crypto/aes-lowmemory/src/sbox.rs rename to crypto/aes/src/sbox.rs diff --git a/crypto/aes-lowmemory/src/schedule.rs b/crypto/aes/src/schedule.rs similarity index 100% rename from crypto/aes-lowmemory/src/schedule.rs rename to crypto/aes/src/schedule.rs diff --git a/crypto/aes-lowmemory/summary.md b/crypto/aes/summary.md similarity index 96% rename from crypto/aes-lowmemory/summary.md rename to crypto/aes/summary.md index 4978e696..7f0e3261 100644 --- a/crypto/aes-lowmemory/summary.md +++ b/crypto/aes/summary.md @@ -1,4 +1,4 @@ -# `crypto/aes-lowmemory` — implementation summary +# `crypto/aes` — implementation summary A constant-time, table-free AES block cipher engine (NIST FIPS 197), added on branch `feature/officialfrancismendoza/100-AES-lightengine-CBC-mode`. @@ -206,9 +206,9 @@ half is never returned either way. ### Changed elsewhere -* `Cargo.toml` — `bouncycastle-aes-lowmemory` in `workspace.dependencies` and in the umbrella +* `Cargo.toml` — `bouncycastle-aes` in `workspace.dependencies` and in the umbrella `[dependencies]`. -* `src/lib.rs` — `pub use bouncycastle_aes_lowmemory as aes_lowmemory;`. +* `src/lib.rs` — `pub use bouncycastle_aes as aes;`. * `mem_usage_benches/bench_aes_mem_usage.rs` (new, 131 lines), plus its `[[bin]]` entry in `mem_usage_benches/Cargo.toml` and a `mod` line in `mem_usage_benches/lib.rs`. * `alpha_0.1.3_release_notes.md` — a "Major features" entry. @@ -293,16 +293,16 @@ schedule is `Secret`); and constant-time execution says nothing about power or E * `cargo fmt --all -- --check` — clean. * `cargo build --workspace`, `cargo test --workspace` — clean, no failures. -* `cargo doc -p bouncycastle-aes-lowmemory --no-deps` — **zero warnings**. -* `cargo clippy -p bouncycastle-aes-lowmemory --all-targets` — **zero warnings** for this crate. -* `./dev_scripts/quality_stats.sh ./crypto/aes-lowmemory` — `Err()` in core code: **3**, exactly the +* `cargo doc -p bouncycastle-aes --no-deps` — **zero warnings**. +* `cargo clippy -p bouncycastle-aes --all-targets` — **zero warnings** for this crate. +* `./dev_scripts/quality_stats.sh ./crypto/aes` — `Err()` in core code: **3**, exactly the three key rejections in `validate`. `unwrap()` in core code: 4, each a `try_into()` on a fixed-size window of a fixed-size array with a preceding justification comment. (Note: `cloc` and `bc` are not installed locally, so the line-count and ratio fields print 0.) ### Mutation testing -`cargo mutants -p bouncycastle-aes-lowmemory` — complete run, 32 minutes: +`cargo mutants -p bouncycastle-aes` — complete run, 32 minutes: ``` 791 mutants tested: 762 caught, 19 missed, 10 unviable, 0 timeouts @@ -453,15 +453,15 @@ file, or both. This is a licensing/policy call rather than a technical one. ## 8. Reproducing the checks ```sh -cargo build -p bouncycastle-aes-lowmemory -cargo test -p bouncycastle-aes-lowmemory # 58 tests -cargo test -p bouncycastle-aes-lowmemory --test acvp_tests -- --nocapture # prints the ACVP count -cargo doc -p bouncycastle-aes-lowmemory --no-deps # expect zero warnings -cargo clippy -p bouncycastle-aes-lowmemory --all-targets +cargo build -p bouncycastle-aes +cargo test -p bouncycastle-aes # 58 tests +cargo test -p bouncycastle-aes --test acvp_tests -- --nocapture # prints the ACVP count +cargo doc -p bouncycastle-aes --no-deps # expect zero warnings +cargo clippy -p bouncycastle-aes --all-targets cargo fmt --all -- --check -cargo bench -p bouncycastle-aes-lowmemory -cargo mutants -p bouncycastle-aes-lowmemory -./dev_scripts/quality_stats.sh ./crypto/aes-lowmemory +cargo bench -p bouncycastle-aes +cargo mutants -p bouncycastle-aes +./dev_scripts/quality_stats.sh ./crypto/aes # struct sizes; add the massif recipe in the file header for stack measurement cargo run --release -p mem_usage_benches --bin bench_aes_mem_usage diff --git a/crypto/aes-lowmemory/tests/acvp_tests.rs b/crypto/aes/tests/acvp_tests.rs similarity index 99% rename from crypto/aes-lowmemory/tests/acvp_tests.rs rename to crypto/aes/tests/acvp_tests.rs index aa7018f8..72132390 100644 --- a/crypto/aes-lowmemory/tests/acvp_tests.rs +++ b/crypto/aes/tests/acvp_tests.rs @@ -44,7 +44,7 @@ //! implementing it from anything other than that specification would be guesswork. The test //! reports how many it skipped so the gap is visible rather than silent. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256, BLOCK_LEN}; +use bouncycastle_aes::{Aes128, Aes192, Aes256, BLOCK_LEN}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; diff --git a/crypto/aes-lowmemory/tests/cbc_alias_tests.rs b/crypto/aes/tests/cbc_alias_tests.rs similarity index 98% rename from crypto/aes-lowmemory/tests/cbc_alias_tests.rs rename to crypto/aes/tests/cbc_alias_tests.rs index debc0842..3ec2fbd6 100644 --- a/crypto/aes-lowmemory/tests/cbc_alias_tests.rs +++ b/crypto/aes/tests/cbc_alias_tests.rs @@ -5,7 +5,7 @@ //! the padding scheme changes the behaviour rather than being decorative. The mode and the padding //! layer are tested in their own crates; this checks the wiring between them. -use bouncycastle_aes_lowmemory::{AES_CBC_128, AES_CBC_192, AES_CBC_256, Aes128}; +use bouncycastle_aes::{AES_CBC_128, AES_CBC_192, AES_CBC_256, Aes128}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; diff --git a/crypto/aes-lowmemory/tests/ecb_alias_tests.rs b/crypto/aes/tests/ecb_alias_tests.rs similarity index 98% rename from crypto/aes-lowmemory/tests/ecb_alias_tests.rs rename to crypto/aes/tests/ecb_alias_tests.rs index 4db0300b..d29773f2 100644 --- a/crypto/aes-lowmemory/tests/ecb_alias_tests.rs +++ b/crypto/aes/tests/ecb_alias_tests.rs @@ -6,7 +6,7 @@ //! here is that its `INIT_DATA_LEN` is 0, so the projection must carry a different value than CBC's //! and the aliases must still resolve correctly. -use bouncycastle_aes_lowmemory::{AES_ECB_128, AES_ECB_192, AES_ECB_256, Aes128}; +use bouncycastle_aes::{AES_ECB_128, AES_ECB_192, AES_ECB_256, Aes128}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; use bouncycastle_modes::{Decrypting, Ecb, Encrypting}; diff --git a/crypto/aes-lowmemory/tests/electronic_code_book_tests.rs b/crypto/aes/tests/electronic_code_book_tests.rs similarity index 93% rename from crypto/aes-lowmemory/tests/electronic_code_book_tests.rs rename to crypto/aes/tests/electronic_code_book_tests.rs index 2098315e..d222471b 100644 --- a/crypto/aes-lowmemory/tests/electronic_code_book_tests.rs +++ b/crypto/aes/tests/electronic_code_book_tests.rs @@ -6,7 +6,7 @@ //! properties matters here specifically: this crate overrides `encrypt_blocks2` and //! `decrypt_blocks2`, so the default implementation is not what runs. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256, BLOCK_LEN}; +use bouncycastle_aes::{Aes128, Aes192, Aes256, BLOCK_LEN}; use bouncycastle_core_test_framework::electronic_code_book::TestFrameworkElectronicCodeBook; #[test] diff --git a/crypto/aes-lowmemory/tests/fips197_tests.rs b/crypto/aes/tests/fips197_tests.rs similarity index 99% rename from crypto/aes-lowmemory/tests/fips197_tests.rs rename to crypto/aes/tests/fips197_tests.rs index d1261b8d..fe4a3f7a 100644 --- a/crypto/aes-lowmemory/tests/fips197_tests.rs +++ b/crypto/aes/tests/fips197_tests.rs @@ -14,7 +14,7 @@ //! //! All values here are transcribed from the published FIPS 197 (Update 1) PDF. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::SecurityStrength; diff --git a/crypto/aes-lowmemory/tests/sp800_38a_tests.rs b/crypto/aes/tests/sp800_38a_tests.rs similarity index 98% rename from crypto/aes-lowmemory/tests/sp800_38a_tests.rs rename to crypto/aes/tests/sp800_38a_tests.rs index 8e975eca..8114314b 100644 --- a/crypto/aes-lowmemory/tests/sp800_38a_tests.rs +++ b/crypto/aes/tests/sp800_38a_tests.rs @@ -15,7 +15,7 @@ //! //! Transcribed from the published SP 800-38A PDF, sections F.1.1 through F.1.6. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256, BLOCK_LEN}; +use bouncycastle_aes::{Aes128, Aes192, Aes256, BLOCK_LEN}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_hex as hex; diff --git a/crypto/core-test-framework/summary.md b/crypto/core-test-framework/summary.md index 51e5baa4..5effa4a0 100644 --- a/crypto/core-test-framework/summary.md +++ b/crypto/core-test-framework/summary.md @@ -1,7 +1,7 @@ # `crypto/core-test-framework` — changes for `ElectronicCodeBook` and CBC Changes made on branch `feature/officialfrancismendoza/100-AES-lightengine-CBC-mode` while adding -`crypto/aes-lowmemory` and `crypto/modes`. Two things: a **new** per-trait suite for +`crypto/aes` and `crypto/modes`. Two things: a **new** per-trait suite for `core::traits::ElectronicCodeBook`, and a **bug fix** to the existing `TestFrameworkBlockCipher`. For what this crate is for in general, see its [`src/lib.rs`](src/lib.rs) docs: one KAT-style @@ -40,7 +40,7 @@ TestFrameworkElectronicCodeBook::new().test::(); ### The order check is the load-bearing one `ElectronicCodeBook::encrypt_blocks2` and `decrypt_blocks2` are *provided* methods: the default is -two single-block calls, and implementations are free to override them. `bouncycastle-aes-lowmemory` +two single-block calls, and implementations are free to override them. `bouncycastle-aes` does, because a pair of blocks is exactly what its bit-sliced state holds, so the pair form costs barely more than one block. @@ -56,7 +56,7 @@ takes the pair path. ### Current implementors -* `crypto/aes-lowmemory/tests/electronic_code_book_tests.rs` — AES-128, AES-192, AES-256. +* `crypto/aes/tests/electronic_code_book_tests.rs` — AES-128, AES-192, AES-256. * `crypto/modes/tests/cbc_tests.rs` — the toy permutation, checked before anything is concluded from it. @@ -171,7 +171,7 @@ cargo fmt --all -- --check This crate has no tests of its own — it *is* tests — so it is verified by its consumers. The two new suites are exercised by: -* `cargo test -p bouncycastle-aes-lowmemory --test electronic_code_book_tests` (3 tests) +* `cargo test -p bouncycastle-aes --test electronic_code_book_tests` (3 tests) * `cargo test -p bouncycastle-modes --test cbc_tests` (11 tests, including `cbc_conforms_to_the_block_cipher_framework`, which is what the §2 fix unblocked, and `the_toy_permutation_conforms_to_the_trait`) diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 2b3518ce..92dac3c7 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -349,7 +349,7 @@ pub trait ElectronicCodeBook: /// /// Provided as two [`ElectronicCodeBook::encrypt_block`] calls. Bit-sliced implementations /// override it, because a pair of blocks is their natural unit of work and costs barely more - /// than one; see `bouncycastle-aes-lowmemory`. + /// than one; see `bouncycastle-aes`. /// /// Overrides must be indistinguishable from the default, including the order of the two /// results. `TestFrameworkElectronicCodeBook` pins that. diff --git a/crypto/modes/Cargo.toml b/crypto/modes/Cargo.toml index 81a97597..6d1c6568 100644 --- a/crypto/modes/Cargo.toml +++ b/crypto/modes/Cargo.toml @@ -11,7 +11,7 @@ bouncycastle-rng.workspace = true bouncycastle-utils.workspace = true [dev-dependencies] -bouncycastle-aes-lowmemory.workspace = true +bouncycastle-aes.workspace = true bouncycastle-core-test-framework.workspace = true bouncycastle-hex.workspace = true # Only to prove the modes compose with the padding layer for arbitrary-length data; no runtime dep. diff --git a/crypto/modes/benches/modes_benches.rs b/crypto/modes/benches/modes_benches.rs index c867e92a..697f16c9 100644 --- a/crypto/modes/benches/modes_benches.rs +++ b/crypto/modes/benches/modes_benches.rs @@ -37,7 +37,7 @@ //! never calls the inverse cipher, so on an engine whose inverse is slower than its forward //! direction, CFB decryption is expected to come out ahead of CBC decryption. -use bouncycastle_aes_lowmemory::{Aes128, Aes256}; +use bouncycastle_aes::{Aes128, Aes256}; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ diff --git a/crypto/modes/src/ctr.rs b/crypto/modes/src/ctr.rs index cb3564d8..21559835 100644 --- a/crypto/modes/src/ctr.rs +++ b/crypto/modes/src/ctr.rs @@ -135,7 +135,7 @@ use core::marker::PhantomData; /// A nonce as long as the block would leave no counter at all, and could not count: /// /// ```compile_fail -/// use bouncycastle_aes_lowmemory::Aes128; +/// use bouncycastle_aes::Aes128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::StreamCipherEncryptor; /// use bouncycastle_modes::{Ctr, Encrypting}; @@ -149,7 +149,7 @@ use core::marker::PhantomData; /// supports: /// /// ```compile_fail -/// use bouncycastle_aes_lowmemory::Aes128; +/// use bouncycastle_aes::Aes128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::StreamCipherEncryptor; /// use bouncycastle_modes::{Ctr, Encrypting}; @@ -162,7 +162,7 @@ use core::marker::PhantomData; /// The permitted lengths all work: /// /// ``` -/// use bouncycastle_aes_lowmemory::Aes128; +/// use bouncycastle_aes::Aes128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::StreamCipherEncryptor; /// use bouncycastle_modes::{Ctr, Encrypting}; diff --git a/crypto/modes/src/lib.rs b/crypto/modes/src/lib.rs index bb13e4c3..37e2cda7 100644 --- a/crypto/modes/src/lib.rs +++ b/crypto/modes/src/lib.rs @@ -1,6 +1,6 @@ //! Block cipher modes of operation (NIST SP 800-38A). //! -//! A mode turns a keyed block permutation -- `bouncycastle-aes-lowmemory`'s `Aes128` and friends, +//! A mode turns a keyed block permutation -- `bouncycastle-aes`'s `Aes128` and friends, //! or anything else implementing [`ElectronicCodeBook`] -- into something that can encrypt more than //! one block. This crate provides: //! @@ -22,7 +22,7 @@ //! handed any mode. A block mode gets there by being wrapped in `bouncycastle-padding`'s adapters, //! which are [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`] with the padded block as //! their final output; a stream mode implements those traits directly, with `FINAL_LEN = 0` because -//! it has no final output at all. The `bouncycastle-aes-lowmemory` aliases show the difference in +//! it has no final output at all. The `bouncycastle-aes` aliases show the difference in //! one line each: `AES_CBC_128` names a padding scheme, `AES_CTR_128` //! has nothing to name. //! @@ -39,12 +39,12 @@ //! The crate is deliberately cipher-agnostic: it depends on no concrete block cipher, only on the //! trait. Define a one-line alias for the combination you use -- or use the ready-made //! `AES_CBC_128` / `AES_CFB_128` / `AES_CFB8_128` / `AES_CTR_128` / `AES_ECB_128` and friends from -//! `bouncycastle-aes-lowmemory`. Those aliases are not all the same shape: the two block modes take +//! `bouncycastle-aes`. Those aliases are not all the same shape: the two block modes take //! a padding scheme as well as a direction, since neither is usable on data of arbitrary length //! without one, while the three stream modes take only the direction: //! //! ``` -//! use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +//! use bouncycastle_aes::{Aes128, Aes192, Aes256}; //! use bouncycastle_modes::{Cbc, Cfb, Cfb8, Ctr, Ecb}; //! //! type Aes128Cbc = Cbc; @@ -74,7 +74,7 @@ //! [Security Considerations](#security-considerations)). //! //! ``` -//! use bouncycastle_aes_lowmemory::Aes128; +//! use bouncycastle_aes::Aes128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; //! use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; @@ -100,7 +100,7 @@ //! the concatenation: //! //! ``` -//! use bouncycastle_aes_lowmemory::Aes256; +//! use bouncycastle_aes::Aes256; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; //! use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; @@ -129,7 +129,7 @@ //! exactly as long as the plaintext: //! //! ``` -//! use bouncycastle_aes_lowmemory::Aes128; +//! use bouncycastle_aes::Aes128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; //! use bouncycastle_modes::{Cfb, Cfb8, Decrypting, Encrypting}; @@ -160,7 +160,7 @@ //! Streaming works at any byte boundary, and the chunking is not visible in the output: //! //! ``` -//! use bouncycastle_aes_lowmemory::Aes128; +//! use bouncycastle_aes::Aes128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; //! use bouncycastle_modes::{Cfb, Decrypting, Encrypting}; @@ -193,7 +193,7 @@ //! The codebook property that makes it unsuitable for data is visible in the ciphertext: //! //! ``` -//! use bouncycastle_aes_lowmemory::Aes128; +//! use bouncycastle_aes::Aes128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; //! use bouncycastle_modes::{Decrypting, Ecb, Encrypting}; @@ -215,7 +215,7 @@ //! Using the wrong direction does not compile: //! //! ```compile_fail -//! use bouncycastle_aes_lowmemory::Aes128; +//! use bouncycastle_aes::Aes128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::BlockCipherDecryptor; //! use bouncycastle_modes::{Cbc, Encrypting}; @@ -242,7 +242,7 @@ //! directly, in the segment they targeted. All are malleable; authenticate the ciphertext. //! * **CFB and CFB8 need only the forward cipher function**, in both directions (Sec 6.3). That //! halves what a permutation has to provide, and where the inverse costs more than the forward -//! direction it makes CFB decryption faster: with `bouncycastle-aes-lowmemory` this crate's +//! direction it makes CFB decryption faster: with `bouncycastle-aes` this crate's //! benches measure CFB decryption at about 1.37x CBC decryption (AES-128, 16 KiB, `N = 8`). //! Encryption is the same speed in CBC and CFB, since both are serial and both use only the //! forward function. @@ -293,7 +293,7 @@ //! an error at `do_final` rather than something padded -- for formats defined on whole blocks. //! //! ``` -//! use bouncycastle_aes_lowmemory::Aes128; +//! use bouncycastle_aes::Aes128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; //! use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; diff --git a/crypto/modes/tests/acvp_cfb8_tests.rs b/crypto/modes/tests/acvp_cfb8_tests.rs index a67c62f8..9a77e882 100644 --- a/crypto/modes/tests/acvp_cfb8_tests.rs +++ b/crypto/modes/tests/acvp_cfb8_tests.rs @@ -2,11 +2,11 @@ //! //! Requires `bc-test-data` to be cloned alongside this repository, i.e. at `../bc-test-data` //! relative to the root of this git project. If it is absent the test prints a warning and passes, -//! matching the convention used by the ML-KEM, ML-DSA, `aes-lowmemory` and AES-CBC suites -- +//! matching the convention used by the ML-KEM, ML-DSA, `aes` and AES-CBC suites -- //! `cargo test` must stay green for someone who has only cloned this repository. //! //! This is the CFB8 counterpart to `acvp_cfb_tests.rs` (AES-CFB128), `acvp_tests.rs` (AES-CBC) and -//! `crypto/aes-lowmemory/tests/acvp_tests.rs` (AES-ECB, the raw permutation). `ACVP-AES-CFB1` is +//! `crypto/aes/tests/acvp_tests.rs` (AES-ECB, the raw permutation). `ACVP-AES-CFB1` is //! the one remaining segment size, which this crate does not implement, and is not read. //! //! # Joining the request and response files @@ -33,7 +33,7 @@ //! than in SP 800-38A, and implementing it from anything else would be guesswork. The test reports //! how many it skipped so the gap stays visible. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; diff --git a/crypto/modes/tests/acvp_cfb_tests.rs b/crypto/modes/tests/acvp_cfb_tests.rs index 933b01d4..2c223be1 100644 --- a/crypto/modes/tests/acvp_cfb_tests.rs +++ b/crypto/modes/tests/acvp_cfb_tests.rs @@ -2,11 +2,11 @@ //! //! Requires `bc-test-data` to be cloned alongside this repository, i.e. at `../bc-test-data` //! relative to the root of this git project. If it is absent the test prints a warning and passes, -//! matching the convention used by the ML-KEM, ML-DSA, `aes-lowmemory` and AES-CBC suites -- +//! matching the convention used by the ML-KEM, ML-DSA, `aes` and AES-CBC suites -- //! `cargo test` must stay green for someone who has only cloned this repository. //! //! This is the CFB128 counterpart to `acvp_tests.rs` (AES-CBC) and to -//! `crypto/aes-lowmemory/tests/acvp_tests.rs` (AES-ECB, the raw permutation). The `CFB128` file is +//! `crypto/aes/tests/acvp_tests.rs` (AES-ECB, the raw permutation). The `CFB128` file is //! the one that matches [`Cfb`]; `ACVP-AES-CFB8` matches `Cfb8` and is read by //! `acvp_cfb8_tests.rs`. `ACVP-AES-CFB1` is the one segment size this crate does not implement, //! and is deliberately not read. @@ -37,7 +37,7 @@ //! than in SP 800-38A, and implementing it from anything else would be guesswork. The test reports //! how many it skipped so the gap stays visible. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; diff --git a/crypto/modes/tests/acvp_ctr_tests.rs b/crypto/modes/tests/acvp_ctr_tests.rs index 8dcbb3df..6f53bd68 100644 --- a/crypto/modes/tests/acvp_ctr_tests.rs +++ b/crypto/modes/tests/acvp_ctr_tests.rs @@ -36,7 +36,7 @@ //! `resultsArray` produced by a chained update rule defined in the ACVP AES specification rather //! than in SP 800-38A, and implementing it from anything else would be guesswork. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; diff --git a/crypto/modes/tests/acvp_ecb_tests.rs b/crypto/modes/tests/acvp_ecb_tests.rs index e33d0593..d35b48ac 100644 --- a/crypto/modes/tests/acvp_ecb_tests.rs +++ b/crypto/modes/tests/acvp_ecb_tests.rs @@ -6,7 +6,7 @@ //! matching the convention used by the other ACVP suites -- `cargo test` must stay green for someone //! who has only cloned this repository. //! -//! `crypto/aes-lowmemory/tests/acvp_tests.rs` runs the same file against the permutation's block +//! `crypto/aes/tests/acvp_tests.rs` runs the same file against the permutation's block //! methods; this file is what pins that the mode adds nothing and loses nothing on the way: every //! case is run through the `BlockCipherEncryptor` / `BlockCipherDecryptor` API in three groupings //! -- block by block, in pairs with a remainder, and the whole payload in one hook call (which for @@ -17,7 +17,7 @@ //! declared direction. The MCT (Monte Carlo) groups carry a `resultsArray` defined by the ACVP AES //! specification rather than SP 800-38A and are skipped, with the count reported. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; diff --git a/crypto/modes/tests/acvp_tests.rs b/crypto/modes/tests/acvp_tests.rs index 37b48d96..94cb3d40 100644 --- a/crypto/modes/tests/acvp_tests.rs +++ b/crypto/modes/tests/acvp_tests.rs @@ -2,10 +2,10 @@ //! //! Requires `bc-test-data` to be cloned alongside this repository, i.e. at `../bc-test-data` //! relative to the root of this git project. If it is absent the test prints a warning and passes, -//! matching the convention used by the ML-KEM, ML-DSA and `aes-lowmemory` suites -- `cargo test` +//! matching the convention used by the ML-KEM, ML-DSA and `aes` suites -- `cargo test` //! must stay green for someone who has only cloned this repository. //! -//! These are the counterpart to `crypto/aes-lowmemory/tests/acvp_tests.rs`, which consumes the +//! These are the counterpart to `crypto/aes/tests/acvp_tests.rs`, which consumes the //! `ACVP-AES-ECB` file to test the raw permutation. CBC is a mode, so its vectors belong here. //! //! # Joining the request and response files @@ -29,7 +29,7 @@ //! than in SP 800-38A, and implementing it from anything else would be guesswork. The test reports //! how many it skipped so the gap stays visible. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; diff --git a/crypto/modes/tests/cbc_tests.rs b/crypto/modes/tests/cbc_tests.rs index 28185e83..e967f4a9 100644 --- a/crypto/modes/tests/cbc_tests.rs +++ b/crypto/modes/tests/cbc_tests.rs @@ -6,7 +6,7 @@ mod common; -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; use bouncycastle_core_test_framework::electronic_code_book::TestFrameworkElectronicCodeBook; diff --git a/crypto/modes/tests/cfb8_tests.rs b/crypto/modes/tests/cfb8_tests.rs index bfea1b17..b711d793 100644 --- a/crypto/modes/tests/cfb8_tests.rs +++ b/crypto/modes/tests/cfb8_tests.rs @@ -13,7 +13,7 @@ mod common; -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ElectronicCodeBook, StreamCipherDecryptor, StreamCipherEncryptor}; use bouncycastle_core_test_framework::FixedSeedRNG; diff --git a/crypto/modes/tests/cfb_tests.rs b/crypto/modes/tests/cfb_tests.rs index 863afd79..7143563e 100644 --- a/crypto/modes/tests/cfb_tests.rs +++ b/crypto/modes/tests/cfb_tests.rs @@ -13,7 +13,7 @@ mod common; -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ BlockCipherEncryptor, ElectronicCodeBook, StreamCipherDecryptor, StreamCipherEncryptor, diff --git a/crypto/modes/tests/ctr_bc_java_tests.rs b/crypto/modes/tests/ctr_bc_java_tests.rs index b0babd62..c47ec786 100644 --- a/crypto/modes/tests/ctr_bc_java_tests.rs +++ b/crypto/modes/tests/ctr_bc_java_tests.rs @@ -36,7 +36,7 @@ //! three key lengths -- and it is exact. Those cases are covered there and by the ACVP suite, so //! what is pinned here is specifically the part neither of them reaches: the narrow counters. -use bouncycastle_aes_lowmemory::Aes128; +use bouncycastle_aes::Aes128; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::StreamCipherEncryptor; use bouncycastle_core_test_framework::FixedSeedRNG; diff --git a/crypto/modes/tests/ctr_tests.rs b/crypto/modes/tests/ctr_tests.rs index f9af6444..716a8f6d 100644 --- a/crypto/modes/tests/ctr_tests.rs +++ b/crypto/modes/tests/ctr_tests.rs @@ -23,7 +23,7 @@ mod common; -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ElectronicCodeBook, StreamCipherDecryptor, StreamCipherEncryptor}; diff --git a/crypto/modes/tests/ctr_vector_tests.rs b/crypto/modes/tests/ctr_vector_tests.rs index 9a93c6f8..24fa9695 100644 --- a/crypto/modes/tests/ctr_vector_tests.rs +++ b/crypto/modes/tests/ctr_vector_tests.rs @@ -25,7 +25,7 @@ //! the counter starting at zero, so the two line up exactly when the IV's low four bytes are zero, //! which is why the IV above ends in `00000000`. See the [`Ctr`] module docs. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ElectronicCodeBook, StreamCipherDecryptor, StreamCipherEncryptor}; use bouncycastle_core_test_framework::FixedSeedRNG; diff --git a/crypto/modes/tests/ecb_tests.rs b/crypto/modes/tests/ecb_tests.rs index db1f2c66..b2c2e46c 100644 --- a/crypto/modes/tests/ecb_tests.rs +++ b/crypto/modes/tests/ecb_tests.rs @@ -12,7 +12,7 @@ mod common; -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook, SymmetricCipherDecryptor, diff --git a/crypto/modes/tests/sp800_38a_cfb8_tests.rs b/crypto/modes/tests/sp800_38a_cfb8_tests.rs index d9fa1468..23c7b245 100644 --- a/crypto/modes/tests/sp800_38a_cfb8_tests.rs +++ b/crypto/modes/tests/sp800_38a_cfb8_tests.rs @@ -30,7 +30,7 @@ //! the vector's IV, and the test asserts the returned init data really is that IV before comparing //! any ciphertext. Decryption takes the IV directly, as init data. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ElectronicCodeBook, StreamCipherDecryptor, StreamCipherEncryptor}; use bouncycastle_core_test_framework::FixedSeedRNG; diff --git a/crypto/modes/tests/sp800_38a_cfb_tests.rs b/crypto/modes/tests/sp800_38a_cfb_tests.rs index 9463f7bd..7243eac5 100644 --- a/crypto/modes/tests/sp800_38a_cfb_tests.rs +++ b/crypto/modes/tests/sp800_38a_cfb_tests.rs @@ -32,7 +32,7 @@ //! the vector's IV, and the test asserts the returned init data really is that IV before comparing //! any ciphertext. Decryption takes the IV directly, as init data. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ElectronicCodeBook, StreamCipherDecryptor, StreamCipherEncryptor}; use bouncycastle_core_test_framework::FixedSeedRNG; diff --git a/crypto/modes/tests/sp800_38a_ecb_tests.rs b/crypto/modes/tests/sp800_38a_ecb_tests.rs index ea9a7539..ea61509a 100644 --- a/crypto/modes/tests/sp800_38a_ecb_tests.rs +++ b/crypto/modes/tests/sp800_38a_ecb_tests.rs @@ -17,7 +17,7 @@ //! checks that, which ties the mode to [`ElectronicCodeBook`] and confirms the transcription: a //! typo in either column would break the equality. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook}; use bouncycastle_hex as hex; diff --git a/crypto/modes/tests/sp800_38a_tests.rs b/crypto/modes/tests/sp800_38a_tests.rs index 1dee9ac7..dcfc45c0 100644 --- a/crypto/modes/tests/sp800_38a_tests.rs +++ b/crypto/modes/tests/sp800_38a_tests.rs @@ -15,7 +15,7 @@ //! the vector's IV, and the test asserts the returned init data really is that IV before comparing //! any ciphertext. Decryption takes the IV directly, as init data. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook}; use bouncycastle_core_test_framework::FixedSeedRNG; diff --git a/crypto/modes/tests/symmetric_cipher_api_tests.rs b/crypto/modes/tests/symmetric_cipher_api_tests.rs index 9a7e28a1..575d841b 100644 --- a/crypto/modes/tests/symmetric_cipher_api_tests.rs +++ b/crypto/modes/tests/symmetric_cipher_api_tests.rs @@ -24,7 +24,7 @@ mod common; -use bouncycastle_aes_lowmemory::Aes128; +use bouncycastle_aes::Aes128; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, diff --git a/mem_usage_benches/bench_aes_mem_usage.rs b/mem_usage_benches/bench_aes_mem_usage.rs index 00d0acd3..a4f35212 100644 --- a/mem_usage_benches/bench_aes_mem_usage.rs +++ b/mem_usage_benches/bench_aes_mem_usage.rs @@ -31,7 +31,7 @@ #![allow(dead_code)] #![allow(unused_imports)] -use bouncycastle::aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle::aes::{Aes128, Aes192, Aes256}; use bouncycastle::core::key_material::{KeyMaterial, KeyType}; /// This exists so /usr/bin/time can measure the base memory footprint of the harness itself. diff --git a/src/lib.rs b/src/lib.rs index afe7659c..16a27ad1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -pub use bouncycastle_aes_lowmemory as aes_lowmemory; +pub use bouncycastle_aes as aes; pub use bouncycastle_base64 as base64; pub use bouncycastle_core as core; pub use bouncycastle_factory as factory; From fed518bfed9c332c3f6e1fdbc9e925fc147406d0 Mon Sep 17 00:00:00 2001 From: David Hook Date: Mon, 7 Sep 2026 13:35:46 +1000 Subject: [PATCH 05/14] aes: drop Block, PaddedMode and the AesParams types from the public API --- alpha_0.1.3_release_notes.md | 2 +- crypto/aes/src/lib.rs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index d3347c61..8d44b292 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -44,7 +44,7 @@ permutation (NIST FIPS 197), re-exported from the umbrella crate. a padding scheme as well -- `AES_CBC_128` -- because neither is defined on data that is not a whole number of blocks, so the scheme is a choice the caller has to make and one both ends must agree on. Naming it in the type makes a mismatched pair a compile error instead of - a decryption that returns plausible rubbish. `PaddedMode` is the projection that lets a single + a decryption that returns plausible rubbish. `PaddedMode` is the crate-internal projection that lets a single alias carry both parameters, `PaddedEncryptor` and `PaddedDecryptor` being distinct types. They are aliases only -- no new engine code, and each one's doctest round-trips and shows that a misaligned length fails to compile. diff --git a/crypto/aes/src/lib.rs b/crypto/aes/src/lib.rs index fa84786b..fbb5bba6 100644 --- a/crypto/aes/src/lib.rs +++ b/crypto/aes/src/lib.rs @@ -228,11 +228,8 @@ mod sbox; mod schedule; pub use aes::{Aes, Aes128, Aes192, Aes256, BLOCK_LEN}; -pub use bitslice::Block; pub use cbc::{AES_CBC_128, AES_CBC_192, AES_CBC_256}; pub use cfb::{AES_CFB_128, AES_CFB_192, AES_CFB_256}; pub use cfb8::{AES_CFB8_128, AES_CFB8_192, AES_CFB8_256}; pub use ctr::{AES_CTR_128, AES_CTR_192, AES_CTR_256, CTR_NONCE_LEN}; pub use ecb::{AES_ECB_128, AES_ECB_192, AES_ECB_256}; -pub use padded_mode::PaddedMode; -pub use schedule::{Aes128Params, Aes192Params, Aes256Params, AesParams}; From 8f932ca99c3f36dc4dd11a66767d85eb84e26074 Mon Sep 17 00:00:00 2001 From: David Hook Date: Mon, 7 Sep 2026 13:43:29 +1000 Subject: [PATCH 06/14] aes: the ElectronicCodeBook trait is the only public route to the permutation --- crypto/aes/benches/aes_benches.rs | 2 +- crypto/aes/src/aes.rs | 24 ++++++++++++------------ crypto/aes/src/lib.rs | 10 ++++++---- crypto/aes/tests/acvp_tests.rs | 2 +- crypto/aes/tests/fips197_tests.rs | 2 +- crypto/aes/tests/sp800_38a_tests.rs | 1 + mem_usage_benches/bench_aes_mem_usage.rs | 1 + 7 files changed, 23 insertions(+), 19 deletions(-) diff --git a/crypto/aes/benches/aes_benches.rs b/crypto/aes/benches/aes_benches.rs index 22b9f51b..6f83afbe 100644 --- a/crypto/aes/benches/aes_benches.rs +++ b/crypto/aes/benches/aes_benches.rs @@ -8,7 +8,7 @@ use bouncycastle_aes::{Aes128, Aes192, Aes256, BLOCK_LEN}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::RNG; +use bouncycastle_core::traits::{ElectronicCodeBook, RNG}; use bouncycastle_rng as rng; use criterion::{Criterion, Throughput, criterion_group, criterion_main}; use std::hint::black_box; diff --git a/crypto/aes/src/aes.rs b/crypto/aes/src/aes.rs index 08198459..6d7bf022 100644 --- a/crypto/aes/src/aes.rs +++ b/crypto/aes/src/aes.rs @@ -20,7 +20,7 @@ pub const BLOCK_LEN: usize = 16; /// /// The only state is the key schedule, held in a [`Secret`] so that it is zeroized on drop and /// redacted from `Debug`. There is no direction flag and no initialisation state: both directions -/// work from the same schedule (see [`Aes::decrypt_blocks2`]), and a constructed value is always +/// work from the same schedule (see [`ElectronicCodeBook::decrypt_blocks2`]), and a constructed value is always /// ready to use, so there is no `init()` or `reset()`. pub struct Aes { schedule: Secret, @@ -122,21 +122,21 @@ impl Aes

{ /// Encrypts two blocks in place. /// /// This is the natural unit of work: the bit-sliced state holds two blocks, so two blocks cost - /// almost exactly what one does. Prefer this over two [`Aes::encrypt_block`] calls whenever + /// almost exactly what one does. Prefer this over two [`ElectronicCodeBook::encrypt_block`] calls whenever /// two blocks are available and independent -- which, for a mode of operation, means CTR, or /// the decryption direction of CBC and CFB, but *not* CBC encryption, whose blocks are /// serially dependent. /// /// Infallible: a constructed [`Aes`] is always usable and every input length is fixed. - pub fn encrypt_blocks2(&self, blocks: &mut [Block; 2]) { + pub(crate) fn encrypt_blocks2(&self, blocks: &mut [Block; 2]) { let mut q = pack(&blocks[0], &blocks[1]); self.encrypt2(&mut q); let (a, b) = blocks.split_at_mut(1); unpack(&q, &mut a[0], &mut b[0]); } - /// Decrypts two blocks in place. See [`Aes::encrypt_blocks2`]. - pub fn decrypt_blocks2(&self, blocks: &mut [Block; 2]) { + /// Decrypts two blocks in place. See [`ElectronicCodeBook::encrypt_blocks2`]. + pub(crate) fn decrypt_blocks2(&self, blocks: &mut [Block; 2]) { let mut q = pack(&blocks[0], &blocks[1]); self.decrypt2(&mut q); let (a, b) = blocks.split_at_mut(1); @@ -147,13 +147,13 @@ impl Aes

{ /// /// The bit-sliced state always holds two blocks, so a single-block call duplicates the block /// into both halves and discards one result: it does twice the necessary work. Use - /// [`Aes::encrypt_blocks2`] where two blocks are available. + /// [`ElectronicCodeBook::encrypt_blocks2`] where two blocks are available. /// /// Duplicating the block costs exactly what filling the unused half with zeros would, and it /// buys a free self-check: the two halves must come out equal, which `debug_assert` verifies. /// That is the whole reason for the choice -- it is not a security property, since the unused /// half is never returned either way. - pub fn encrypt_block(&self, block: &mut Block) { + pub(crate) fn encrypt_block(&self, block: &mut Block) { let mut q = pack(block, block); self.encrypt2(&mut q); let mut discard = [0u8; BLOCK_LEN]; @@ -161,8 +161,8 @@ impl Aes

{ debug_assert_eq!(*block, discard, "the two interleaved halves must agree"); } - /// Decrypts one block in place. See [`Aes::encrypt_block`] for the two-blocks-at-once caveat. - pub fn decrypt_block(&self, block: &mut Block) { + /// Decrypts one block in place. See [`ElectronicCodeBook::encrypt_block`] for the two-blocks-at-once caveat. + pub(crate) fn decrypt_block(&self, block: &mut Block) { let mut q = pack(block, block); self.decrypt2(&mut q); let mut discard = [0u8; BLOCK_LEN]; @@ -184,7 +184,7 @@ impl Aes128 { /// * [`KeyMaterialError::InvalidKeyType`] if the key is not [`KeyType::SymmetricCipherKey`]. /// * [`KeyMaterialError::InvalidLength`] if the key is not 16 bytes long. /// * [`KeyMaterialError::SecurityStrength`] if the key carries a strength below 128 bits. - pub fn new(key: &KeyMaterial<16>) -> Result { + pub(crate) fn new(key: &KeyMaterial<16>) -> Result { Self::validate(key)?; Ok(Self { schedule: expand::(key.ref_to_bytes()) }) } @@ -192,7 +192,7 @@ impl Aes128 { impl Aes192 { /// Expands a 24-byte key into an AES-192 schedule. See [`Aes128::new`] for the error cases. - pub fn new(key: &KeyMaterial<24>) -> Result { + pub(crate) fn new(key: &KeyMaterial<24>) -> Result { Self::validate(key)?; Ok(Self { schedule: expand::(key.ref_to_bytes()) }) } @@ -200,7 +200,7 @@ impl Aes192 { impl Aes256 { /// Expands a 32-byte key into an AES-256 schedule. See [`Aes128::new`] for the error cases. - pub fn new(key: &KeyMaterial<32>) -> Result { + pub(crate) fn new(key: &KeyMaterial<32>) -> Result { Self::validate(key)?; Ok(Self { schedule: expand::(key.ref_to_bytes()) }) } diff --git a/crypto/aes/src/lib.rs b/crypto/aes/src/lib.rs index fbb5bba6..ac6fddd0 100644 --- a/crypto/aes/src/lib.rs +++ b/crypto/aes/src/lib.rs @@ -14,6 +14,7 @@ //! ``` //! use bouncycastle_aes::Aes128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +//! use bouncycastle_core::traits::ElectronicCodeBook; //! //! let key = KeyMaterial::<16>::from_bytes_as_type( //! &[0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, @@ -39,12 +40,13 @@ //! ## Two blocks at a time //! //! The bit-sliced state holds two blocks, so two independent blocks cost barely more than one. -//! Where a caller has two, [`Aes::encrypt_blocks2`] is roughly twice the throughput of two -//! [`Aes::encrypt_block`] calls: +//! Where a caller has two, [`ElectronicCodeBook::encrypt_blocks2`](bouncycastle_core::traits::ElectronicCodeBook::encrypt_blocks2) is roughly twice the throughput of two +//! [`ElectronicCodeBook::encrypt_block`](bouncycastle_core::traits::ElectronicCodeBook::encrypt_block) calls: //! //! ``` //! use bouncycastle_aes::Aes256; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +//! use bouncycastle_core::traits::ElectronicCodeBook; //! //! let key = KeyMaterial::<32>::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) //! .expect("a 32-byte symmetric cipher key"); @@ -137,7 +139,7 @@ //! Decryption follows FIPS 197 Algorithm 3, the straight inverse cipher, rather than the //! equivalent inverse cipher of Sec 5.3.5. Algorithm 3 puts INVMIXCOLUMNS() after ADDROUNDKEY(), //! so it uses the *unmodified* key schedule; the equivalent inverse cipher would need a second -//! schedule with each round key transformed. One [`Aes`] value therefore encrypts and decrypts +//! schedule with each round key transformed. One [`Aes128`] value therefore encrypts and decrypts //! from one stored schedule. //! //! # Memory Usage @@ -227,7 +229,7 @@ mod round; mod sbox; mod schedule; -pub use aes::{Aes, Aes128, Aes192, Aes256, BLOCK_LEN}; +pub use aes::{Aes128, Aes192, Aes256, BLOCK_LEN}; pub use cbc::{AES_CBC_128, AES_CBC_192, AES_CBC_256}; pub use cfb::{AES_CFB_128, AES_CFB_192, AES_CFB_256}; pub use cfb8::{AES_CFB8_128, AES_CFB8_192, AES_CFB8_256}; diff --git a/crypto/aes/tests/acvp_tests.rs b/crypto/aes/tests/acvp_tests.rs index 72132390..453d738a 100644 --- a/crypto/aes/tests/acvp_tests.rs +++ b/crypto/aes/tests/acvp_tests.rs @@ -48,7 +48,7 @@ use bouncycastle_aes::{Aes128, Aes192, Aes256, BLOCK_LEN}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; -use bouncycastle_core::traits::SecurityStrength; +use bouncycastle_core::traits::{ElectronicCodeBook, SecurityStrength}; use bouncycastle_hex as hex; use serde_json::Value; use std::fs; diff --git a/crypto/aes/tests/fips197_tests.rs b/crypto/aes/tests/fips197_tests.rs index fe4a3f7a..aa01f6d6 100644 --- a/crypto/aes/tests/fips197_tests.rs +++ b/crypto/aes/tests/fips197_tests.rs @@ -16,7 +16,7 @@ use bouncycastle_aes::{Aes128, Aes192, Aes256}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; -use bouncycastle_core::traits::SecurityStrength; +use bouncycastle_core::traits::{ElectronicCodeBook, SecurityStrength}; /// Appendix A.1 / Appendix B key: `2b7e151628aed2a6abf7158809cf4f3c`. const KEY_128: [u8; 16] = [ diff --git a/crypto/aes/tests/sp800_38a_tests.rs b/crypto/aes/tests/sp800_38a_tests.rs index 8114314b..c4314182 100644 --- a/crypto/aes/tests/sp800_38a_tests.rs +++ b/crypto/aes/tests/sp800_38a_tests.rs @@ -17,6 +17,7 @@ use bouncycastle_aes::{Aes128, Aes192, Aes256, BLOCK_LEN}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::ElectronicCodeBook; use bouncycastle_hex as hex; /// The four plaintext blocks shared by every F.1 subsection. diff --git a/mem_usage_benches/bench_aes_mem_usage.rs b/mem_usage_benches/bench_aes_mem_usage.rs index a4f35212..fab4dfdb 100644 --- a/mem_usage_benches/bench_aes_mem_usage.rs +++ b/mem_usage_benches/bench_aes_mem_usage.rs @@ -33,6 +33,7 @@ use bouncycastle::aes::{Aes128, Aes192, Aes256}; use bouncycastle::core::key_material::{KeyMaterial, KeyType}; +use bouncycastle::core::traits::ElectronicCodeBook; /// This exists so /usr/bin/time can measure the base memory footprint of the harness itself. fn bench_do_nothing() { From 41ce203afb550f8037f285c63dece73d4ce63b5a Mon Sep 17 00:00:00 2001 From: David Hook Date: Wed, 9 Sep 2026 12:47:45 +1000 Subject: [PATCH 07/14] aes: the permutation types keep the spec's capitalisation, AES / AES_128 / AES_192 / AES_256, AESParams and Rcon, and the sealing supertrait becomes AESParamsInternalTrait after the mlkem pattern (from Mike Ounsworth's 736b0ac review of #105); QUALITY_AND_STYLE.md records the spec-capitalisation exception to the clippy naming rules --- QUALITY_AND_STYLE.md | 11 +- alpha_0.1.3_release_notes.md | 2 +- cli/src/aes_cbc_cmd.rs | 8 +- cli/src/aes_cfb8_cmd.rs | 8 +- cli/src/aes_cfb_cmd.rs | 8 +- cli/src/aes_ctr_cmd.rs | 8 +- cli/src/aes_ecb_cmd.rs | 8 +- cli/tests/aes_cfb_cli_tests.rs | 2 +- crypto/aes/benches/aes_benches.rs | 26 ++-- crypto/aes/src/aes.rs | 117 +++++++++--------- crypto/aes/src/cbc.rs | 16 +-- crypto/aes/src/cfb.rs | 8 +- crypto/aes/src/cfb8.rs | 8 +- crypto/aes/src/ctr.rs | 8 +- crypto/aes/src/ecb.rs | 14 +-- crypto/aes/src/lib.rs | 26 ++-- crypto/aes/src/padded_mode.rs | 4 +- crypto/aes/src/schedule.rs | 87 ++++++------- crypto/aes/summary.md | 30 ++--- crypto/aes/tests/acvp_tests.rs | 16 +-- crypto/aes/tests/cbc_alias_tests.rs | 6 +- crypto/aes/tests/ecb_alias_tests.rs | 6 +- .../aes/tests/electronic_code_book_tests.rs | 8 +- crypto/aes/tests/fips197_tests.rs | 38 +++--- crypto/aes/tests/sp800_38a_tests.rs | 18 +-- crypto/modes/benches/modes_benches.rs | 60 ++++----- crypto/modes/src/ctr.rs | 14 +-- crypto/modes/src/lib.rs | 54 ++++---- crypto/modes/tests/acvp_cfb8_tests.rs | 8 +- crypto/modes/tests/acvp_cfb_tests.rs | 8 +- crypto/modes/tests/acvp_ctr_tests.rs | 8 +- crypto/modes/tests/acvp_ecb_tests.rs | 8 +- crypto/modes/tests/acvp_tests.rs | 8 +- crypto/modes/tests/cbc_tests.rs | 14 +-- crypto/modes/tests/cfb8_tests.rs | 30 ++--- crypto/modes/tests/cfb_tests.rs | 28 ++--- crypto/modes/tests/ctr_bc_java_tests.rs | 6 +- crypto/modes/tests/ctr_tests.rs | 22 ++-- crypto/modes/tests/ctr_vector_tests.rs | 8 +- crypto/modes/tests/ecb_tests.rs | 20 +-- crypto/modes/tests/sp800_38a_cfb8_tests.rs | 16 +-- crypto/modes/tests/sp800_38a_cfb_tests.rs | 28 ++--- crypto/modes/tests/sp800_38a_ecb_tests.rs | 20 +-- crypto/modes/tests/sp800_38a_tests.rs | 26 ++-- .../modes/tests/symmetric_cipher_api_tests.rs | 14 +-- mem_usage_benches/bench_aes_mem_usage.rs | 36 +++--- 46 files changed, 471 insertions(+), 456 deletions(-) diff --git a/QUALITY_AND_STYLE.md b/QUALITY_AND_STYLE.md index 65f7e7e0..db9e2056 100644 --- a/QUALITY_AND_STYLE.md +++ b/QUALITY_AND_STYLE.md @@ -63,7 +63,16 @@ which parts were done for a very specific reason and should not be changed on a ## Naming Conventions -All normal rust naming convensions from clippy apply. In addition, some library-specific naming conventions: +All normal rust naming conventions from clippy apply, with one exception: + +* Where a type, constant or variable corresponds to something a specification (FIPS, RFC, etc) names, keep the + specification's spelling and capitalization, and `#[allow(non_camel_case_types)]`, `#[allow(non_snake_case)]` or + `#[allow(non_upper_case_globals)]` the item locally. So the FIPS 197 cipher is `AES_128`, not `Aes128`, its CBC + mode is `AES_CBC_128`, not `AesCbc128`, and if a specification writes `A` for a matrix and `a` for a vector then + `let A = ...; let a = ...;` is the right thing to do. The point is that a reviewer with the specification open can + match names by eye; that matters more here than rust convention. + +In addition, some library-specific naming conventions: * In constants, "LEN" is the length of a value in bytes (typically used for sizing arrays), whereas "SIZE" is a value in bits (typically used as a security parameter). For example SHA256 could have constants `HASH_SIZE = 256` and diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index 8d44b292..3af60235 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -20,7 +20,7 @@ permutation (NIST FIPS 197), re-exported from the umbrella crate. AES that removes the tables only from the cipher still leaks through `SUBWORD()` in the expansion. * **Low memory.** No lookup tables at all (0 bytes, against 512 bytes for BC Java's `AESLightEngine` and 2-8 KiB for T-table engines) and no heap allocation. The only persistent state is the key schedule, stored bit-sliced - in a compressed form that is exactly the FIPS 197 Sec 5.2 size: `Aes128` 176 B, `Aes192` 208 B, `Aes256` 240 B. + in a compressed form that is exactly the FIPS 197 Sec 5.2 size: `AES_128` 176 B, `AES_192` 208 B, `AES_256` 240 B. * **Both directions from one value.** Decryption follows FIPS 197 Algorithm 3 (the straight inverse cipher) rather than the equivalent inverse cipher of Sec 5.3.5, so it uses the unmodified key schedule -- one stored schedule encrypts and decrypts, with no second copy and no transformation at construction time. diff --git a/cli/src/aes_cbc_cmd.rs b/cli/src/aes_cbc_cmd.rs index 1fc288ee..303601fc 100644 --- a/cli/src/aes_cbc_cmd.rs +++ b/cli/src/aes_cbc_cmd.rs @@ -10,7 +10,7 @@ //! separately. use crate::block_mode_cmd::{BLOCK_LEN, BlockModeAction, decrypt_stream, encrypt_stream, load_key}; -use bouncycastle::aes::{Aes128, Aes192, Aes256}; +use bouncycastle::aes::{AES_128, AES_192, AES_256}; use bouncycastle::core::key_material::KeyMaterial; use bouncycastle::core::traits::ElectronicCodeBook; use bouncycastle::modes::{Cbc, Decrypting, Encrypting}; @@ -24,7 +24,7 @@ pub(crate) fn aes128_cbc_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<16>(key, key_file, "AES-128"), output_hex); + run::(action, &load_key::<16>(key, key_file, "AES-128"), output_hex); } pub(crate) fn aes192_cbc_cmd( @@ -33,7 +33,7 @@ pub(crate) fn aes192_cbc_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<24>(key, key_file, "AES-192"), output_hex); + run::(action, &load_key::<24>(key, key_file, "AES-192"), output_hex); } pub(crate) fn aes256_cbc_cmd( @@ -42,7 +42,7 @@ pub(crate) fn aes256_cbc_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<32>(key, key_file, "AES-256"), output_hex); + run::(action, &load_key::<32>(key, key_file, "AES-256"), output_hex); } /// Dispatches to the shared streaming loops with `Cbc` filled in as the mode. diff --git a/cli/src/aes_cfb8_cmd.rs b/cli/src/aes_cfb8_cmd.rs index 74eacce5..ec554b13 100644 --- a/cli/src/aes_cfb8_cmd.rs +++ b/cli/src/aes_cfb8_cmd.rs @@ -27,7 +27,7 @@ use crate::block_mode_cmd::{BLOCK_LEN, BlockModeAction, load_key}; use crate::stream_mode_cmd::run_stream_mode; -use bouncycastle::aes::{Aes128, Aes192, Aes256}; +use bouncycastle::aes::{AES_128, AES_192, AES_256}; use bouncycastle::core::key_material::KeyMaterial; use bouncycastle::core::traits::ElectronicCodeBook; use bouncycastle::modes::{Cfb8, Decrypting, Encrypting}; @@ -38,7 +38,7 @@ pub(crate) fn aes128_cfb8_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<16>(key, key_file, "AES-128"), output_hex); + run::(action, &load_key::<16>(key, key_file, "AES-128"), output_hex); } pub(crate) fn aes192_cfb8_cmd( @@ -47,7 +47,7 @@ pub(crate) fn aes192_cfb8_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<24>(key, key_file, "AES-192"), output_hex); + run::(action, &load_key::<24>(key, key_file, "AES-192"), output_hex); } pub(crate) fn aes256_cfb8_cmd( @@ -56,7 +56,7 @@ pub(crate) fn aes256_cfb8_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<32>(key, key_file, "AES-256"), output_hex); + run::(action, &load_key::<32>(key, key_file, "AES-256"), output_hex); } /// Dispatches to the shared streaming loops with `Cfb8` filled in as the mode. diff --git a/cli/src/aes_cfb_cmd.rs b/cli/src/aes_cfb_cmd.rs index 4b40690d..4c2182c9 100644 --- a/cli/src/aes_cfb_cmd.rs +++ b/cli/src/aes_cfb_cmd.rs @@ -28,7 +28,7 @@ use crate::block_mode_cmd::{BLOCK_LEN, BlockModeAction, load_key}; use crate::stream_mode_cmd::run_stream_mode; -use bouncycastle::aes::{Aes128, Aes192, Aes256}; +use bouncycastle::aes::{AES_128, AES_192, AES_256}; use bouncycastle::core::key_material::KeyMaterial; use bouncycastle::core::traits::ElectronicCodeBook; use bouncycastle::modes::{Cfb, Decrypting, Encrypting}; @@ -39,7 +39,7 @@ pub(crate) fn aes128_cfb_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<16>(key, key_file, "AES-128"), output_hex); + run::(action, &load_key::<16>(key, key_file, "AES-128"), output_hex); } pub(crate) fn aes192_cfb_cmd( @@ -48,7 +48,7 @@ pub(crate) fn aes192_cfb_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<24>(key, key_file, "AES-192"), output_hex); + run::(action, &load_key::<24>(key, key_file, "AES-192"), output_hex); } pub(crate) fn aes256_cfb_cmd( @@ -57,7 +57,7 @@ pub(crate) fn aes256_cfb_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<32>(key, key_file, "AES-256"), output_hex); + run::(action, &load_key::<32>(key, key_file, "AES-256"), output_hex); } /// Dispatches to the shared streaming loops with `Cfb` filled in as the mode. diff --git a/cli/src/aes_ctr_cmd.rs b/cli/src/aes_ctr_cmd.rs index b125c5cd..9e32f750 100644 --- a/cli/src/aes_ctr_cmd.rs +++ b/cli/src/aes_ctr_cmd.rs @@ -35,7 +35,7 @@ use crate::block_mode_cmd::{BLOCK_LEN, BlockModeAction, load_key}; use crate::stream_mode_cmd::run_stream_mode; -use bouncycastle::aes::{Aes128, Aes192, Aes256, CTR_NONCE_LEN}; +use bouncycastle::aes::{AES_128, AES_192, AES_256, CTR_NONCE_LEN}; use bouncycastle::core::key_material::KeyMaterial; use bouncycastle::core::traits::ElectronicCodeBook; use bouncycastle::modes::{Ctr, Decrypting, Encrypting}; @@ -46,7 +46,7 @@ pub(crate) fn aes128_ctr_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<16>(key, key_file, "AES-128"), output_hex); + run::(action, &load_key::<16>(key, key_file, "AES-128"), output_hex); } pub(crate) fn aes192_ctr_cmd( @@ -55,7 +55,7 @@ pub(crate) fn aes192_ctr_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<24>(key, key_file, "AES-192"), output_hex); + run::(action, &load_key::<24>(key, key_file, "AES-192"), output_hex); } pub(crate) fn aes256_ctr_cmd( @@ -64,7 +64,7 @@ pub(crate) fn aes256_ctr_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<32>(key, key_file, "AES-256"), output_hex); + run::(action, &load_key::<32>(key, key_file, "AES-256"), output_hex); } /// Dispatches to the shared streaming loops with `Ctr` filled in as the mode. diff --git a/cli/src/aes_ecb_cmd.rs b/cli/src/aes_ecb_cmd.rs index d21af91d..3692bc3f 100644 --- a/cli/src/aes_ecb_cmd.rs +++ b/cli/src/aes_ecb_cmd.rs @@ -16,7 +16,7 @@ //! `aes*-cbc` or `aes*-cfb` under separate authentication, or better an AEAD. use crate::block_mode_cmd::{BLOCK_LEN, BlockModeAction, decrypt_stream, encrypt_stream, load_key}; -use bouncycastle::aes::{Aes128, Aes192, Aes256}; +use bouncycastle::aes::{AES_128, AES_192, AES_256}; use bouncycastle::core::key_material::KeyMaterial; use bouncycastle::core::traits::ElectronicCodeBook; use bouncycastle::modes::{Decrypting, Ecb, Encrypting}; @@ -30,7 +30,7 @@ pub(crate) fn aes128_ecb_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<16>(key, key_file, "AES-128"), output_hex); + run::(action, &load_key::<16>(key, key_file, "AES-128"), output_hex); } pub(crate) fn aes192_ecb_cmd( @@ -39,7 +39,7 @@ pub(crate) fn aes192_ecb_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<24>(key, key_file, "AES-192"), output_hex); + run::(action, &load_key::<24>(key, key_file, "AES-192"), output_hex); } pub(crate) fn aes256_ecb_cmd( @@ -48,7 +48,7 @@ pub(crate) fn aes256_ecb_cmd( key_file: &Option, output_hex: bool, ) { - run::(action, &load_key::<32>(key, key_file, "AES-256"), output_hex); + run::(action, &load_key::<32>(key, key_file, "AES-256"), output_hex); } /// Dispatches to the shared streaming loops with `Ecb` filled in as the mode. `INIT_DATA_LEN` is 0, diff --git a/cli/tests/aes_cfb_cli_tests.rs b/cli/tests/aes_cfb_cli_tests.rs index e402fca8..ec39475d 100644 --- a/cli/tests/aes_cfb_cli_tests.rs +++ b/cli/tests/aes_cfb_cli_tests.rs @@ -386,7 +386,7 @@ fn an_unaligned_message_matches_the_library() { use bouncycastle::core::traits::StreamCipherDecryptor; use bouncycastle::modes::{Cfb, Decrypting}; - type Aes128Cfb

= Cfb; + type Aes128Cfb = Cfb; for len in [5usize, 17, 1000, 1024, 1025, 4099] { let plaintext = pseudo_random(len, len as u32); diff --git a/crypto/aes/benches/aes_benches.rs b/crypto/aes/benches/aes_benches.rs index 6f83afbe..9b010a54 100644 --- a/crypto/aes/benches/aes_benches.rs +++ b/crypto/aes/benches/aes_benches.rs @@ -6,7 +6,7 @@ //! argument for modes of operation using the two-block entry points wherever their blocks are //! independent (CTR, and the decrypt direction of CBC and CFB). -use bouncycastle_aes::{Aes128, Aes192, Aes256, BLOCK_LEN}; +use bouncycastle_aes::{AES_128, AES_192, AES_256, BLOCK_LEN}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ElectronicCodeBook, RNG}; use bouncycastle_rng as rng; @@ -36,28 +36,28 @@ fn bench_key_expansion(c: &mut Criterion) { let mut group = c.benchmark_group("aes::key expansion"); let key128 = key::<16>(); - group.bench_function("Aes128::new()", |b| { - b.iter(|| black_box(Aes128::new(black_box(&key128)).unwrap())) + group.bench_function("AES_128::new()", |b| { + b.iter(|| black_box(AES_128::new(black_box(&key128)).unwrap())) }); let key192 = key::<24>(); - group.bench_function("Aes192::new()", |b| { - b.iter(|| black_box(Aes192::new(black_box(&key192)).unwrap())) + group.bench_function("AES_192::new()", |b| { + b.iter(|| black_box(AES_192::new(black_box(&key192)).unwrap())) }); let key256 = key::<32>(); - group.bench_function("Aes256::new()", |b| { - b.iter(|| black_box(Aes256::new(black_box(&key256)).unwrap())) + group.bench_function("AES_256::new()", |b| { + b.iter(|| black_box(AES_256::new(black_box(&key256)).unwrap())) }); group.finish(); } fn bench_aes128(c: &mut Criterion) { - let aes = Aes128::new(&key::<16>()).unwrap(); + let aes = AES_128::new(&key::<16>()).unwrap(); let blocks = random_blocks(); - let mut group = c.benchmark_group("aes::Aes128"); + let mut group = c.benchmark_group("aes::AES_128"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); group.bench_function("16KiB -- .encrypt_block() x1024", |b| { @@ -107,10 +107,10 @@ fn bench_aes128(c: &mut Criterion) { } fn bench_aes192(c: &mut Criterion) { - let aes = Aes192::new(&key::<24>()).unwrap(); + let aes = AES_192::new(&key::<24>()).unwrap(); let blocks = random_blocks(); - let mut group = c.benchmark_group("aes::Aes192"); + let mut group = c.benchmark_group("aes::AES_192"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); group.bench_function("16KiB -- .encrypt_block() x1024", |b| { @@ -138,10 +138,10 @@ fn bench_aes192(c: &mut Criterion) { } fn bench_aes256(c: &mut Criterion) { - let aes = Aes256::new(&key::<32>()).unwrap(); + let aes = AES_256::new(&key::<32>()).unwrap(); let blocks = random_blocks(); - let mut group = c.benchmark_group("aes::Aes256"); + let mut group = c.benchmark_group("aes::AES_256"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); group.bench_function("16KiB -- .encrypt_block() x1024", |b| { diff --git a/crypto/aes/src/aes.rs b/crypto/aes/src/aes.rs index 6d7bf022..d4a35cc0 100644 --- a/crypto/aes/src/aes.rs +++ b/crypto/aes/src/aes.rs @@ -3,7 +3,7 @@ use crate::bitslice::{Block, Planes, pack, unpack}; use crate::round::{add_round_key, inv_mix_columns, inv_shift_rows, mix_columns, shift_rows}; use crate::sbox::{inv_sbox, sbox}; -use crate::schedule::{Aes128Params, Aes192Params, Aes256Params, AesParams, expand, round_key}; +use crate::schedule::{AES128Params, AES192Params, AES256Params, AESParams, expand, round_key}; use bouncycastle_core::errors::{KeyMaterialError, SymmetricCipherError}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{Algorithm, ElectronicCodeBook, SecurityStrength}; @@ -14,7 +14,7 @@ pub const BLOCK_LEN: usize = 16; /// The AES keyed permutation, parameterised by key length. /// -/// Use the aliases [`Aes128`], [`Aes192`] and [`Aes256`] rather than naming this directly. +/// Use the aliases [`AES_128`], [`AES_192`] and [`AES_256`] rather than naming this directly. /// `P` is sealed to the three parameter sets of FIPS 197 Sec 6.1, so no fourth instantiation /// exists. /// @@ -22,18 +22,21 @@ pub const BLOCK_LEN: usize = 16; /// redacted from `Debug`. There is no direction flag and no initialisation state: both directions /// work from the same schedule (see [`ElectronicCodeBook::decrypt_blocks2`]), and a constructed value is always /// ready to use, so there is no `init()` or `reset()`. -pub struct Aes { +pub struct AES { schedule: Secret, } /// AES-128: 16-byte key, 10 rounds (FIPS 197 Sec 6.1). -pub type Aes128 = Aes; +#[allow(non_camel_case_types)] +pub type AES_128 = AES; /// AES-192: 24-byte key, 12 rounds (FIPS 197 Sec 6.1). -pub type Aes192 = Aes; +#[allow(non_camel_case_types)] +pub type AES_192 = AES; /// AES-256: 32-byte key, 14 rounds (FIPS 197 Sec 6.1). -pub type Aes256 = Aes; +#[allow(non_camel_case_types)] +pub type AES_256 = AES; -impl Aes

{ +impl AES

{ /// Checks a key is fit to use before it is expanded. /// /// The key must be tagged [`KeyType::SymmetricCipherKey`], must be exactly `P::KEY_LEN` bytes @@ -94,7 +97,7 @@ impl Aes

{ /// the two the other way round and needs a separate schedule with INVMIXCOLUMNS() applied to /// each round key (Algorithm 5, KEYEXPANSIONEIC()). /// - /// Following Algorithm 3 is therefore what allows one [`Aes`] value to encrypt *and* decrypt + /// Following Algorithm 3 is therefore what allows one [`AES`] value to encrypt *and* decrypt /// from a single stored schedule, with no second copy and no transformation at construction /// time -- which is the whole reason this crate can offer both directions at 176-240 bytes of /// state. @@ -127,7 +130,7 @@ impl Aes

{ /// the decryption direction of CBC and CFB, but *not* CBC encryption, whose blocks are /// serially dependent. /// - /// Infallible: a constructed [`Aes`] is always usable and every input length is fixed. + /// Infallible: a constructed [`AES`] is always usable and every input length is fixed. pub(crate) fn encrypt_blocks2(&self, blocks: &mut [Block; 2]) { let mut q = pack(&blocks[0], &blocks[1]); self.encrypt2(&mut q); @@ -177,7 +180,7 @@ impl Aes

{ // Each `new` differs only in the `KeyMaterial` capacity it accepts, which is what makes a // wrong-length key a compile error at the call site rather than a runtime error. -impl Aes128 { +impl AES_128 { /// Expands a 16-byte key into an AES-128 schedule. /// /// # Errors @@ -186,38 +189,38 @@ impl Aes128 { /// * [`KeyMaterialError::SecurityStrength`] if the key carries a strength below 128 bits. pub(crate) fn new(key: &KeyMaterial<16>) -> Result { Self::validate(key)?; - Ok(Self { schedule: expand::(key.ref_to_bytes()) }) + Ok(Self { schedule: expand::(key.ref_to_bytes()) }) } } -impl Aes192 { - /// Expands a 24-byte key into an AES-192 schedule. See [`Aes128::new`] for the error cases. +impl AES_192 { + /// Expands a 24-byte key into an AES-192 schedule. See [`AES_128::new`] for the error cases. pub(crate) fn new(key: &KeyMaterial<24>) -> Result { Self::validate(key)?; - Ok(Self { schedule: expand::(key.ref_to_bytes()) }) + Ok(Self { schedule: expand::(key.ref_to_bytes()) }) } } -impl Aes256 { - /// Expands a 32-byte key into an AES-256 schedule. See [`Aes128::new`] for the error cases. +impl AES_256 { + /// Expands a 32-byte key into an AES-256 schedule. See [`AES_128::new`] for the error cases. pub(crate) fn new(key: &KeyMaterial<32>) -> Result { Self::validate(key)?; - Ok(Self { schedule: expand::(key.ref_to_bytes()) }) + Ok(Self { schedule: expand::(key.ref_to_bytes()) }) } } -impl Algorithm for Aes128 { - const ALG_NAME: &'static str = Aes128Params::ALG_NAME; +impl Algorithm for AES_128 { + const ALG_NAME: &'static str = AES128Params::ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } -impl Algorithm for Aes192 { - const ALG_NAME: &'static str = Aes192Params::ALG_NAME; +impl Algorithm for AES_192 { + const ALG_NAME: &'static str = AES192Params::ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit; } -impl Algorithm for Aes256 { - const ALG_NAME: &'static str = Aes256Params::ALG_NAME; +impl Algorithm for AES_256 { + const ALG_NAME: &'static str = AES256Params::ALG_NAME; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit; } @@ -228,61 +231,61 @@ impl Algorithm for Aes256 { // the bit-sliced state holds: the pair form costs barely more than one block, where the default // (two single-block calls) would do four blocks' worth of work. -impl ElectronicCodeBook<16, BLOCK_LEN> for Aes128 { +impl ElectronicCodeBook<16, BLOCK_LEN> for AES_128 { fn new(key: &KeyMaterial<16>) -> Result { - Aes128::new(key) + AES_128::new(key) } fn encrypt_block(&self, block: &mut Block) { - Aes::encrypt_block(self, block) + AES::encrypt_block(self, block) } fn decrypt_block(&self, block: &mut Block) { - Aes::decrypt_block(self, block) + AES::decrypt_block(self, block) } fn encrypt_blocks2(&self, blocks: &mut [Block; 2]) { - Aes::encrypt_blocks2(self, blocks) + AES::encrypt_blocks2(self, blocks) } fn decrypt_blocks2(&self, blocks: &mut [Block; 2]) { - Aes::decrypt_blocks2(self, blocks) + AES::decrypt_blocks2(self, blocks) } } -impl ElectronicCodeBook<24, BLOCK_LEN> for Aes192 { +impl ElectronicCodeBook<24, BLOCK_LEN> for AES_192 { fn new(key: &KeyMaterial<24>) -> Result { - Aes192::new(key) + AES_192::new(key) } fn encrypt_block(&self, block: &mut Block) { - Aes::encrypt_block(self, block) + AES::encrypt_block(self, block) } fn decrypt_block(&self, block: &mut Block) { - Aes::decrypt_block(self, block) + AES::decrypt_block(self, block) } fn encrypt_blocks2(&self, blocks: &mut [Block; 2]) { - Aes::encrypt_blocks2(self, blocks) + AES::encrypt_blocks2(self, blocks) } fn decrypt_blocks2(&self, blocks: &mut [Block; 2]) { - Aes::decrypt_blocks2(self, blocks) + AES::decrypt_blocks2(self, blocks) } } -impl ElectronicCodeBook<32, BLOCK_LEN> for Aes256 { +impl ElectronicCodeBook<32, BLOCK_LEN> for AES_256 { fn new(key: &KeyMaterial<32>) -> Result { - Aes256::new(key) + AES_256::new(key) } fn encrypt_block(&self, block: &mut Block) { - Aes::encrypt_block(self, block) + AES::encrypt_block(self, block) } fn decrypt_block(&self, block: &mut Block) { - Aes::decrypt_block(self, block) + AES::decrypt_block(self, block) } fn encrypt_blocks2(&self, blocks: &mut [Block; 2]) { - Aes::encrypt_blocks2(self, blocks) + AES::encrypt_blocks2(self, blocks) } fn decrypt_blocks2(&self, blocks: &mut [Block; 2]) { - Aes::decrypt_blocks2(self, blocks) + AES::decrypt_blocks2(self, blocks) } } -impl core::fmt::Debug for Aes

{ +impl core::fmt::Debug for AES

{ /// Prints the algorithm name only. The key schedule is secret and is never formatted. fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str(P::ALG_NAME) @@ -298,40 +301,40 @@ mod tests { // The "Memory Usage" table in the crate docs quotes these, and the whole point of the // crate is that they are this small: 4 * (Nr + 1) words of schedule, nothing else, and no // tables anywhere. If the representation grows, the docs are wrong -- fix both. - assert_eq!(size_of::(), 176, "AES-128: 4 * (10 + 1) words"); - assert_eq!(size_of::(), 208, "AES-192: 4 * (12 + 1) words"); - assert_eq!(size_of::(), 240, "AES-256: 4 * (14 + 1) words"); + assert_eq!(size_of::(), 176, "AES-128: 4 * (10 + 1) words"); + assert_eq!(size_of::(), 208, "AES-192: 4 * (12 + 1) words"); + assert_eq!(size_of::(), 240, "AES-256: 4 * (14 + 1) words"); } #[test] fn test_engine_size_is_exactly_the_schedule() { // No round counter, no direction flag, no initialised marker: the schedule is all there // is, which is what makes both directions available from one value at no extra cost. - assert_eq!(size_of::(), size_of::<::Schedule>()); - assert_eq!(size_of::(), size_of::<::Schedule>()); - assert_eq!(size_of::(), size_of::<::Schedule>()); + assert_eq!(size_of::(), size_of::<::Schedule>()); + assert_eq!(size_of::(), size_of::<::Schedule>()); + assert_eq!(size_of::(), size_of::<::Schedule>()); } #[test] fn test_alg_names() { - assert_eq!(::ALG_NAME, "AES-128"); - assert_eq!(::ALG_NAME, "AES-192"); - assert_eq!(::ALG_NAME, "AES-256"); + assert_eq!(::ALG_NAME, "AES-128"); + assert_eq!(::ALG_NAME, "AES-192"); + assert_eq!(::ALG_NAME, "AES-256"); } #[test] fn test_max_security_strength_matches_the_key_length() { assert_eq!( - ::MAX_SECURITY_STRENGTH, - SecurityStrength::from_bytes(Aes128Params::KEY_LEN) + ::MAX_SECURITY_STRENGTH, + SecurityStrength::from_bytes(AES128Params::KEY_LEN) ); assert_eq!( - ::MAX_SECURITY_STRENGTH, - SecurityStrength::from_bytes(Aes192Params::KEY_LEN) + ::MAX_SECURITY_STRENGTH, + SecurityStrength::from_bytes(AES192Params::KEY_LEN) ); assert_eq!( - ::MAX_SECURITY_STRENGTH, - SecurityStrength::from_bytes(Aes256Params::KEY_LEN) + ::MAX_SECURITY_STRENGTH, + SecurityStrength::from_bytes(AES256Params::KEY_LEN) ); } } diff --git a/crypto/aes/src/cbc.rs b/crypto/aes/src/cbc.rs index c8486ced..2c80bfca 100644 --- a/crypto/aes/src/cbc.rs +++ b/crypto/aes/src/cbc.rs @@ -33,7 +33,7 @@ //! its in-place data methods, is `bouncycastle_modes::Cbc` itself, which these wrap: //! //! ```text -//! bouncycastle_modes::Cbc // block-aligned, in place +//! bouncycastle_modes::Cbc // block-aligned, in place //! AES_CBC_128 // any length, padded //! ``` //! @@ -47,7 +47,7 @@ //! [`Decrypting`](bouncycastle_modes::Decrypting), which was already true. use crate::padded_mode::PaddedMode; -use crate::{Aes128, Aes192, Aes256, BLOCK_LEN}; +use crate::{AES_128, AES_192, AES_256, BLOCK_LEN}; use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; // Imports needed for docs @@ -145,8 +145,8 @@ use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; /// ``` #[allow(non_camel_case_types)] pub type AES_CBC_128 =

, - Cbc, + Cbc, + Cbc, Pad, 16, BLOCK_LEN, @@ -172,8 +172,8 @@ pub type AES_CBC_128 = = , - Cbc, + Cbc, + Cbc, Pad, 24, BLOCK_LEN, @@ -199,8 +199,8 @@ pub type AES_CBC_192 = = , - Cbc, + Cbc, + Cbc, Pad, 32, BLOCK_LEN, diff --git a/crypto/aes/src/cfb.rs b/crypto/aes/src/cfb.rs index be63d775..55539508 100644 --- a/crypto/aes/src/cfb.rs +++ b/crypto/aes/src/cfb.rs @@ -9,7 +9,7 @@ //! different, non-interoperable mode with its own aliases -- [`AES_CFB8_128`](crate::AES_CFB8_128) //! and friends -- and `s = 1` is not implemented; see the `bouncycastle_modes::Cfb` docs. -use crate::{Aes128, Aes192, Aes256, BLOCK_LEN}; +use crate::{AES_128, AES_192, AES_256, BLOCK_LEN}; use bouncycastle_modes::Cfb; /// AES-128 in CFB128 mode. `Dir` is [`bouncycastle_modes::Encrypting`] or @@ -49,7 +49,7 @@ use bouncycastle_modes::Cfb; /// ``` /// #[allow(non_camel_case_types)] -pub type AES_CFB_128 = Cfb; +pub type AES_CFB_128 = Cfb; /// AES-192 in CFB128 mode. See [`AES_CFB_128`]. /// @@ -66,7 +66,7 @@ pub type AES_CFB_128 = Cfb; /// assert_eq!(data, [0u8; 30]); /// ``` #[allow(non_camel_case_types)] -pub type AES_CFB_192 = Cfb; +pub type AES_CFB_192 = Cfb; /// AES-256 in CFB128 mode. See [`AES_CFB_128`]. /// @@ -83,4 +83,4 @@ pub type AES_CFB_192 = Cfb; /// assert_eq!(data, [0u8; 30]); /// ``` #[allow(non_camel_case_types)] -pub type AES_CFB_256 = Cfb; +pub type AES_CFB_256 = Cfb; diff --git a/crypto/aes/src/cfb8.rs b/crypto/aes/src/cfb8.rs index cea63042..1d26481e 100644 --- a/crypto/aes/src/cfb8.rs +++ b/crypto/aes/src/cfb8.rs @@ -10,7 +10,7 @@ //! the work of [`AES_CFB_128`](crate::AES_CFB_128). See the `bouncycastle_modes::Cfb8` docs for //! when that is the right trade. -use crate::{Aes128, Aes192, Aes256, BLOCK_LEN}; +use crate::{AES_128, AES_192, AES_256, BLOCK_LEN}; use bouncycastle_modes::Cfb8; /// AES-128 in CFB8 mode. `Dir` is [`bouncycastle_modes::Encrypting`] or @@ -56,7 +56,7 @@ use bouncycastle_modes::Cfb8; /// assert_ne!(as_cfb128, message); /// ``` #[allow(non_camel_case_types)] -pub type AES_CFB8_128 = Cfb8; +pub type AES_CFB8_128 = Cfb8; /// AES-192 in CFB8 mode. See [`AES_CFB8_128`]. /// @@ -73,7 +73,7 @@ pub type AES_CFB8_128 = Cfb8; /// assert_eq!(data, [0u8; 30]); /// ``` #[allow(non_camel_case_types)] -pub type AES_CFB8_192 = Cfb8; +pub type AES_CFB8_192 = Cfb8; /// AES-256 in CFB8 mode. See [`AES_CFB8_128`]. /// @@ -90,4 +90,4 @@ pub type AES_CFB8_192 = Cfb8; /// assert_eq!(data, [0u8; 30]); /// ``` #[allow(non_camel_case_types)] -pub type AES_CFB8_256 = Cfb8; +pub type AES_CFB8_256 = Cfb8; diff --git a/crypto/aes/src/ctr.rs b/crypto/aes/src/ctr.rs index 73be8e40..6c6e64c8 100644 --- a/crypto/aes/src/ctr.rs +++ b/crypto/aes/src/ctr.rs @@ -13,7 +13,7 @@ //! repeating keystream. A shorter message limit in exchange for more nonce bits is available by //! naming `Ctr` directly with a 13, 14 or 15-byte nonce. -use crate::{Aes128, Aes192, Aes256, BLOCK_LEN}; +use crate::{AES_128, AES_192, AES_256, BLOCK_LEN}; use bouncycastle_modes::Ctr; /// The nonce length these aliases use, leaving a 4-byte counter. @@ -55,7 +55,7 @@ pub const CTR_NONCE_LEN: usize = 12; /// assert_eq!(rest, [1u8; 30]); /// ``` #[allow(non_camel_case_types)] -pub type AES_CTR_128 = Ctr; +pub type AES_CTR_128 = Ctr; /// AES-192 in CTR mode with a 12-byte nonce. See [`AES_CTR_128`]. /// @@ -72,7 +72,7 @@ pub type AES_CTR_128 = Ctr; /// assert_eq!(data, [0u8; 30]); /// ``` #[allow(non_camel_case_types)] -pub type AES_CTR_192 = Ctr; +pub type AES_CTR_192 = Ctr; /// AES-256 in CTR mode with a 12-byte nonce. See [`AES_CTR_128`]. /// @@ -89,4 +89,4 @@ pub type AES_CTR_192 = Ctr; /// assert_eq!(data, [0u8; 30]); /// ``` #[allow(non_camel_case_types)] -pub type AES_CTR_256 = Ctr; +pub type AES_CTR_256 = Ctr; diff --git a/crypto/aes/src/ecb.rs b/crypto/aes/src/ecb.rs index e24da5c5..6683ee36 100644 --- a/crypto/aes/src/ecb.rs +++ b/crypto/aes/src/ecb.rs @@ -39,7 +39,7 @@ //! decryptor adapter. `Dir` must be [`Encrypting`] or [`Decrypting`], as before. use crate::padded_mode::PaddedMode; -use crate::{Aes128, Aes192, Aes256, BLOCK_LEN}; +use crate::{AES_128, AES_192, AES_256, BLOCK_LEN}; use bouncycastle_modes::{Decrypting, Ecb, Encrypting}; // Imports needed for docs @@ -100,8 +100,8 @@ use bouncycastle_padding::{NoPadding, PKCS7}; /// ``` #[allow(non_camel_case_types)] pub type AES_ECB_128 = , - Ecb, + Ecb, + Ecb, Pad, 16, 0, @@ -127,8 +127,8 @@ pub type AES_ECB_128 = = , - Ecb, + Ecb, + Ecb, Pad, 24, 0, @@ -154,8 +154,8 @@ pub type AES_ECB_192 = = , - Ecb, + Ecb, + Ecb, Pad, 32, 0, diff --git a/crypto/aes/src/lib.rs b/crypto/aes/src/lib.rs index ac6fddd0..bf7382a6 100644 --- a/crypto/aes/src/lib.rs +++ b/crypto/aes/src/lib.rs @@ -1,6 +1,6 @@ //! A constant-time, table-free AES block cipher engine (NIST FIPS 197). //! -//! This crate provides the raw AES keyed permutation -- [`Aes128`], [`Aes192`] and [`Aes256`] -- +//! This crate provides the raw AES keyed permutation -- [`AES_128`], [`AES_192`] and [`AES_256`] -- //! implemented as a Boolean circuit over bit-planes rather than as byte substitutions through a //! lookup table. That makes it both smaller and constant-time; see [Design](#design). //! @@ -12,7 +12,7 @@ //! ## Encrypting and decrypting a single block //! //! ``` -//! use bouncycastle_aes::Aes128; +//! use bouncycastle_aes::AES_128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::ElectronicCodeBook; //! @@ -22,7 +22,7 @@ //! KeyType::SymmetricCipherKey, //! ).expect("a 16-byte symmetric cipher key"); //! -//! let aes = Aes128::new(&key).expect("a valid AES-128 key"); +//! let aes = AES_128::new(&key).expect("a valid AES-128 key"); //! //! // FIPS 197 Appendix B. //! let mut block = [0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, @@ -44,13 +44,13 @@ //! [`ElectronicCodeBook::encrypt_block`](bouncycastle_core::traits::ElectronicCodeBook::encrypt_block) calls: //! //! ``` -//! use bouncycastle_aes::Aes256; +//! use bouncycastle_aes::AES_256; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::ElectronicCodeBook; //! //! let key = KeyMaterial::<32>::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey) //! .expect("a 32-byte symmetric cipher key"); -//! let aes = Aes256::new(&key).expect("a valid AES-256 key"); +//! let aes = AES_256::new(&key).expect("a valid AES-256 key"); //! //! let mut blocks = [[0u8; 16], [1u8; 16]]; //! aes.encrypt_blocks2(&mut blocks); @@ -103,7 +103,7 @@ //! For the block-aligned API -- whole blocks in place, with the length checked at compile time -- //! name `bouncycastle_modes::Cbc` directly; that is what these aliases wrap. //! -//! There is no one-shot static on the permutation, because `Aes128::new(&key)?.encrypt_block(..)` +//! There is no one-shot static on the permutation, because `AES_128::new(&key)?.encrypt_block(..)` //! already *is* the one shot. Data-level one-shots belong to the modes of operation, which take //! arbitrary-length input and generate their own initialisation data. //! @@ -139,7 +139,7 @@ //! Decryption follows FIPS 197 Algorithm 3, the straight inverse cipher, rather than the //! equivalent inverse cipher of Sec 5.3.5. Algorithm 3 puts INVMIXCOLUMNS() after ADDROUNDKEY(), //! so it uses the *unmodified* key schedule; the equivalent inverse cipher would need a second -//! schedule with each round key transformed. One [`Aes128`] value therefore encrypts and decrypts +//! schedule with each round key transformed. One [`AES_128`] value therefore encrypts and decrypts //! from one stored schedule. //! //! # Memory Usage @@ -150,9 +150,9 @@ //! //! | Type | Key | `Nr` | Schedule (persistent) | Tables | //! |---|---|---|---|---| -//! | [`Aes128`] | 16 B | 10 | 176 B | 0 B | -//! | [`Aes192`] | 24 B | 12 | 208 B | 0 B | -//! | [`Aes256`] | 32 B | 14 | 240 B | 0 B | +//! | [`AES_128`] | 16 B | 10 | 176 B | 0 B | +//! | [`AES_192`] | 24 B | 12 | 208 B | 0 B | +//! | [`AES_256`] | 32 B | 14 | 240 B | 0 B | //! //! Per-call stack usage is independent of key length: 32 bytes of bit-sliced state for the two //! blocks, 32 bytes for the round key expanded from its compressed form, plus the S-box circuit's @@ -167,7 +167,7 @@ //! //! ## A block permutation is not a cipher //! -//! [`Aes128`] and friends transform exactly 16 bytes. Using them directly on data means ECB, +//! [`AES_128`] and friends transform exactly 16 bytes. Using them directly on data means ECB, //! which is not confidential: identical plaintext blocks produce identical ciphertext blocks, so //! structure in the plaintext survives encryption. **Do not do it.** Use a mode of operation, and //! prefer an authenticated one so that ciphertext tampering is detected. @@ -213,7 +213,7 @@ #![no_std] #![forbid(unsafe_code)] #![forbid(missing_docs)] -// `AesParams` is deliberately sealed with a private supertrait so that no fourth parameter set can +// `AESParams` is deliberately sealed with a private supertrait so that no fourth parameter set can // be added outside this crate; that is what triggers this lint. #![allow(private_bounds)] @@ -229,7 +229,7 @@ mod round; mod sbox; mod schedule; -pub use aes::{Aes128, Aes192, Aes256, BLOCK_LEN}; +pub use aes::{AES_128, AES_192, AES_256, BLOCK_LEN}; pub use cbc::{AES_CBC_128, AES_CBC_192, AES_CBC_256}; pub use cfb::{AES_CFB_128, AES_CFB_192, AES_CFB_256}; pub use cfb8::{AES_CFB8_128, AES_CFB8_192, AES_CFB8_256}; diff --git a/crypto/aes/src/padded_mode.rs b/crypto/aes/src/padded_mode.rs index da4914d5..e9ac6ba2 100644 --- a/crypto/aes/src/padded_mode.rs +++ b/crypto/aes/src/padded_mode.rs @@ -10,8 +10,8 @@ //! //! ```text //! pub type AES_CBC_128 = , // what Encrypting resolves to -//! Cbc, // what Decrypting resolves to +//! Cbc, // what Encrypting resolves to +//! Cbc, // what Decrypting resolves to //! Pad, 16, 16, //! >>::Mode; //! ``` diff --git a/crypto/aes/src/schedule.rs b/crypto/aes/src/schedule.rs index 9ae50e38..8043d75b 100644 --- a/crypto/aes/src/schedule.rs +++ b/crypto/aes/src/schedule.rs @@ -31,15 +31,16 @@ use bouncycastle_utils::secret::{Secret, ZeroizablePrimitive}; /// /// Table 5 gives each as the word `[x, 00, 00, 00]`; only the leftmost byte is ever non-zero, and /// words are held little-endian here, so the word `Rcon[j]` is just this byte. Indexing is shifted -/// by one against the spec: `RCON[j - 1]` is the spec's `Rcon[j]`, since the spec counts from 1. -const RCON: [u32; 10] = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; +/// by one against the spec: `Rcon[j - 1]` here is the spec's `Rcon[j]`, since the spec counts from 1. +#[allow(non_upper_case_globals)] +const Rcon: [u32; 10] = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; /// Prevents a fourth parameter set from being added outside this crate. /// -/// FIPS 197 Sec 6.1 defines exactly three: AES-128, AES-192 and AES-256. Because [`AesParams`] +/// FIPS 197 Sec 6.1 defines exactly three: AES-128, AES-192 and AES-256. Because [`AESParams`] /// has this private supertrait, only the three types in this module can implement it, so no /// downstream crate can instantiate the cipher with an unapproved key length or round count. -trait AesParamsSealed {} +trait AESParamsInternalTrait {} /// The per-key-length constants of FIPS 197 Sec 6.1. /// @@ -48,8 +49,10 @@ trait AesParamsSealed {} /// const-generics; each implementation spells its own array type out instead. The same pattern is /// used by the `HashDRBG80090AParams_*` types in `bouncycastle-rng`. /// -/// Sealed via a private supertrait, so the three types below are the only implementations. -pub trait AesParams: AesParamsSealed { +/// Sealed via a private supertrait, so the three types below are the only implementations. The +/// supertrait is named `*InternalTrait` after the pattern of `MLKEMPrivateKeyInternalTrait` in +/// `bouncycastle-mlkem`, which seals its key types the same way. +pub trait AESParams: AESParamsInternalTrait { /// Key length in bytes: 16, 24 or 32 (FIPS 197 Sec 6.1). const KEY_LEN: usize; /// `Nk`, the key length in 32-bit words: 4, 6 or 8 (FIPS 197 Sec 6.1). @@ -64,19 +67,19 @@ pub trait AesParams: AesParamsSealed { /// AES-128 parameters: 16-byte key, `Nk` = 4, `Nr` = 10 (FIPS 197 Sec 6.1). #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Aes128Params; +pub struct AES128Params; /// AES-192 parameters: 24-byte key, `Nk` = 6, `Nr` = 12 (FIPS 197 Sec 6.1). #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Aes192Params; +pub struct AES192Params; /// AES-256 parameters: 32-byte key, `Nk` = 8, `Nr` = 14 (FIPS 197 Sec 6.1). #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Aes256Params; +pub struct AES256Params; -impl AesParamsSealed for Aes128Params {} -impl AesParamsSealed for Aes192Params {} -impl AesParamsSealed for Aes256Params {} +impl AESParamsInternalTrait for AES128Params {} +impl AESParamsInternalTrait for AES192Params {} +impl AESParamsInternalTrait for AES256Params {} -impl AesParams for Aes128Params { +impl AESParams for AES128Params { const KEY_LEN: usize = 16; const NK: usize = 4; const NR: usize = 10; @@ -84,7 +87,7 @@ impl AesParams for Aes128Params { type Schedule = [u32; 44]; // 4 * (10 + 1) } -impl AesParams for Aes192Params { +impl AESParams for AES192Params { const KEY_LEN: usize = 24; const NK: usize = 6; const NR: usize = 12; @@ -92,7 +95,7 @@ impl AesParams for Aes192Params { type Schedule = [u32; 52]; // 4 * (12 + 1) } -impl AesParams for Aes256Params { +impl AESParams for AES256Params { const KEY_LEN: usize = 32; const NK: usize = 8; const NR: usize = 14; @@ -145,7 +148,7 @@ fn sub_word(word: u32) -> u32 { /// described in the module docs. Verified against the worked expansions in FIPS 197 /// Appendix A.1, A.2 and A.3 by the tests at the bottom of this file, which decompress the /// stored schedule and compare every w[i]. -pub(crate) fn expand(key: &[u8]) -> Secret { +pub(crate) fn expand(key: &[u8]) -> Secret { debug_assert_eq!(key.len(), P::KEY_LEN); let mut schedule = Secret::::new(); @@ -162,7 +165,7 @@ pub(crate) fn expand(key: &[u8]) -> Secret { for i in P::NK..w.len() { if i % P::NK == 0 { // line 10: temp = SUBWORD(ROTWORD(temp)) XOR Rcon[i / Nk] - temp = sub_word(rot_word(temp)) ^ RCON[i / P::NK - 1]; + temp = sub_word(rot_word(temp)) ^ Rcon[i / P::NK - 1]; } else if P::NK > 6 && i % P::NK == 4 { // lines 11-12: the extra substitution that only AES-256 reaches temp = sub_word(temp); @@ -203,7 +206,7 @@ pub(crate) fn expand(key: &[u8]) -> Secret { /// /// Translated from BearSSL `aes_ct.c:br_aes_ct_skey_expand`. #[inline(always)] -pub(crate) fn round_key(schedule: &P::Schedule, round: usize) -> Planes { +pub(crate) fn round_key(schedule: &P::Schedule, round: usize) -> Planes { debug_assert!(round <= P::NR); let w = schedule.as_ref(); let mut sk: Planes = [0u32; 8]; @@ -285,7 +288,7 @@ mod tests { /// leaving the duplicated pre-slicing words with `w[4*round + j]` in position `2j`. This is /// what lets the Appendix A vectors test the real [`expand`] output rather than a /// reimplementation of it. - fn classical_word(schedule: &P::Schedule, i: usize) -> u32 { + fn classical_word(schedule: &P::Schedule, i: usize) -> u32 { let mut q = round_key::

(schedule, i / 4); ortho(&mut q); let j = i % 4; @@ -298,7 +301,7 @@ mod tests { /// Appendix A prints a word as the byte sequence `[a0,a1,a2,a3]` left to right, so the /// tabulated `u32` has `a0` in its *most* significant byte; words are held little-endian /// here, so `swap_bytes` is the conversion. - fn assert_expansion_matches(key: &[u8], expected: &[u32], label: &str) { + fn assert_expansion_matches(key: &[u8], expected: &[u32], label: &str) { let schedule = expand::

(key); assert_eq!(expected.len(), 4 * (P::NR + 1), "{label}: table length"); for (i, &want) in expected.iter().enumerate() { @@ -313,7 +316,7 @@ mod tests { 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, ]; - assert_expansion_matches::(&key, &APPENDIX_A1_WORDS, "Appendix A.1"); + assert_expansion_matches::(&key, &APPENDIX_A1_WORDS, "Appendix A.1"); } #[test] @@ -322,7 +325,7 @@ mod tests { 0x8e, 0x73, 0xb0, 0xf7, 0xda, 0x0e, 0x64, 0x52, 0xc8, 0x10, 0xf3, 0x2b, 0x80, 0x90, 0x79, 0xe5, 0x62, 0xf8, 0xea, 0xd2, 0x52, 0x2c, 0x6b, 0x7b, ]; - assert_expansion_matches::(&key, &APPENDIX_A2_WORDS, "Appendix A.2"); + assert_expansion_matches::(&key, &APPENDIX_A2_WORDS, "Appendix A.2"); } #[test] @@ -332,7 +335,7 @@ mod tests { 0x77, 0x81, 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4, ]; - assert_expansion_matches::(&key, &APPENDIX_A3_WORDS, "Appendix A.3"); + assert_expansion_matches::(&key, &APPENDIX_A3_WORDS, "Appendix A.3"); } #[test] @@ -343,9 +346,9 @@ mod tests { 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, ]; - let schedule = expand::(&key); - for i in 0..Aes128Params::NK { - let got = classical_word::(&schedule, i); + let schedule = expand::(&key); + for i in 0..AES128Params::NK { + let got = classical_word::(&schedule, i); assert_eq!(got.to_le_bytes(), key[4 * i..4 * i + 4]); } } @@ -388,7 +391,7 @@ mod tests { 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, ]; - let schedule = expand::(&key); + let schedule = expand::(&key); // Recompute the classical schedule without the compression step. let mut w = [0u32; 44]; @@ -398,14 +401,14 @@ mod tests { let mut temp = w[3]; for i in 4..44 { if i % 4 == 0 { - temp = sub_word(rot_word(temp)) ^ RCON[i / 4 - 1]; + temp = sub_word(rot_word(temp)) ^ Rcon[i / 4 - 1]; } temp ^= w[i - 4]; w[i] = temp; } - for round in 0..=Aes128Params::NR { - let got = round_key::(&schedule, round); + for round in 0..=AES128Params::NR { + let got = round_key::(&schedule, round); let mut expected: Planes = [0u32; 8]; for j in 0..4 { expected[2 * j] = w[4 * round + j]; @@ -421,25 +424,25 @@ mod tests { // FIPS 197 Sec 5.2: the schedule is 4 * (Nr + 1) words. The array types are written out // by hand per parameter set, so this guards against a typo in one of them. assert_eq!( - size_of::<::Schedule>() / 4, - 4 * (Aes128Params::NR + 1) + size_of::<::Schedule>() / 4, + 4 * (AES128Params::NR + 1) ); assert_eq!( - size_of::<::Schedule>() / 4, - 4 * (Aes192Params::NR + 1) + size_of::<::Schedule>() / 4, + 4 * (AES192Params::NR + 1) ); assert_eq!( - size_of::<::Schedule>() / 4, - 4 * (Aes256Params::NR + 1) + size_of::<::Schedule>() / 4, + 4 * (AES256Params::NR + 1) ); } #[test] fn test_key_len_is_four_times_nk() { // FIPS 197 Sec 6.1 ties the two together; both are declared independently above. - assert_eq!(Aes128Params::KEY_LEN, 4 * Aes128Params::NK); - assert_eq!(Aes192Params::KEY_LEN, 4 * Aes192Params::NK); - assert_eq!(Aes256Params::KEY_LEN, 4 * Aes256Params::NK); + assert_eq!(AES128Params::KEY_LEN, 4 * AES128Params::NK); + assert_eq!(AES192Params::KEY_LEN, 4 * AES192Params::NK); + assert_eq!(AES256Params::KEY_LEN, 4 * AES256Params::NK); } #[test] @@ -453,9 +456,9 @@ mod tests { *slot = u32::from(v); v = (v << 1) ^ if v & 0x80 != 0 { 0x1b } else { 0 }; } - assert_eq!(RCON, expected); + assert_eq!(Rcon, expected); // Spot-check the two values from Table 5 that are not plain powers of two. - assert_eq!(RCON[8], 0x1b); - assert_eq!(RCON[9], 0x36); + assert_eq!(Rcon[8], 0x1b); + assert_eq!(Rcon[9], 0x36); } } diff --git a/crypto/aes/summary.md b/crypto/aes/summary.md index 7f0e3261..4943c216 100644 --- a/crypto/aes/summary.md +++ b/crypto/aes/summary.md @@ -14,7 +14,7 @@ place to start reading the source. ## 1. What this crate is (and is not) -It provides the **raw AES keyed permutation** — `Aes128`, `Aes192`, `Aes256` — transforming exactly +It provides the **raw AES keyed permutation** — `AES_128`, `AES_192`, `AES_256` — transforming exactly 16 bytes at a time. It is not something you can encrypt data with: used directly on data it *is* ECB, which is not confidential. Modes of operation and padding are separate layers. @@ -106,7 +106,7 @@ inverse cipher of Sec 5.3.5. Algorithm 3 applies InvMixColumns *after* AddRoundK **unmodified** key schedule; Sec 5.3.5 reorders the round and needs a separate schedule with InvMixColumns applied to every round key (Algorithm 5, `KEYEXPANSIONEIC()`). -Following Algorithm 3 is what lets one `Aes` value encrypt *and* decrypt from a single stored +Following Algorithm 3 is what lets one `AES` value encrypt *and* decrypt from a single stored schedule — no second copy, no transformation at construction time, no direction flag. That is the whole reason both directions are available at 176–240 bytes of state. @@ -117,7 +117,7 @@ const generic parameter, so a params trait is used instead — the same pattern `HashDRBG80090AParams_*` types in `bouncycastle-rng`: ```rust -pub trait AesParams: AesParamsSealed { +pub trait AESParams: AESParamsInternalTrait { const KEY_LEN: usize; // 16 | 24 | 32 (FIPS 197 Sec 6.1) const NK: usize; // 4 | 6 | 8 const NR: usize; // 10 | 12 | 14 @@ -126,7 +126,7 @@ pub trait AesParams: AesParamsSealed { } ``` -`AesParams` has a **private** supertrait, so only the three types in `schedule.rs` can implement +`AESParams` has a **private** supertrait, so only the three types in `schedule.rs` can implement it and no downstream crate can instantiate the cipher with an unapproved key length or round count. (This is what `#![allow(private_bounds)]` in `lib.rs` is for.) @@ -144,9 +144,9 @@ stack when the round loop needs it. | Type | Key | `Nr` | Schedule (persistent) | Tables | |---|---|---|---|---| -| `Aes128` | 16 B | 10 | 176 B | 0 B | -| `Aes192` | 24 B | 12 | 208 B | 0 B | -| `Aes256` | 32 B | 14 | 240 B | 0 B | +| `AES_128` | 16 B | 10 | 176 B | 0 B | +| `AES_192` | 24 B | 12 | 208 B | 0 B | +| `AES_256` | 32 B | 14 | 240 B | 0 B | These are **measured**, not asserted — `cargo run --release -p mem_usage_benches --bin bench_aes_mem_usage` prints exactly 176/208/240, and `test_engine_sizes_match_the_documented_memory_table` pins them so @@ -163,7 +163,7 @@ Per-call stack usage is independent of key length: 32 B of bit-sliced state for ### 2.7 API surface ```rust -Aes128::new(&KeyMaterial<16>) -> Result // and 24 / 32 +AES_128::new(&KeyMaterial<16>) -> Result // and 24 / 32 aes.encrypt_block(&mut [u8; 16]) // infallible aes.decrypt_block(&mut [u8; 16]) aes.encrypt_blocks2(&mut [[u8; 16]; 2]) // the natural unit of work @@ -172,7 +172,7 @@ aes.decrypt_blocks2(&mut [[u8; 16]; 2]) No `init()`, no `reset()`, no direction flag: constructors set up state and a constructed value is always ready. There are no one-shot statics on the permutation because -`Aes128::new(&key)?.encrypt_block(..)` already *is* the one shot; data-level one-shots belong to the +`AES_128::new(&key)?.encrypt_block(..)` already *is* the one shot; data-level one-shots belong to the modes, which take arbitrary-length input and generate their own initialisation data. `encrypt_blocks2` / `decrypt_blocks2` are the pair form and roughly double throughput. A @@ -197,8 +197,8 @@ half is never returned either way. | [`src/bitslice.rs`](src/bitslice.rs) | 210 | `ortho`, `pack`, `unpack`; the layout table and its exhaustive test | | [`src/sbox.rs`](src/sbox.rs) | 377 | The 113-gate circuit; `inv_sbox`; Tables 4 and 6 for tests | | [`src/round.rs`](src/round.rs) | 507 | AddRoundKey, ShiftRows, MixColumns and inverses; byte-wise references | -| [`src/schedule.rs`](src/schedule.rs) | 456 | `AesParams`, `expand` (Alg 2), `round_key`; Appendix A tables | -| [`src/aes.rs`](src/aes.rs) | 276 | `Aes

`, the three aliases, Alg 1 and Alg 3, key validation | +| [`src/schedule.rs`](src/schedule.rs) | 456 | `AESParams`, `expand` (Alg 2), `round_key`; Appendix A tables | +| [`src/aes.rs`](src/aes.rs) | 276 | `AES

`, the three aliases, Alg 1 and Alg 3, key validation | | [`tests/fips197_tests.rs`](tests/fips197_tests.rs) | 230 | Appendix B; two-block path; key handling | | [`tests/sp800_38a_tests.rs`](tests/sp800_38a_tests.rs) | 176 | SP 800-38A F.1.1–F.1.6 | | [`tests/acvp_tests.rs`](tests/acvp_tests.rs) | 266 | NIST ACVP `ACVP-AES-ECB` loader | @@ -226,7 +226,7 @@ recall** — every one is transcribed from a downloaded specification PDF or an | FIPS 197 Sec 5.1.1 | The worked example `S[{53}] = {ed}`. | | FIPS 197 Eq 5.5 / 5.8 / 5.12 / 5.15 | ShiftRows and MixColumns and their inverses, against byte-wise references written from the equations — plus a second literal transcription of Eq 5.8/5.15 cross-checking the matrix form. | | FIPS 197 Sec 4.2 / Eq 4.5 | The test-only `xtimes`/`gf_mul` helpers against the Sec 4.2 worked chain and `{57}·{13} = {fe}`. | -| FIPS 197 Table 5 | `RCON` re-derived by repeated XTIMES and compared. | +| FIPS 197 Table 5 | `Rcon` re-derived by repeated XTIMES and compared. | | FIPS 197 Appendix A.1/A.2/A.3 | **Every one of the 156 schedule words**, for all three key lengths. | | FIPS 197 Appendix B | The worked AES-128 block, both directions, and via the two-block path in both slots. | | SP 800-38A F.1.1–F.1.6 | ECB known answers, all three key lengths, both directions. | @@ -256,7 +256,7 @@ Two details worth knowing: * Some AFT cases have multi-block plaintexts, so the loader iterates blocks (ECB). * The set includes **all-zero keys** (the GFSbox-style groups). `KeyMaterial` tags an all-zero buffer `Zeroized` and refuses to promote it outside a hazardous closure — which is the right - default, and `Aes128::new` rejecting it is itself tested. The *test* opts in via + default, and `AES_128::new` rejecting it is itself tested. The *test* opts in via `do_hazardous_operations`; the engine's guard was **not** weakened to accommodate NIST. ### Only the ECB file belongs to this crate @@ -350,11 +350,11 @@ not have to repeat this investigation. #### The one real gap, fixed -**`< → >` in `Aes

::validate`.** There was no test for a key whose security strength is *below* +**`< → >` in `AES

::validate`.** There was no test for a key whose security strength is *below* the level its length implies; because `from_bytes_as_type` always tags a key at its length-implied strength, neither `<` nor `>` was ever true and the two comparisons behaved identically. `a_key_carrying_too_low_a_security_strength_is_rejected` now covers it (a 32-byte key lowered to -128-bit must be rejected by `Aes256::new`), and the fix was confirmed by hand-applying the mutation +128-bit must be rejected by `AES_256::new`), and the fix was confirmed by hand-applying the mutation and watching that test fail, then reverting. This mutant still appears in the run output above, which analysed the pre-fix source — the fix diff --git a/crypto/aes/tests/acvp_tests.rs b/crypto/aes/tests/acvp_tests.rs index 453d738a..c4d35005 100644 --- a/crypto/aes/tests/acvp_tests.rs +++ b/crypto/aes/tests/acvp_tests.rs @@ -44,7 +44,7 @@ //! implementing it from anything other than that specification would be guesswork. The test //! reports how many it skipped so the gap is visible rather than silent. -use bouncycastle_aes::{Aes128, Aes192, Aes256, BLOCK_LEN}; +use bouncycastle_aes::{AES_128, AES_192, AES_256, BLOCK_LEN}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -82,7 +82,7 @@ fn test_data_dir() -> Option { /// The ACVP set deliberately includes an all-zero key (the GFSbox-style groups vary only the /// plaintext under a zero key). `KeyMaterial` tags an all-zero buffer as [`KeyType::Zeroized`] /// and will not promote it outside a [`do_hazardous_operations`] closure, which is the right -/// default -- an all-zero key normally means a broken RNG, and `Aes128::new` rejecting it is +/// default -- an all-zero key normally means a broken RNG, and `AES_128::new` rejecting it is /// tested in `fips197_tests.rs`. Here the zero key is deliberate and comes from NIST, so this /// opts in explicitly rather than the library weakening its guard. fn cipher_key(bytes: &[u8]) -> KeyMaterial { @@ -111,7 +111,7 @@ fn ecb(key: &[u8], data: &[u8], encrypt: bool) -> Vec { let transform: BlockTransform = match key.len() { 16 => { let km = cipher_key::<16>(key); - let aes = Aes128::new(&km).expect("valid AES-128 key"); + let aes = AES_128::new(&km).expect("valid AES-128 key"); if encrypt { Box::new(move |b| aes.encrypt_block(b)) } else { @@ -120,7 +120,7 @@ fn ecb(key: &[u8], data: &[u8], encrypt: bool) -> Vec { } 24 => { let km = cipher_key::<24>(key); - let aes = Aes192::new(&km).expect("valid AES-192 key"); + let aes = AES_192::new(&km).expect("valid AES-192 key"); if encrypt { Box::new(move |b| aes.encrypt_block(b)) } else { @@ -129,7 +129,7 @@ fn ecb(key: &[u8], data: &[u8], encrypt: bool) -> Vec { } 32 => { let km = cipher_key::<32>(key); - let aes = Aes256::new(&km).expect("valid AES-256 key"); + let aes = AES_256::new(&km).expect("valid AES-256 key"); if encrypt { Box::new(move |b| aes.encrypt_block(b)) } else { @@ -158,21 +158,21 @@ fn ecb_pairwise(key: &[u8], data: &[u8], encrypt: bool) -> Vec { match key.len() { 16 => { let km = cipher_key::<16>(key); - let aes = Aes128::new(&km).unwrap(); + let aes = AES_128::new(&km).unwrap(); run_pairwise(&mut blocks, encrypt, |p, e| { if e { aes.encrypt_blocks2(p) } else { aes.decrypt_blocks2(p) } }); } 24 => { let km = cipher_key::<24>(key); - let aes = Aes192::new(&km).unwrap(); + let aes = AES_192::new(&km).unwrap(); run_pairwise(&mut blocks, encrypt, |p, e| { if e { aes.encrypt_blocks2(p) } else { aes.decrypt_blocks2(p) } }); } 32 => { let km = cipher_key::<32>(key); - let aes = Aes256::new(&km).unwrap(); + let aes = AES_256::new(&km).unwrap(); run_pairwise(&mut blocks, encrypt, |p, e| { if e { aes.encrypt_blocks2(p) } else { aes.decrypt_blocks2(p) } }); diff --git a/crypto/aes/tests/cbc_alias_tests.rs b/crypto/aes/tests/cbc_alias_tests.rs index 3ec2fbd6..bb0f4529 100644 --- a/crypto/aes/tests/cbc_alias_tests.rs +++ b/crypto/aes/tests/cbc_alias_tests.rs @@ -5,7 +5,7 @@ //! the padding scheme changes the behaviour rather than being decorative. The mode and the padding //! layer are tested in their own crates; this checks the wiring between them. -use bouncycastle_aes::{AES_CBC_128, AES_CBC_192, AES_CBC_256, Aes128}; +use bouncycastle_aes::{AES_128, AES_CBC_128, AES_CBC_192, AES_CBC_256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; @@ -27,11 +27,11 @@ fn the_aliases_name_the_expected_types() { assert_eq!( size_of::>(), - size_of::, PKCS7, 16, 16, 16>>() + size_of::, PKCS7, 16, 16, 16>>() ); assert_eq!( size_of::>(), - size_of::, PKCS7, 16, 16, 16>>() + size_of::, PKCS7, 16, 16, 16>>() ); // The two directions are genuinely different types, so the encryptor and the decryptor do not diff --git a/crypto/aes/tests/ecb_alias_tests.rs b/crypto/aes/tests/ecb_alias_tests.rs index d29773f2..6ad0cf4a 100644 --- a/crypto/aes/tests/ecb_alias_tests.rs +++ b/crypto/aes/tests/ecb_alias_tests.rs @@ -6,7 +6,7 @@ //! here is that its `INIT_DATA_LEN` is 0, so the projection must carry a different value than CBC's //! and the aliases must still resolve correctly. -use bouncycastle_aes::{AES_ECB_128, AES_ECB_192, AES_ECB_256, Aes128}; +use bouncycastle_aes::{AES_128, AES_ECB_128, AES_ECB_192, AES_ECB_256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; use bouncycastle_modes::{Decrypting, Ecb, Encrypting}; @@ -24,11 +24,11 @@ fn the_aliases_name_the_expected_types() { assert_eq!( size_of::>(), - size_of::, PKCS7, 16, 0, 16>>() + size_of::, PKCS7, 16, 0, 16>>() ); assert_eq!( size_of::>(), - size_of::, PKCS7, 16, 0, 16>>() + size_of::, PKCS7, 16, 0, 16>>() ); } diff --git a/crypto/aes/tests/electronic_code_book_tests.rs b/crypto/aes/tests/electronic_code_book_tests.rs index d222471b..f387be95 100644 --- a/crypto/aes/tests/electronic_code_book_tests.rs +++ b/crypto/aes/tests/electronic_code_book_tests.rs @@ -6,20 +6,20 @@ //! properties matters here specifically: this crate overrides `encrypt_blocks2` and //! `decrypt_blocks2`, so the default implementation is not what runs. -use bouncycastle_aes::{Aes128, Aes192, Aes256, BLOCK_LEN}; +use bouncycastle_aes::{AES_128, AES_192, AES_256, BLOCK_LEN}; use bouncycastle_core_test_framework::electronic_code_book::TestFrameworkElectronicCodeBook; #[test] fn aes128_conforms_to_electronic_code_book() { - TestFrameworkElectronicCodeBook::new().test::<16, BLOCK_LEN, Aes128>(); + TestFrameworkElectronicCodeBook::new().test::<16, BLOCK_LEN, AES_128>(); } #[test] fn aes192_conforms_to_electronic_code_book() { - TestFrameworkElectronicCodeBook::new().test::<24, BLOCK_LEN, Aes192>(); + TestFrameworkElectronicCodeBook::new().test::<24, BLOCK_LEN, AES_192>(); } #[test] fn aes256_conforms_to_electronic_code_book() { - TestFrameworkElectronicCodeBook::new().test::<32, BLOCK_LEN, Aes256>(); + TestFrameworkElectronicCodeBook::new().test::<32, BLOCK_LEN, AES_256>(); } diff --git a/crypto/aes/tests/fips197_tests.rs b/crypto/aes/tests/fips197_tests.rs index aa01f6d6..7ea4a818 100644 --- a/crypto/aes/tests/fips197_tests.rs +++ b/crypto/aes/tests/fips197_tests.rs @@ -14,7 +14,7 @@ //! //! All values here are transcribed from the published FIPS 197 (Update 1) PDF. -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ElectronicCodeBook, SecurityStrength}; @@ -47,7 +47,7 @@ fn appendix_b_encrypts_the_documented_block() { // Key = 2b 7e 15 16 28 ae d2 a6 ab f7 15 88 09 cf 4f 3c // The final state printed as "output" reads, column by column (Eq 3.7): // 39 25 84 1d 02 dc 09 fb dc 11 85 97 19 6a 0b 32 - let aes = Aes128::new(&key_material(&KEY_128)).unwrap(); + let aes = AES_128::new(&key_material(&KEY_128)).unwrap(); let mut block = [ 0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, @@ -65,7 +65,7 @@ fn appendix_b_encrypts_the_documented_block() { #[test] fn appendix_b_decrypts_back_to_the_documented_input() { - let aes = Aes128::new(&key_material(&KEY_128)).unwrap(); + let aes = AES_128::new(&key_material(&KEY_128)).unwrap(); let mut block = [ 0x39, 0x25, 0x84, 0x1d, 0x02, 0xdc, 0x09, 0xfb, 0xdc, 0x11, 0x85, 0x97, 0x19, 0x6a, 0x0b, @@ -83,7 +83,7 @@ fn appendix_b_decrypts_back_to_the_documented_input() { #[test] fn appendix_b_two_block_path_agrees_with_the_single_block_path() { - let aes = Aes128::new(&key_material(&KEY_128)).unwrap(); + let aes = AES_128::new(&key_material(&KEY_128)).unwrap(); let input = [ 0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, 0x34, @@ -117,9 +117,9 @@ fn appendix_b_two_block_path_agrees_with_the_single_block_path() { /// deliberately makes no claim about the schedule being *correct* -- see the module docs. #[test] fn encryption_and_decryption_are_inverses_for_all_three_key_lengths() { - let aes128 = Aes128::new(&key_material(&KEY_128)).unwrap(); - let aes192 = Aes192::new(&key_material(&KEY_192)).unwrap(); - let aes256 = Aes256::new(&key_material(&KEY_256)).unwrap(); + let aes128 = AES_128::new(&key_material(&KEY_128)).unwrap(); + let aes192 = AES_192::new(&key_material(&KEY_192)).unwrap(); + let aes256 = AES_256::new(&key_material(&KEY_256)).unwrap(); for block in [[0u8; 16], [0xFFu8; 16], core::array::from_fn(|i| i as u8)] { let mut b = block; @@ -149,9 +149,9 @@ fn encryption_and_decryption_are_inverses_for_all_three_key_lengths() { fn the_three_key_lengths_are_distinct_permutations() { // A key whose first 16 bytes are shared, so only Nk/Nr and the extra key bytes differ. let shared = [0x11u8; 32]; - let aes128 = Aes128::new(&key_material::<16>(&shared[..16].try_into().unwrap())).unwrap(); - let aes192 = Aes192::new(&key_material::<24>(&shared[..24].try_into().unwrap())).unwrap(); - let aes256 = Aes256::new(&key_material(&shared)).unwrap(); + let aes128 = AES_128::new(&key_material::<16>(&shared[..16].try_into().unwrap())).unwrap(); + let aes192 = AES_192::new(&key_material::<24>(&shared[..24].try_into().unwrap())).unwrap(); + let aes256 = AES_256::new(&key_material(&shared)).unwrap(); let block = [0x42u8; 16]; let mut b128 = block; @@ -173,10 +173,10 @@ fn a_key_of_the_wrong_type_is_rejected() { // KeyType::Seed is not a cipher key: a seed reused directly as an AES key is a real mistake // and the type system tracks enough to catch it. let key = KeyMaterial::<16>::from_bytes_as_type(&[0x01; 16], KeyType::Seed).unwrap(); - assert!(Aes128::new(&key).is_err()); + assert!(AES_128::new(&key).is_err()); let key = KeyMaterial::<16>::from_bytes_as_type(&[0x01; 16], KeyType::MACKey).unwrap(); - assert!(Aes128::new(&key).is_err()); + assert!(AES_128::new(&key).is_err()); } #[test] @@ -185,7 +185,7 @@ fn a_key_of_the_wrong_length_is_rejected() { // parameter set. This is the one length error the const generic cannot catch by itself. let key = KeyMaterial::<32>::from_bytes_as_type(&[0x01; 16], KeyType::SymmetricCipherKey).unwrap(); - assert!(Aes256::new(&key).is_err()); + assert!(AES_256::new(&key).is_err()); } #[test] @@ -200,7 +200,7 @@ fn a_key_carrying_too_low_a_security_strength_is_rejected() { key.set_security_strength(SecurityStrength::_128bit).unwrap(); assert!( - Aes256::new(&key).is_err(), + AES_256::new(&key).is_err(), "AES-256 must reject a 32-byte key only derived at the 128-bit strength" ); @@ -208,20 +208,20 @@ fn a_key_carrying_too_low_a_security_strength_is_rejected() { // not about anything else having gone wrong with the key. let good = KeyMaterial::<32>::from_bytes_as_type(&[0x01; 32], KeyType::SymmetricCipherKey).unwrap(); - assert!(Aes256::new(&good).is_ok()); + assert!(AES_256::new(&good).is_ok()); } #[test] fn a_correctly_typed_key_of_each_length_is_accepted() { - assert!(Aes128::new(&key_material(&KEY_128)).is_ok()); - assert!(Aes192::new(&key_material(&KEY_192)).is_ok()); - assert!(Aes256::new(&key_material(&KEY_256)).is_ok()); + assert!(AES_128::new(&key_material(&KEY_128)).is_ok()); + assert!(AES_192::new(&key_material(&KEY_192)).is_ok()); + assert!(AES_256::new(&key_material(&KEY_256)).is_ok()); } #[test] fn debug_does_not_print_the_key_schedule() { // The schedule is secret; `Debug` must not be a way to leak it. - let aes = Aes128::new(&key_material(&KEY_128)).unwrap(); + let aes = AES_128::new(&key_material(&KEY_128)).unwrap(); let rendered = format!("{aes:?}"); assert_eq!(rendered, "AES-128"); // No byte of the key should appear as hex in the output. diff --git a/crypto/aes/tests/sp800_38a_tests.rs b/crypto/aes/tests/sp800_38a_tests.rs index c4314182..c13e4afb 100644 --- a/crypto/aes/tests/sp800_38a_tests.rs +++ b/crypto/aes/tests/sp800_38a_tests.rs @@ -15,7 +15,7 @@ //! //! Transcribed from the published SP 800-38A PDF, sections F.1.1 through F.1.6. -use bouncycastle_aes::{Aes128, Aes192, Aes256, BLOCK_LEN}; +use bouncycastle_aes::{AES_128, AES_192, AES_256, BLOCK_LEN}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::ElectronicCodeBook; use bouncycastle_hex as hex; @@ -73,7 +73,7 @@ fn key_material(hex_str: &str) -> KeyMaterial { #[test] fn f_1_1_ecb_aes128_encrypt() { - let aes = Aes128::new(&key_material::<16>(KEY_128)).unwrap(); + let aes = AES_128::new(&key_material::<16>(KEY_128)).unwrap(); for (i, (pt, ct)) in PLAINTEXTS.iter().zip(CIPHERTEXTS_128.iter()).enumerate() { let mut b = block(pt); aes.encrypt_block(&mut b); @@ -83,7 +83,7 @@ fn f_1_1_ecb_aes128_encrypt() { #[test] fn f_1_2_ecb_aes128_decrypt() { - let aes = Aes128::new(&key_material::<16>(KEY_128)).unwrap(); + let aes = AES_128::new(&key_material::<16>(KEY_128)).unwrap(); for (i, (pt, ct)) in PLAINTEXTS.iter().zip(CIPHERTEXTS_128.iter()).enumerate() { let mut b = block(ct); aes.decrypt_block(&mut b); @@ -95,7 +95,7 @@ fn f_1_2_ecb_aes128_decrypt() { #[test] fn f_1_3_ecb_aes192_encrypt() { - let aes = Aes192::new(&key_material::<24>(KEY_192)).unwrap(); + let aes = AES_192::new(&key_material::<24>(KEY_192)).unwrap(); for (i, (pt, ct)) in PLAINTEXTS.iter().zip(CIPHERTEXTS_192.iter()).enumerate() { let mut b = block(pt); aes.encrypt_block(&mut b); @@ -105,7 +105,7 @@ fn f_1_3_ecb_aes192_encrypt() { #[test] fn f_1_4_ecb_aes192_decrypt() { - let aes = Aes192::new(&key_material::<24>(KEY_192)).unwrap(); + let aes = AES_192::new(&key_material::<24>(KEY_192)).unwrap(); for (i, (pt, ct)) in PLAINTEXTS.iter().zip(CIPHERTEXTS_192.iter()).enumerate() { let mut b = block(ct); aes.decrypt_block(&mut b); @@ -117,7 +117,7 @@ fn f_1_4_ecb_aes192_decrypt() { #[test] fn f_1_5_ecb_aes256_encrypt() { - let aes = Aes256::new(&key_material::<32>(KEY_256)).unwrap(); + let aes = AES_256::new(&key_material::<32>(KEY_256)).unwrap(); for (i, (pt, ct)) in PLAINTEXTS.iter().zip(CIPHERTEXTS_256.iter()).enumerate() { let mut b = block(pt); aes.encrypt_block(&mut b); @@ -127,7 +127,7 @@ fn f_1_5_ecb_aes256_encrypt() { #[test] fn f_1_6_ecb_aes256_decrypt() { - let aes = Aes256::new(&key_material::<32>(KEY_256)).unwrap(); + let aes = AES_256::new(&key_material::<32>(KEY_256)).unwrap(); for (i, (pt, ct)) in PLAINTEXTS.iter().zip(CIPHERTEXTS_256.iter()).enumerate() { let mut b = block(ct); aes.decrypt_block(&mut b); @@ -144,7 +144,7 @@ fn f_1_6_ecb_aes256_decrypt() { /// puts the same data in both halves. #[test] fn two_block_path_matches_the_f_1_vectors() { - let aes = Aes128::new(&key_material::<16>(KEY_128)).unwrap(); + let aes = AES_128::new(&key_material::<16>(KEY_128)).unwrap(); // Blocks 1 and 2 as a pair, then 3 and 4. for chunk in 0..2 { @@ -163,7 +163,7 @@ fn two_block_path_matches_the_f_1_vectors() { /// Swapping the two slots must swap the two results, and nothing else. #[test] fn two_block_path_is_slot_symmetric() { - let aes = Aes256::new(&key_material::<32>(KEY_256)).unwrap(); + let aes = AES_256::new(&key_material::<32>(KEY_256)).unwrap(); let mut forward = [block(PLAINTEXTS[0]), block(PLAINTEXTS[1])]; let mut reversed = [block(PLAINTEXTS[1]), block(PLAINTEXTS[0])]; diff --git a/crypto/modes/benches/modes_benches.rs b/crypto/modes/benches/modes_benches.rs index 697f16c9..e80c05b0 100644 --- a/crypto/modes/benches/modes_benches.rs +++ b/crypto/modes/benches/modes_benches.rs @@ -21,9 +21,9 @@ //! blocks: every such call ends mid-segment and the next one starts by finishing it byte by byte, //! so they show what the byte path costs relative to the block path at a comparable call length. //! -//! The `modes::cfb8::Aes128` group measures the other thing worth knowing about CFB8: it spends one +//! The `modes::cfb8::AES_128` group measures the other thing worth knowing about CFB8: it spends one //! full forward cipher per *byte*, so on a 16-byte block it should come out at roughly **1/16** the -//! throughput of CFB over the same 16 KiB. That ratio, against `modes::cfb::Aes128`, is the number +//! throughput of CFB over the same 16 KiB. That ratio, against `modes::cfb::AES_128`, is the number //! to watch; it is inherent to `s = 8` (Sec 6.3 discards `b - s` bits of every output block), not a //! property of this implementation. Decryption should still beat encryption, because CFB8 //! decryption builds its input blocks in series and then batches the ciphers eight at a time while @@ -32,12 +32,12 @@ //! The cipher works in place, so each measurement runs on a fresh copy of the data made in //! criterion's untimed setup (`iter_batched`); the copy is not part of the timing. //! -//! The `modes::cbc::Aes128` and `modes::cfb::Aes128` groups are directly comparable -- same cipher, +//! The `modes::cbc::AES_128` and `modes::cfb::AES_128` groups are directly comparable -- same cipher, //! same data, same call granularity -- so the difference between them is the cost of the mode. CFB //! never calls the inverse cipher, so on an engine whose inverse is slower than its forward //! direction, CFB decryption is expected to come out ahead of CBC decryption. -use bouncycastle_aes::{Aes128, Aes256}; +use bouncycastle_aes::{AES_128, AES_256}; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ @@ -53,26 +53,26 @@ const BLOCK_LEN: usize = 16; const NUM_BLOCKS: usize = 1024; const DATA_LEN: usize = NUM_BLOCKS * BLOCK_LEN; -type Aes128Cbc

= Cbc; -type Aes256Cbc = Cbc; -type Aes128Cfb = Cfb; -type Aes256Cfb = Cfb; -type Aes128Cfb8 = Cfb8; -type Aes128Ctr = Ctr; -type Aes256Ctr = Ctr; -type Aes128Ecb = Ecb; +type Aes128Cbc = Cbc; +type Aes256Cbc = Cbc; +type Aes128Cfb = Cfb; +type Aes256Cfb = Cfb; +type Aes128Cfb8 = Cfb8; +type Aes128Ctr = Ctr; +type Aes256Ctr = Ctr; +type Aes128Ecb = Ecb; /// AES-128 with the pair methods **not** overridden, so they fall back to the trait defaults of /// two single-block calls. /// -/// This exists purely to isolate the value of the pair path. Comparing `Cbc` against +/// This exists purely to isolate the value of the pair path. Comparing `Cbc` against /// `Cbc` at the *same* `N` holds everything else fixed -- same cipher, same /// call granularity, same amount of data movement -- so the difference is attributable to /// `decrypt_blocks2` and nothing else. /// /// Comparing `N = 1` against `N = 8` does *not* isolate it: encryption, which can never pair, also /// speeds up substantially between those two, so call granularity dominates that comparison. -struct UnpairedAes128(Aes128); +struct UnpairedAes128(AES_128); impl Algorithm for UnpairedAes128 { const ALG_NAME: &'static str = "AES-128 (unpaired)"; @@ -81,13 +81,13 @@ impl Algorithm for UnpairedAes128 { impl ElectronicCodeBook<16, BLOCK_LEN> for UnpairedAes128 { fn new(key: &KeyMaterial<16>) -> Result { - Ok(Self(>::new(key)?)) + Ok(Self(>::new(key)?)) } fn encrypt_block(&self, block: &mut [u8; BLOCK_LEN]) { - >::encrypt_block(&self.0, block) + >::encrypt_block(&self.0, block) } fn decrypt_block(&self, block: &mut [u8; BLOCK_LEN]) { - >::decrypt_block(&self.0, block) + >::decrypt_block(&self.0, block) } // encrypt_blocks2 / decrypt_blocks2 deliberately left as the trait defaults. } @@ -111,7 +111,7 @@ fn bench_aes128(c: &mut Criterion) { let k = key::<16>(); let blocks = data(); - let mut group = c.benchmark_group("modes::cbc::Aes128"); + let mut group = c.benchmark_group("modes::cbc::AES_128"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); // ---- encryption: serial, one block at a time is all it can do ---- @@ -260,7 +260,7 @@ fn bench_aes256(c: &mut Criterion) { let k = key::<32>(); let blocks = data(); - let mut group = c.benchmark_group("modes::cbc::Aes256"); + let mut group = c.benchmark_group("modes::cbc::AES_256"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); group.bench_function("16KiB encrypt -- N=8", |b| { @@ -343,7 +343,7 @@ fn bench_cfb_aes128(c: &mut Criterion) { let blocks = data(); let flat: Vec = blocks.as_flattened().to_vec(); - let mut group = c.benchmark_group("modes::cfb::Aes128"); + let mut group = c.benchmark_group("modes::cfb::AES_128"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); // ---- encryption: serial. Oj+1 = CIPH_K(Cj), and Cj is the previous call's output ---- @@ -441,7 +441,7 @@ fn bench_cfb_aes256(c: &mut Criterion) { let k = key::<32>(); let flat: Vec = data().as_flattened().to_vec(); - let mut group = c.benchmark_group("modes::cfb::Aes256"); + let mut group = c.benchmark_group("modes::cfb::AES_256"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); group.bench_function("16KiB encrypt -- N=8", |b| { @@ -492,7 +492,7 @@ fn bench_cfb8_aes128(c: &mut Criterion) { let k = key::<16>(); let flat: Vec = data().as_flattened().to_vec(); - let mut group = c.benchmark_group("modes::cfb8::Aes128"); + let mut group = c.benchmark_group("modes::cfb8::AES_128"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); // Serial by construction: I_{j+1} needs Cj, which this call just produced. @@ -549,7 +549,7 @@ fn bench_ctr_aes128(c: &mut Criterion) { let k = key::<16>(); let flat: Vec = data().as_flattened().to_vec(); - let mut group = c.benchmark_group("modes::ctr::Aes128"); + let mut group = c.benchmark_group("modes::ctr::AES_128"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); for (name, call_len) in [ @@ -604,7 +604,7 @@ fn bench_ctr_aes256(c: &mut Criterion) { let k = key::<32>(); let flat: Vec = data().as_flattened().to_vec(); - let mut group = c.benchmark_group("modes::ctr::Aes256"); + let mut group = c.benchmark_group("modes::ctr::AES_256"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); group.bench_function("16KiB encrypt -- N=8", |b| { @@ -633,7 +633,7 @@ fn bench_ecb_aes128(c: &mut Criterion) { let k = key::<16>(); let blocks = data(); - let mut group = c.benchmark_group("modes::ecb::Aes128"); + let mut group = c.benchmark_group("modes::ecb::AES_128"); group.throughput(Throughput::Bytes(DATA_LEN as u64)); group.bench_function("16KiB encrypt -- N=1 (no batching)", |b| { @@ -711,15 +711,15 @@ fn bench_init(c: &mut Criterion) { let mut group = c.benchmark_group("modes::init"); - group.bench_function("Aes128 do_encrypt_init (key schedule + IV)", |b| { + group.bench_function("AES_128 do_encrypt_init (key schedule + IV)", |b| { b.iter(|| black_box(Aes128Cbc::::do_encrypt_init(black_box(&k128)).unwrap().1)) }); - group.bench_function("Aes128 do_decrypt_init (key schedule only)", |b| { + group.bench_function("AES_128 do_decrypt_init (key schedule only)", |b| { b.iter(|| { black_box(Aes128Cbc::::do_decrypt_init(black_box(&k128), &iv).unwrap()) }) }); - group.bench_function("Aes256 do_decrypt_init (key schedule only)", |b| { + group.bench_function("AES_256 do_decrypt_init (key schedule only)", |b| { b.iter(|| { black_box(Aes256Cbc::::do_decrypt_init(black_box(&k256), &iv).unwrap()) }) @@ -728,10 +728,10 @@ fn bench_init(c: &mut Criterion) { // CFB does exactly the same work here -- one key expansion, plus an IV draw when encrypting -- // so these should match the CBC numbers. A divergence would mean one mode is doing something // extra at construction time. - group.bench_function("Aes128 do_encrypt_init, CFB (key schedule + IV)", |b| { + group.bench_function("AES_128 do_encrypt_init, CFB (key schedule + IV)", |b| { b.iter(|| black_box(Aes128Cfb::::do_encrypt_init(black_box(&k128)).unwrap().1)) }); - group.bench_function("Aes128 do_decrypt_init, CFB (key schedule only)", |b| { + group.bench_function("AES_128 do_decrypt_init, CFB (key schedule only)", |b| { b.iter(|| { black_box(Aes128Cfb::::do_decrypt_init(black_box(&k128), &iv).unwrap()) }) diff --git a/crypto/modes/src/ctr.rs b/crypto/modes/src/ctr.rs index 21559835..a5545f44 100644 --- a/crypto/modes/src/ctr.rs +++ b/crypto/modes/src/ctr.rs @@ -135,41 +135,41 @@ use core::marker::PhantomData; /// A nonce as long as the block would leave no counter at all, and could not count: /// /// ```compile_fail -/// use bouncycastle_aes::Aes128; +/// use bouncycastle_aes::AES_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::StreamCipherEncryptor; /// use bouncycastle_modes::{Ctr, Encrypting}; /// /// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); /// // A 16-byte nonce on a 16-byte block leaves a zero-byte counter. -/// let _ = Ctr::::do_encrypt_init(&key); +/// let _ = Ctr::::do_encrypt_init(&key); /// ``` /// /// ...and a nonce shorter than `BLOCK_LEN - 4` would ask for a counter wider than this type /// supports: /// /// ```compile_fail -/// use bouncycastle_aes::Aes128; +/// use bouncycastle_aes::AES_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::StreamCipherEncryptor; /// use bouncycastle_modes::{Ctr, Encrypting}; /// /// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); /// // An 11-byte nonce would give a 5-byte counter, past the 4-byte cap. -/// let _ = Ctr::::do_encrypt_init(&key); +/// let _ = Ctr::::do_encrypt_init(&key); /// ``` /// /// The permitted lengths all work: /// /// ``` -/// use bouncycastle_aes::Aes128; +/// use bouncycastle_aes::AES_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::StreamCipherEncryptor; /// use bouncycastle_modes::{Ctr, Encrypting}; /// /// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); -/// let _ = Ctr::::do_encrypt_init(&key).unwrap(); // 4-byte counter -/// let _ = Ctr::::do_encrypt_init(&key).unwrap(); // 1-byte counter +/// let _ = Ctr::::do_encrypt_init(&key).unwrap(); // 4-byte counter +/// let _ = Ctr::::do_encrypt_init(&key).unwrap(); // 1-byte counter /// ``` /// /// # State diff --git a/crypto/modes/src/lib.rs b/crypto/modes/src/lib.rs index 37e2cda7..2644bea3 100644 --- a/crypto/modes/src/lib.rs +++ b/crypto/modes/src/lib.rs @@ -1,6 +1,6 @@ //! Block cipher modes of operation (NIST SP 800-38A). //! -//! A mode turns a keyed block permutation -- `bouncycastle-aes`'s `Aes128` and friends, +//! A mode turns a keyed block permutation -- `bouncycastle-aes`'s `AES_128` and friends, //! or anything else implementing [`ElectronicCodeBook`] -- into something that can encrypt more than //! one block. This crate provides: //! @@ -44,24 +44,24 @@ //! without one, while the three stream modes take only the direction: //! //! ``` -//! use bouncycastle_aes::{Aes128, Aes192, Aes256}; +//! use bouncycastle_aes::{AES_128, AES_192, AES_256}; //! use bouncycastle_modes::{Cbc, Cfb, Cfb8, Ctr, Ecb}; //! -//! type Aes128Cbc = Cbc; -//! type Aes192Cbc = Cbc; -//! type Aes256Cbc = Cbc; +//! type Aes128Cbc = Cbc; +//! type Aes192Cbc = Cbc; +//! type Aes256Cbc = Cbc; //! -//! type Aes128Cfb = Cfb; -//! type Aes192Cfb = Cfb; -//! type Aes256Cfb = Cfb; +//! type Aes128Cfb = Cfb; +//! type Aes192Cfb = Cfb; +//! type Aes256Cfb = Cfb; //! -//! type Aes128Cfb8 = Cfb8; +//! type Aes128Cfb8 = Cfb8; //! //! // CTR takes one more parameter: the nonce length, which fixes the counter width at //! // `BLOCK_LEN - NONCE_LEN`. 12 bytes of nonce leaves the maximum 4-byte counter. -//! type Aes128Ctr = Ctr; +//! type Aes128Ctr = Ctr; //! -//! type Aes128Ecb = Ecb; +//! type Aes128Ecb = Ecb; //! ``` //! //! # Usage Examples @@ -74,12 +74,12 @@ //! [Security Considerations](#security-considerations)). //! //! ``` -//! use bouncycastle_aes::Aes128; +//! use bouncycastle_aes::AES_128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; //! use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; //! -//! type Aes128Cbc = Cbc; +//! type Aes128Cbc = Cbc; //! //! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); @@ -100,12 +100,12 @@ //! the concatenation: //! //! ``` -//! use bouncycastle_aes::Aes256; +//! use bouncycastle_aes::AES_256; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; //! use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; //! -//! type Aes256Cbc = Cbc; +//! type Aes256Cbc = Cbc; //! //! let key = KeyMaterial::<32>::from_bytes_as_type(&[0x07; 32], KeyType::SymmetricCipherKey) //! .expect("a 32-byte symmetric cipher key"); @@ -129,13 +129,13 @@ //! exactly as long as the plaintext: //! //! ``` -//! use bouncycastle_aes::Aes128; +//! use bouncycastle_aes::AES_128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; //! use bouncycastle_modes::{Cfb, Cfb8, Decrypting, Encrypting}; //! -//! type Aes128Cfb = Cfb; -//! type Aes128Cfb8 = Cfb8; +//! type Aes128Cfb = Cfb; +//! type Aes128Cfb8 = Cfb8; //! //! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); @@ -160,12 +160,12 @@ //! Streaming works at any byte boundary, and the chunking is not visible in the output: //! //! ``` -//! use bouncycastle_aes::Aes128; +//! use bouncycastle_aes::AES_128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; //! use bouncycastle_modes::{Cfb, Decrypting, Encrypting}; //! -//! type Aes128Cfb = Cfb; +//! type Aes128Cfb = Cfb; //! //! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); @@ -193,12 +193,12 @@ //! The codebook property that makes it unsuitable for data is visible in the ciphertext: //! //! ``` -//! use bouncycastle_aes::Aes128; +//! use bouncycastle_aes::AES_128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; //! use bouncycastle_modes::{Decrypting, Ecb, Encrypting}; //! -//! type Aes128Ecb = Ecb; +//! type Aes128Ecb = Ecb; //! //! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); @@ -215,12 +215,12 @@ //! Using the wrong direction does not compile: //! //! ```compile_fail -//! use bouncycastle_aes::Aes128; +//! use bouncycastle_aes::AES_128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::BlockCipherDecryptor; //! use bouncycastle_modes::{Cbc, Encrypting}; //! -//! type Aes128Cbc = Cbc; +//! type Aes128Cbc = Cbc; //! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); //! //! // `Encrypting` does not implement `BlockCipherDecryptor`. @@ -293,14 +293,14 @@ //! an error at `do_final` rather than something padded -- for formats defined on whole blocks. //! //! ``` -//! use bouncycastle_aes::Aes128; +//! use bouncycastle_aes::AES_128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; //! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; //! use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; //! use bouncycastle_padding::{PKCS7, PaddedDecryptor, PaddedEncryptor}; //! -//! type Enc = PaddedEncryptor, PKCS7, 16, 16, 16>; -//! type Dec = PaddedDecryptor, PKCS7, 16, 16, 16>; +//! type Enc = PaddedEncryptor, PKCS7, 16, 16, 16>; +//! type Dec = PaddedDecryptor, PKCS7, 16, 16, 16>; //! //! let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) //! .expect("a 16-byte symmetric cipher key"); diff --git a/crypto/modes/tests/acvp_cfb8_tests.rs b/crypto/modes/tests/acvp_cfb8_tests.rs index 9a77e882..d7724e3d 100644 --- a/crypto/modes/tests/acvp_cfb8_tests.rs +++ b/crypto/modes/tests/acvp_cfb8_tests.rs @@ -33,7 +33,7 @@ //! than in SP 800-38A, and implementing it from anything else would be guesswork. The test reports //! how many it skipped so the gap stays visible. -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -167,9 +167,9 @@ fn run_case_for_key_len( grouping: Grouping, ) -> Vec { match key_bytes.len() { - 16 => run_case::(key_bytes, iv, input, encrypt, grouping), - 24 => run_case::(key_bytes, iv, input, encrypt, grouping), - 32 => run_case::(key_bytes, iv, input, encrypt, grouping), + 16 => run_case::(key_bytes, iv, input, encrypt, grouping), + 24 => run_case::(key_bytes, iv, input, encrypt, grouping), + 32 => run_case::(key_bytes, iv, input, encrypt, grouping), other => panic!("ACVP AES vectors should only use 16, 24 or 32 byte keys, got {other}"), } } diff --git a/crypto/modes/tests/acvp_cfb_tests.rs b/crypto/modes/tests/acvp_cfb_tests.rs index 2c223be1..a2320a81 100644 --- a/crypto/modes/tests/acvp_cfb_tests.rs +++ b/crypto/modes/tests/acvp_cfb_tests.rs @@ -37,7 +37,7 @@ //! than in SP 800-38A, and implementing it from anything else would be guesswork. The test reports //! how many it skipped so the gap stays visible. -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -172,9 +172,9 @@ fn run_case_for_key_len( grouping: Grouping, ) -> Vec { match key_bytes.len() { - 16 => run_case::(key_bytes, iv, input, encrypt, grouping), - 24 => run_case::(key_bytes, iv, input, encrypt, grouping), - 32 => run_case::(key_bytes, iv, input, encrypt, grouping), + 16 => run_case::(key_bytes, iv, input, encrypt, grouping), + 24 => run_case::(key_bytes, iv, input, encrypt, grouping), + 32 => run_case::(key_bytes, iv, input, encrypt, grouping), other => panic!("ACVP AES vectors should only use 16, 24 or 32 byte keys, got {other}"), } } diff --git a/crypto/modes/tests/acvp_ctr_tests.rs b/crypto/modes/tests/acvp_ctr_tests.rs index 6f53bd68..2e51dcb6 100644 --- a/crypto/modes/tests/acvp_ctr_tests.rs +++ b/crypto/modes/tests/acvp_ctr_tests.rs @@ -36,7 +36,7 @@ //! `resultsArray` produced by a chained update rule defined in the ACVP AES specification rather //! than in SP 800-38A, and implementing it from anything else would be guesswork. -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -173,9 +173,9 @@ fn run_case_for_key_len( grouping: Grouping, ) -> Vec { match key_bytes.len() { - 16 => run_case::(key_bytes, nonce, input, encrypt, grouping), - 24 => run_case::(key_bytes, nonce, input, encrypt, grouping), - 32 => run_case::(key_bytes, nonce, input, encrypt, grouping), + 16 => run_case::(key_bytes, nonce, input, encrypt, grouping), + 24 => run_case::(key_bytes, nonce, input, encrypt, grouping), + 32 => run_case::(key_bytes, nonce, input, encrypt, grouping), other => panic!("ACVP AES vectors should only use 16, 24 or 32 byte keys, got {other}"), } } diff --git a/crypto/modes/tests/acvp_ecb_tests.rs b/crypto/modes/tests/acvp_ecb_tests.rs index d35b48ac..e529b0f7 100644 --- a/crypto/modes/tests/acvp_ecb_tests.rs +++ b/crypto/modes/tests/acvp_ecb_tests.rs @@ -17,7 +17,7 @@ //! declared direction. The MCT (Monte Carlo) groups carry a `resultsArray` defined by the ACVP AES //! specification rather than SP 800-38A and are skipped, with the count reported. -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -132,9 +132,9 @@ fn run_case_for_key_len( grouping: Grouping, ) -> Vec<[u8; BLOCK_LEN]> { match key_bytes.len() { - 16 => run_case::(key_bytes, input, encrypt, grouping), - 24 => run_case::(key_bytes, input, encrypt, grouping), - 32 => run_case::(key_bytes, input, encrypt, grouping), + 16 => run_case::(key_bytes, input, encrypt, grouping), + 24 => run_case::(key_bytes, input, encrypt, grouping), + 32 => run_case::(key_bytes, input, encrypt, grouping), other => panic!("ACVP AES vectors should only use 16, 24 or 32 byte keys, got {other}"), } } diff --git a/crypto/modes/tests/acvp_tests.rs b/crypto/modes/tests/acvp_tests.rs index 94cb3d40..463aa283 100644 --- a/crypto/modes/tests/acvp_tests.rs +++ b/crypto/modes/tests/acvp_tests.rs @@ -29,7 +29,7 @@ //! than in SP 800-38A, and implementing it from anything else would be guesswork. The test reports //! how many it skipped so the gap stays visible. -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -186,9 +186,9 @@ fn run_case_for_key_len( grouping: Grouping, ) -> Vec<[u8; BLOCK_LEN]> { match key_bytes.len() { - 16 => run_case::(key_bytes, iv, input, encrypt, grouping), - 24 => run_case::(key_bytes, iv, input, encrypt, grouping), - 32 => run_case::(key_bytes, iv, input, encrypt, grouping), + 16 => run_case::(key_bytes, iv, input, encrypt, grouping), + 24 => run_case::(key_bytes, iv, input, encrypt, grouping), + 32 => run_case::(key_bytes, iv, input, encrypt, grouping), other => panic!("ACVP AES vectors should only use 16, 24 or 32 byte keys, got {other}"), } } diff --git a/crypto/modes/tests/cbc_tests.rs b/crypto/modes/tests/cbc_tests.rs index e967f4a9..b62c002a 100644 --- a/crypto/modes/tests/cbc_tests.rs +++ b/crypto/modes/tests/cbc_tests.rs @@ -6,7 +6,7 @@ mod common; -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; use bouncycastle_core_test_framework::electronic_code_book::TestFrameworkElectronicCodeBook; @@ -370,20 +370,20 @@ fn a_key_of_the_wrong_type_is_rejected() { fn sizes_match_the_documented_memory_table() { use core::mem::size_of; - assert_eq!(size_of::>(), 176 + 16); - assert_eq!(size_of::>(), 208 + 16); - assert_eq!(size_of::>(), 240 + 16); + assert_eq!(size_of::>(), 176 + 16); + assert_eq!(size_of::>(), 208 + 16); + assert_eq!(size_of::>(), 240 + 16); // The direction marker is free, and does not change the layout. assert_eq!( - size_of::>(), - size_of::>() + size_of::>(), + size_of::>() ); assert_eq!(size_of::(), 0); assert_eq!(size_of::(), 0); // ...and the general rule the docs state. - assert_eq!(size_of::>(), size_of::() + 16); + assert_eq!(size_of::>(), size_of::() + 16); } /// The one-shots (`encrypt` / `decrypt` on a `[u8; LEN]`, in place) must produce exactly what the diff --git a/crypto/modes/tests/cfb8_tests.rs b/crypto/modes/tests/cfb8_tests.rs index b711d793..6e5c224c 100644 --- a/crypto/modes/tests/cfb8_tests.rs +++ b/crypto/modes/tests/cfb8_tests.rs @@ -13,7 +13,7 @@ mod common; -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ElectronicCodeBook, StreamCipherDecryptor, StreamCipherEncryptor}; use bouncycastle_core_test_framework::FixedSeedRNG; @@ -411,9 +411,9 @@ fn aes_chunking_matches_a_single_call() { } } - check::("AES-128"); - check::("AES-192"); - check::("AES-256"); + check::("AES-128"); + check::("AES-192"); + check::("AES-256"); } /// The pair path in `do_decrypt` must actually be taken. @@ -525,7 +525,7 @@ fn one_shots_agree_with_the_streaming_api() { /// block cipher's diffusion rather than of the mode, and the byte-local toy cannot show it. #[test] fn a_ciphertext_bit_error_damages_exactly_sixteen_following_bytes() { - type Aes128Cfb8 = Cfb8; + type Aes128Cfb8 = Cfb8; const LEN: usize = 48; let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) @@ -684,26 +684,26 @@ fn every_length_round_trips_without_padding() { fn sizes_match_the_documented_memory_table() { use core::mem::size_of; - assert_eq!(size_of::>(), 176 + 16); - assert_eq!(size_of::>(), 208 + 16); - assert_eq!(size_of::>(), 240 + 16); + assert_eq!(size_of::>(), 176 + 16); + assert_eq!(size_of::>(), 208 + 16); + assert_eq!(size_of::>(), 240 + 16); // The direction marker is free, and does not change the layout. assert_eq!( - size_of::>(), - size_of::>() + size_of::>(), + size_of::>() ); // ...and the general rule the docs state. - assert_eq!(size_of::>(), size_of::() + 16); + assert_eq!(size_of::>(), size_of::() + 16); // The docs say CFB8 is the same size as CBC, and one `usize` smaller than CFB. assert_eq!( - size_of::>(), - size_of::>() + size_of::>(), + size_of::>() ); assert_eq!( - size_of::>() + size_of::(), - size_of::>() + size_of::>() + size_of::(), + size_of::>() ); } diff --git a/crypto/modes/tests/cfb_tests.rs b/crypto/modes/tests/cfb_tests.rs index 7143563e..94734859 100644 --- a/crypto/modes/tests/cfb_tests.rs +++ b/crypto/modes/tests/cfb_tests.rs @@ -13,7 +13,7 @@ mod common; -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ BlockCipherEncryptor, ElectronicCodeBook, StreamCipherDecryptor, StreamCipherEncryptor, @@ -440,9 +440,9 @@ fn aes_chunking_matches_a_single_call() { } } - check::("AES-128"); - check::("AES-192"); - check::("AES-256"); + check::("AES-128"); + check::("AES-192"); + check::("AES-256"); } /// The pair path in `do_decrypt` must actually be taken, and only where a pair of whole blocks sits @@ -631,7 +631,7 @@ fn a_ciphertext_bit_error_flips_exactly_that_bit_of_its_own_block() { /// real bug and this is what catches it. #[test] fn an_iv_bit_error_randomises_only_the_first_block() { - type Aes128Cfb = Cfb; + type Aes128Cfb = Cfb; const LEN: usize = 16; let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey) @@ -764,25 +764,25 @@ fn every_length_round_trips_without_padding() { fn sizes_match_the_documented_memory_table() { use core::mem::size_of; - assert_eq!(size_of::>(), 176 + 16 + 8); - assert_eq!(size_of::>(), 208 + 16 + 8); - assert_eq!(size_of::>(), 240 + 16 + 8); + assert_eq!(size_of::>(), 176 + 16 + 8); + assert_eq!(size_of::>(), 208 + 16 + 8); + assert_eq!(size_of::>(), 240 + 16 + 8); // The direction marker is free, and does not change the layout. assert_eq!( - size_of::>(), - size_of::>() + size_of::>(), + size_of::>() ); // ...and the general rule the docs state. assert_eq!( - size_of::>(), - size_of::() + 16 + size_of::() + size_of::>(), + size_of::() + 16 + size_of::() ); // The docs say CFB is one `usize` bigger than CBC. assert_eq!( - size_of::>(), - size_of::>() + size_of::() + size_of::>(), + size_of::>() + size_of::() ); } diff --git a/crypto/modes/tests/ctr_bc_java_tests.rs b/crypto/modes/tests/ctr_bc_java_tests.rs index c47ec786..b02e37c6 100644 --- a/crypto/modes/tests/ctr_bc_java_tests.rs +++ b/crypto/modes/tests/ctr_bc_java_tests.rs @@ -36,7 +36,7 @@ //! three key lengths -- and it is exact. Those cases are covered there and by the ACVP suite, so //! what is pinned here is specifically the part neither of them reaches: the narrow counters. -use bouncycastle_aes::Aes128; +use bouncycastle_aes::AES_128; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::StreamCipherEncryptor; use bouncycastle_core_test_framework::FixedSeedRNG; @@ -55,7 +55,7 @@ fn key() -> KeyMaterial<16> { fn keystream(nonce_hex: &str, blocks: usize) -> Vec { let nonce: [u8; NONCE_LEN] = hex::decode(nonce_hex).expect("valid hex").try_into().expect("nonce length"); - let (mut enc, got) = Ctr::::do_encrypt_init_rng( + let (mut enc, got) = Ctr::::do_encrypt_init_rng( &key(), &mut FixedSeedRNG::::new(nonce), ) @@ -148,7 +148,7 @@ fn three_byte_counter_matches_bc_java() { fn the_counter_limit_falls_where_bc_java_throws() { let nonce: [u8; 15] = hex::decode("5a5b5c5d5e5f606162636465666768").unwrap().try_into().unwrap(); - let (mut enc, _) = Ctr::::do_encrypt_init_rng( + let (mut enc, _) = Ctr::::do_encrypt_init_rng( &key(), &mut FixedSeedRNG::<15>::new(nonce), ) diff --git a/crypto/modes/tests/ctr_tests.rs b/crypto/modes/tests/ctr_tests.rs index 716a8f6d..3b386ffe 100644 --- a/crypto/modes/tests/ctr_tests.rs +++ b/crypto/modes/tests/ctr_tests.rs @@ -23,7 +23,7 @@ mod common; -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ElectronicCodeBook, StreamCipherDecryptor, StreamCipherEncryptor}; @@ -550,9 +550,9 @@ fn aes_chunking_matches_a_single_call() { } } - check::("AES-128"); - check::("AES-192"); - check::("AES-256"); + check::("AES-128"); + check::("AES-192"); + check::("AES-256"); } /// The pair path must be taken, **in both directions** -- unlike CBC and CFB, CTR encryption @@ -720,19 +720,19 @@ fn sizes_match_the_documented_memory_table() { // permutation + nonce + counter (u64) + keystream block + the used offset, rounded up to the // u64's alignment. For a 12-byte nonce on AES that is 176/208/240 + 12 + 8 + 16 + 8 = 220/252/284, // padded to 224/256/288. - assert_eq!(size_of::>(), 224); - assert_eq!(size_of::>(), 256); - assert_eq!(size_of::>(), 288); + assert_eq!(size_of::>(), 224); + assert_eq!(size_of::>(), 256); + assert_eq!(size_of::>(), 288); // The direction marker is free, and the nonce length does not change the layout: the counter // block is always a whole block. assert_eq!( - size_of::>(), - size_of::>() + size_of::>(), + size_of::>() ); // A longer nonce fits in the same padding, so the total is unchanged. assert_eq!( - size_of::>(), - size_of::>() + size_of::>(), + size_of::>() ); } diff --git a/crypto/modes/tests/ctr_vector_tests.rs b/crypto/modes/tests/ctr_vector_tests.rs index 24fa9695..ef85dd34 100644 --- a/crypto/modes/tests/ctr_vector_tests.rs +++ b/crypto/modes/tests/ctr_vector_tests.rs @@ -25,7 +25,7 @@ //! the counter starting at zero, so the two line up exactly when the IV's low four bytes are zero, //! which is why the IV above ends in `00000000`. See the [`Ctr`] module docs. -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ElectronicCodeBook, StreamCipherDecryptor, StreamCipherEncryptor}; use bouncycastle_core_test_framework::FixedSeedRNG; @@ -142,17 +142,17 @@ where #[test] fn aes128_ctr_matches_openssl() { - check::("AES-128", KEY_128, CT_128); + check::("AES-128", KEY_128, CT_128); } #[test] fn aes192_ctr_matches_openssl() { - check::("AES-192", KEY_192, CT_192); + check::("AES-192", KEY_192, CT_192); } #[test] fn aes256_ctr_matches_openssl() { - check::("AES-256", KEY_256, CT_256); + check::("AES-256", KEY_256, CT_256); } /// The vectors must actually depend on the counter advancing: the second block of ciphertext must diff --git a/crypto/modes/tests/ecb_tests.rs b/crypto/modes/tests/ecb_tests.rs index b2c2e46c..73790bea 100644 --- a/crypto/modes/tests/ecb_tests.rs +++ b/crypto/modes/tests/ecb_tests.rs @@ -12,7 +12,7 @@ mod common; -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook, SymmetricCipherDecryptor, @@ -348,7 +348,7 @@ fn a_ciphertext_bit_error_affects_only_its_own_block() { /// must randomise `P2` (more than one bit differs) and leave `P1` and `P3` untouched. #[test] fn with_aes_a_ciphertext_bit_error_randomises_its_block() { - type Aes128Ecb = Ecb; + type Aes128Ecb = Ecb; let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); let plaintext = [[0x00u8; 16], [0x11u8; 16], [0x22u8; 16]]; @@ -413,17 +413,17 @@ fn the_padding_layer_round_trips_every_length() { #[test] fn sizes_match_the_documented_memory_table() { use core::mem::size_of; - assert_eq!(size_of::>(), 176); - assert_eq!(size_of::>(), 208); - assert_eq!(size_of::>(), 240); + assert_eq!(size_of::>(), 176); + assert_eq!(size_of::>(), 208); + assert_eq!(size_of::>(), 240); assert_eq!( - size_of::>(), - size_of::>() + size_of::>(), + size_of::>() ); - assert_eq!(size_of::>(), size_of::()); + assert_eq!(size_of::>(), size_of::()); // One block smaller than CBC, which stores a chaining value. assert_eq!( - size_of::>() + 16, - size_of::>() + size_of::>() + 16, + size_of::>() ); } diff --git a/crypto/modes/tests/sp800_38a_cfb8_tests.rs b/crypto/modes/tests/sp800_38a_cfb8_tests.rs index 23c7b245..59b10187 100644 --- a/crypto/modes/tests/sp800_38a_cfb8_tests.rs +++ b/crypto/modes/tests/sp800_38a_cfb8_tests.rs @@ -30,7 +30,7 @@ //! the vector's IV, and the test asserts the returned init data really is that IV before comparing //! any ciphertext. Decryption takes the IV directly, as init data. -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ElectronicCodeBook, StreamCipherDecryptor, StreamCipherEncryptor}; use bouncycastle_core_test_framework::FixedSeedRNG; @@ -183,32 +183,32 @@ where #[test] fn f_3_7_cfb8_aes128_encrypt() { - check_encrypt::("F.3.7", KEY_128, CIPHERTEXT_128); + check_encrypt::("F.3.7", KEY_128, CIPHERTEXT_128); } #[test] fn f_3_8_cfb8_aes128_decrypt() { - check_decrypt::("F.3.8", KEY_128, CIPHERTEXT_128); + check_decrypt::("F.3.8", KEY_128, CIPHERTEXT_128); } #[test] fn f_3_9_cfb8_aes192_encrypt() { - check_encrypt::("F.3.9", KEY_192, CIPHERTEXT_192); + check_encrypt::("F.3.9", KEY_192, CIPHERTEXT_192); } #[test] fn f_3_10_cfb8_aes192_decrypt() { - check_decrypt::("F.3.10", KEY_192, CIPHERTEXT_192); + check_decrypt::("F.3.10", KEY_192, CIPHERTEXT_192); } #[test] fn f_3_11_cfb8_aes256_encrypt() { - check_encrypt::("F.3.11", KEY_256, CIPHERTEXT_256); + check_encrypt::("F.3.11", KEY_256, CIPHERTEXT_256); } #[test] fn f_3_12_cfb8_aes256_decrypt() { - check_decrypt::("F.3.12", KEY_256, CIPHERTEXT_256); + check_decrypt::("F.3.12", KEY_256, CIPHERTEXT_256); } /// The spec's tabulated **Input Blocks** are the shift register and its **Output Blocks** are @@ -225,7 +225,7 @@ fn f_3_12_cfb8_aes256_decrypt() { #[test] fn the_tabulated_blocks_are_the_shift_register() { let key = key_material::<16>(KEY_128); - let perm = >::new(&key).expect("a valid key"); + let perm = >::new(&key).expect("a valid key"); let plaintext = bytes(PLAINTEXT); let ciphertext = bytes(CIPHERTEXT_128); diff --git a/crypto/modes/tests/sp800_38a_cfb_tests.rs b/crypto/modes/tests/sp800_38a_cfb_tests.rs index 7243eac5..892e0448 100644 --- a/crypto/modes/tests/sp800_38a_cfb_tests.rs +++ b/crypto/modes/tests/sp800_38a_cfb_tests.rs @@ -32,7 +32,7 @@ //! the vector's IV, and the test asserts the returned init data really is that IV before comparing //! any ciphertext. Decryption takes the IV directly, as init data. -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ElectronicCodeBook, StreamCipherDecryptor, StreamCipherEncryptor}; use bouncycastle_core_test_framework::FixedSeedRNG; @@ -226,32 +226,32 @@ where #[test] fn f_3_13_cfb128_aes128_encrypt() { - check_encrypt::("F.3.13", KEY_128, &CIPHERTEXTS_128); + check_encrypt::("F.3.13", KEY_128, &CIPHERTEXTS_128); } #[test] fn f_3_14_cfb128_aes128_decrypt() { - check_decrypt::("F.3.14", KEY_128, &CIPHERTEXTS_128); + check_decrypt::("F.3.14", KEY_128, &CIPHERTEXTS_128); } #[test] fn f_3_15_cfb128_aes192_encrypt() { - check_encrypt::("F.3.15", KEY_192, &CIPHERTEXTS_192); + check_encrypt::("F.3.15", KEY_192, &CIPHERTEXTS_192); } #[test] fn f_3_16_cfb128_aes192_decrypt() { - check_decrypt::("F.3.16", KEY_192, &CIPHERTEXTS_192); + check_decrypt::("F.3.16", KEY_192, &CIPHERTEXTS_192); } #[test] fn f_3_17_cfb128_aes256_encrypt() { - check_encrypt::("F.3.17", KEY_256, &CIPHERTEXTS_256); + check_encrypt::("F.3.17", KEY_256, &CIPHERTEXTS_256); } #[test] fn f_3_18_cfb128_aes256_decrypt() { - check_decrypt::("F.3.18", KEY_256, &CIPHERTEXTS_256); + check_decrypt::("F.3.18", KEY_256, &CIPHERTEXTS_256); } /// The one-shot API must agree with the vectors too, on the decrypt side where the IV is an input. @@ -263,17 +263,17 @@ fn the_one_shot_api_matches_the_vectors() { let pt = flat(&PLAINTEXTS); let mut data = flat(&CIPHERTEXTS_128); - Cfb::::decrypt(&key_material::<16>(KEY_128), &iv, &mut data) + Cfb::::decrypt(&key_material::<16>(KEY_128), &iv, &mut data) .unwrap(); assert_eq!(data, pt); let mut data = flat(&CIPHERTEXTS_192); - Cfb::::decrypt(&key_material::<24>(KEY_192), &iv, &mut data) + Cfb::::decrypt(&key_material::<24>(KEY_192), &iv, &mut data) .unwrap(); assert_eq!(data, pt); let mut data = flat(&CIPHERTEXTS_256); - Cfb::::decrypt(&key_material::<32>(KEY_256), &iv, &mut data) + Cfb::::decrypt(&key_material::<32>(KEY_256), &iv, &mut data) .unwrap(); assert_eq!(data, pt); } @@ -329,9 +329,9 @@ fn check_output_blocks( #[test] fn the_tabulated_output_blocks_are_the_keystream() { - check_output_blocks::("F.3.13", KEY_128, &CIPHERTEXTS_128, &OUTPUT_BLOCKS_128); - check_output_blocks::("F.3.15", KEY_192, &CIPHERTEXTS_192, &OUTPUT_BLOCKS_192); - check_output_blocks::("F.3.17", KEY_256, &CIPHERTEXTS_256, &OUTPUT_BLOCKS_256); + check_output_blocks::("F.3.13", KEY_128, &CIPHERTEXTS_128, &OUTPUT_BLOCKS_128); + check_output_blocks::("F.3.15", KEY_192, &CIPHERTEXTS_192, &OUTPUT_BLOCKS_192); + check_output_blocks::("F.3.17", KEY_256, &CIPHERTEXTS_256, &OUTPUT_BLOCKS_256); } /// CFB128 and OFB must agree on the **first** block and on nothing after it. @@ -359,7 +359,7 @@ fn cfb128_agrees_with_ofb_on_the_first_block_only() { let key = key_material::<16>(KEY_128); let iv = block(IV); - let (mut enc, got_iv) = Cfb::::do_encrypt_init_rng( + let (mut enc, got_iv) = Cfb::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::<16>::new(iv), ) diff --git a/crypto/modes/tests/sp800_38a_ecb_tests.rs b/crypto/modes/tests/sp800_38a_ecb_tests.rs index ea61509a..4c90436b 100644 --- a/crypto/modes/tests/sp800_38a_ecb_tests.rs +++ b/crypto/modes/tests/sp800_38a_ecb_tests.rs @@ -17,7 +17,7 @@ //! checks that, which ties the mode to [`ElectronicCodeBook`] and confirms the transcription: a //! typo in either column would break the equality. -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook}; use bouncycastle_hex as hex; @@ -169,32 +169,32 @@ where #[test] fn f_1_1_ecb_aes128_encrypt() { - check_encrypt::("F.1.1", KEY_128, &CIPHERTEXTS_128); + check_encrypt::("F.1.1", KEY_128, &CIPHERTEXTS_128); } #[test] fn f_1_2_ecb_aes128_decrypt() { - check_decrypt::("F.1.2", KEY_128, &CIPHERTEXTS_128); + check_decrypt::("F.1.2", KEY_128, &CIPHERTEXTS_128); } #[test] fn f_1_3_ecb_aes192_encrypt() { - check_encrypt::("F.1.3", KEY_192, &CIPHERTEXTS_192); + check_encrypt::("F.1.3", KEY_192, &CIPHERTEXTS_192); } #[test] fn f_1_4_ecb_aes192_decrypt() { - check_decrypt::("F.1.4", KEY_192, &CIPHERTEXTS_192); + check_decrypt::("F.1.4", KEY_192, &CIPHERTEXTS_192); } #[test] fn f_1_5_ecb_aes256_encrypt() { - check_encrypt::("F.1.5", KEY_256, &CIPHERTEXTS_256); + check_encrypt::("F.1.5", KEY_256, &CIPHERTEXTS_256); } #[test] fn f_1_6_ecb_aes256_decrypt() { - check_decrypt::("F.1.6", KEY_256, &CIPHERTEXTS_256); + check_decrypt::("F.1.6", KEY_256, &CIPHERTEXTS_256); } /// Sec 6.1: `Cj = CIPH_K(Pj)`. Every tabulated ciphertext block is the raw permutation of the @@ -213,7 +213,7 @@ where #[test] fn each_block_is_the_raw_permutation() { - check_raw::("F.1.1", KEY_128, &CIPHERTEXTS_128); - check_raw::("F.1.3", KEY_192, &CIPHERTEXTS_192); - check_raw::("F.1.5", KEY_256, &CIPHERTEXTS_256); + check_raw::("F.1.1", KEY_128, &CIPHERTEXTS_128); + check_raw::("F.1.3", KEY_192, &CIPHERTEXTS_192); + check_raw::("F.1.5", KEY_256, &CIPHERTEXTS_256); } diff --git a/crypto/modes/tests/sp800_38a_tests.rs b/crypto/modes/tests/sp800_38a_tests.rs index dcfc45c0..810d7ed3 100644 --- a/crypto/modes/tests/sp800_38a_tests.rs +++ b/crypto/modes/tests/sp800_38a_tests.rs @@ -15,7 +15,7 @@ //! the vector's IV, and the test asserts the returned init data really is that IV before comparing //! any ciphertext. Decryption takes the IV directly, as init data. -use bouncycastle_aes::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook}; use bouncycastle_core_test_framework::FixedSeedRNG; @@ -179,32 +179,32 @@ where #[test] fn f_2_1_cbc_aes128_encrypt() { - check_encrypt::("F.2.1", KEY_128, &CIPHERTEXTS_128); + check_encrypt::("F.2.1", KEY_128, &CIPHERTEXTS_128); } #[test] fn f_2_2_cbc_aes128_decrypt() { - check_decrypt::("F.2.2", KEY_128, &CIPHERTEXTS_128); + check_decrypt::("F.2.2", KEY_128, &CIPHERTEXTS_128); } #[test] fn f_2_3_cbc_aes192_encrypt() { - check_encrypt::("F.2.3", KEY_192, &CIPHERTEXTS_192); + check_encrypt::("F.2.3", KEY_192, &CIPHERTEXTS_192); } #[test] fn f_2_4_cbc_aes192_decrypt() { - check_decrypt::("F.2.4", KEY_192, &CIPHERTEXTS_192); + check_decrypt::("F.2.4", KEY_192, &CIPHERTEXTS_192); } #[test] fn f_2_5_cbc_aes256_encrypt() { - check_encrypt::("F.2.5", KEY_256, &CIPHERTEXTS_256); + check_encrypt::("F.2.5", KEY_256, &CIPHERTEXTS_256); } #[test] fn f_2_6_cbc_aes256_decrypt() { - check_decrypt::("F.2.6", KEY_256, &CIPHERTEXTS_256); + check_decrypt::("F.2.6", KEY_256, &CIPHERTEXTS_256); } /// The one-shot API must agree with the vectors too, on the decrypt side where the IV is an input. @@ -216,17 +216,17 @@ fn the_one_shot_api_matches_the_vectors() { let pt = flat(&PLAINTEXTS); let mut data = flat(&CIPHERTEXTS_128); - Cbc::::decrypt(&key_material::<16>(KEY_128), &iv, &mut data) + Cbc::::decrypt(&key_material::<16>(KEY_128), &iv, &mut data) .unwrap(); assert_eq!(data, pt); let mut data = flat(&CIPHERTEXTS_192); - Cbc::::decrypt(&key_material::<24>(KEY_192), &iv, &mut data) + Cbc::::decrypt(&key_material::<24>(KEY_192), &iv, &mut data) .unwrap(); assert_eq!(data, pt); let mut data = flat(&CIPHERTEXTS_256); - Cbc::::decrypt(&key_material::<32>(KEY_256), &iv, &mut data) + Cbc::::decrypt(&key_material::<32>(KEY_256), &iv, &mut data) .unwrap(); assert_eq!(data, pt); } @@ -243,14 +243,14 @@ fn cbc_differs_from_ecb_by_the_iv() { // The raw permutation on P1 alone is the ECB answer from F.1.1. let mut ecb = block(PLAINTEXTS[0]); - >::encrypt_block( - &>::new(&key).unwrap(), + >::encrypt_block( + &>::new(&key).unwrap(), &mut ecb, ); assert_eq!(ecb, block("3ad77bb40d7a3660a89ecaf32466ef97"), "F.1.1 block #1"); // CBC's C1 = CIPH_K(P1 XOR IV) is the F.2.1 answer, and differs. - let (mut enc, _) = Cbc::::do_encrypt_init_rng( + let (mut enc, _) = Cbc::::do_encrypt_init_rng( &key, &mut FixedSeedRNG::<16>::new(iv), ) diff --git a/crypto/modes/tests/symmetric_cipher_api_tests.rs b/crypto/modes/tests/symmetric_cipher_api_tests.rs index 575d841b..fdef4cb1 100644 --- a/crypto/modes/tests/symmetric_cipher_api_tests.rs +++ b/crypto/modes/tests/symmetric_cipher_api_tests.rs @@ -24,7 +24,7 @@ mod common; -use bouncycastle_aes::Aes128; +use bouncycastle_aes::AES_128; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, @@ -252,12 +252,12 @@ fn the_one_shots_round_trip_with_real_aes() { // CFB128 let (iv, ct) = - as SymmetricCipherEncryptor<16, 16, 0>>::encrypt( + as SymmetricCipherEncryptor<16, 16, 0>>::encrypt( &key, message, ) .unwrap(); assert_eq!(ct.len(), message.len(), "a stream cipher does not change the length"); - let back = as SymmetricCipherDecryptor<16, 16, 0>>::decrypt( + let back = as SymmetricCipherDecryptor<16, 16, 0>>::decrypt( &key, &iv, &ct, ) .unwrap(); @@ -265,11 +265,11 @@ fn the_one_shots_round_trip_with_real_aes() { // CFB8 let (iv, ct) = - as SymmetricCipherEncryptor<16, 16, 0>>::encrypt( + as SymmetricCipherEncryptor<16, 16, 0>>::encrypt( &key, message, ) .unwrap(); - let back = as SymmetricCipherDecryptor<16, 16, 0>>::decrypt( + let back = as SymmetricCipherDecryptor<16, 16, 0>>::decrypt( &key, &iv, &ct, ) .unwrap(); @@ -277,13 +277,13 @@ fn the_one_shots_round_trip_with_real_aes() { // CTR let (nonce, ct) = - as SymmetricCipherEncryptor<16, 12, 0>>::encrypt( + as SymmetricCipherEncryptor<16, 12, 0>>::encrypt( &key, message, ) .unwrap(); assert_eq!(nonce.len(), 12, "CTR's init data is its 12-byte nonce"); let back = - as SymmetricCipherDecryptor<16, 12, 0>>::decrypt( + as SymmetricCipherDecryptor<16, 12, 0>>::decrypt( &key, &nonce, &ct, ) .unwrap(); diff --git a/mem_usage_benches/bench_aes_mem_usage.rs b/mem_usage_benches/bench_aes_mem_usage.rs index fab4dfdb..59df3bd0 100644 --- a/mem_usage_benches/bench_aes_mem_usage.rs +++ b/mem_usage_benches/bench_aes_mem_usage.rs @@ -31,7 +31,7 @@ #![allow(dead_code)] #![allow(unused_imports)] -use bouncycastle::aes::{Aes128, Aes192, Aes256}; +use bouncycastle::aes::{AES_128, AES_192, AES_256}; use bouncycastle::core::key_material::{KeyMaterial, KeyType}; use bouncycastle::core::traits::ElectronicCodeBook; @@ -48,9 +48,9 @@ fn print_struct_sizes() { // FIPS 197 Sec 5.2: the schedule is 4 * (Nr + 1) words, so 176 / 208 / 240 bytes. The // bit-sliced form is stored compressed, so bit-slicing adds nothing to these. - println!("size_of: {}", size_of::()); - println!("size_of: {}", size_of::()); - println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); } fn key() -> KeyMaterial { @@ -63,57 +63,57 @@ fn key() -> KeyMaterial { } fn bench_aes128_key_expansion() { - eprintln!("Aes128::new (key expansion)"); + eprintln!("AES_128::new (key expansion)"); - let aes = Aes128::new(&key::<16>()).unwrap(); + let aes = AES_128::new(&key::<16>()).unwrap(); print!("{aes:?}"); } fn bench_aes192_key_expansion() { - eprintln!("Aes192::new (key expansion)"); + eprintln!("AES_192::new (key expansion)"); - let aes = Aes192::new(&key::<24>()).unwrap(); + let aes = AES_192::new(&key::<24>()).unwrap(); print!("{aes:?}"); } fn bench_aes256_key_expansion() { - eprintln!("Aes256::new (key expansion)"); + eprintln!("AES_256::new (key expansion)"); - let aes = Aes256::new(&key::<32>()).unwrap(); + let aes = AES_256::new(&key::<32>()).unwrap(); print!("{aes:?}"); } fn bench_aes128_encrypt_block() { - eprintln!("Aes128::encrypt_block"); + eprintln!("AES_128::encrypt_block"); - let aes = Aes128::new(&key::<16>()).unwrap(); + let aes = AES_128::new(&key::<16>()).unwrap(); let mut block = [0x11u8; 16]; aes.encrypt_block(&mut block); print!("{block:x?}"); } fn bench_aes256_encrypt_block() { - eprintln!("Aes256::encrypt_block"); + eprintln!("AES_256::encrypt_block"); - let aes = Aes256::new(&key::<32>()).unwrap(); + let aes = AES_256::new(&key::<32>()).unwrap(); let mut block = [0x11u8; 16]; aes.encrypt_block(&mut block); print!("{block:x?}"); } fn bench_aes256_decrypt_block() { - eprintln!("Aes256::decrypt_block"); + eprintln!("AES_256::decrypt_block"); - let aes = Aes256::new(&key::<32>()).unwrap(); + let aes = AES_256::new(&key::<32>()).unwrap(); let mut block = [0x11u8; 16]; aes.decrypt_block(&mut block); print!("{block:x?}"); } fn bench_aes256_encrypt_blocks2() { - eprintln!("Aes256::encrypt_blocks2"); + eprintln!("AES_256::encrypt_blocks2"); - let aes = Aes256::new(&key::<32>()).unwrap(); + let aes = AES_256::new(&key::<32>()).unwrap(); let mut blocks = [[0x11u8; 16], [0x22u8; 16]]; aes.encrypt_blocks2(&mut blocks); print!("{blocks:x?}"); From d98f7039c16c4ebefb08346a70e7247180b13a63 Mon Sep 17 00:00:00 2001 From: David Hook Date: Wed, 9 Sep 2026 13:13:45 +1000 Subject: [PATCH 08/14] core: ElectronicCodeBook's pair methods are encrypt_2blocks / decrypt_2blocks (were *_blocks2), the reading Mike Ounsworth gave them in 736b0ac; modes, aes, the framework suite, benches and notes follow --- .fred.swp | Bin 0 -> 12288 bytes alpha_0.1.3_release_notes.md | 16 ++++---- crypto/aes/benches/aes_benches.rs | 22 +++++------ crypto/aes/src/aes.rs | 36 +++++++++--------- crypto/aes/src/lib.rs | 6 +-- crypto/aes/summary.md | 8 ++-- crypto/aes/tests/acvp_tests.rs | 10 ++--- .../aes/tests/electronic_code_book_tests.rs | 4 +- crypto/aes/tests/fips197_tests.rs | 4 +- crypto/aes/tests/sp800_38a_tests.rs | 8 ++-- .../src/electronic_code_book.rs | 20 +++++----- crypto/core-test-framework/summary.md | 6 +-- crypto/core/src/traits.rs | 12 +++--- crypto/modes/benches/modes_benches.rs | 22 +++++------ crypto/modes/src/cbc.rs | 8 ++-- crypto/modes/src/cfb.rs | 8 ++-- crypto/modes/src/cfb8.rs | 4 +- crypto/modes/src/ctr.rs | 4 +- crypto/modes/src/ecb.rs | 6 +-- crypto/modes/tests/acvp_cfb8_tests.rs | 2 +- crypto/modes/tests/acvp_cfb_tests.rs | 2 +- crypto/modes/tests/acvp_tests.rs | 2 +- crypto/modes/tests/cbc_tests.rs | 4 +- crypto/modes/tests/cfb8_tests.rs | 8 ++-- crypto/modes/tests/cfb_tests.rs | 8 ++-- crypto/modes/tests/common/mod.rs | 14 +++---- crypto/modes/tests/ctr_tests.rs | 2 +- crypto/modes/tests/ecb_tests.rs | 6 +-- mem_usage_benches/bench_aes_mem_usage.rs | 8 ++-- 29 files changed, 130 insertions(+), 130 deletions(-) create mode 100644 .fred.swp diff --git a/.fred.swp b/.fred.swp new file mode 100644 index 0000000000000000000000000000000000000000..b402b0be5bd1d16c53c61e17f4181945f6c20339 GIT binary patch literal 12288 zcmeI&y-ve05C`zII|9KATw$e23lk&aL#hNDW%tK5u}I>`=OVGd6Y@?tfsPc^t! zDG<9+_K~(e{@MQMm$;u_hh0Me0uX=z1Rwwb2tWV=5P$##AkYh_bl`q}m}NRW{rUgq z|9{9q1OW&@00Izz00bZa0SG_<0uX?}KLzNiVzS;)46bQhTaq%ti%{)!9^{-9%Mi7T zQai&#BBo-yuKR>kYbjl^r-mCJ-biz6s+<;)Lh5*B83v_eGc`U0md>{})i4DWoo`jm XsX|4%dAMHQ-sO!YB`-oNAM)%ASFlo? literal 0 HcmV?d00001 diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index 3af60235..ccdd6df8 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -24,7 +24,7 @@ permutation (NIST FIPS 197), re-exported from the umbrella crate. * **Both directions from one value.** Decryption follows FIPS 197 Algorithm 3 (the straight inverse cipher) rather than the equivalent inverse cipher of Sec 5.3.5, so it uses the unmodified key schedule -- one stored schedule encrypts and decrypts, with no second copy and no transformation at construction time. -* **Two-block entry points.** The bit-sliced state holds two blocks, so `encrypt_blocks2` / `decrypt_blocks2` are +* **Two-block entry points.** The bit-sliced state holds two blocks, so `encrypt_2blocks` / `decrypt_2blocks` are the natural unit of work and roughly double single-block throughput. `encrypt_block` / `decrypt_block` are provided but do twice the necessary work; modes whose blocks are independent (CTR, and CBC/CFB decryption) should prefer the pair form. @@ -74,7 +74,7 @@ only OFB outstanding. Re-exported from the umbrella crate. `P1 XOR P1'` outright rather than merely whether the blocks were equal. * **Parallel decryption.** Sec 6.2 notes CBC decryption's inverse cipher calls can run in parallel, so `do_decrypt_blocks` walks the ciphertext in eights through - `ElectronicCodeBook::decrypt_blocks8`, then pairs through `decrypt_blocks2`, then a one-block + `ElectronicCodeBook::decrypt_blocks8`, then pairs through `decrypt_2blocks`, then a one-block remainder. A toy permutation that rotates its eight results proves the eight path is taken, and only for full eights. Measured against an otherwise identical permutation that does not override the pair methods, this is **1.83x** the @@ -91,7 +91,7 @@ only OFB outstanding. Re-exported from the umbrella crate. for a ciphertext bit error (affects exactly two blocks). * Also verified against the **2150 NIST ACVP `ACVP-AES-CBC` AFT cases** from `bc-test-data` (all three key lengths, both directions, 60 of them spanning 2-10 blocks). Each case is run twice -- - block by block, and in pairs with a one-block remainder -- so the `decrypt_blocks2` path is + block by block, and in pairs with a one-block remainder -- so the `decrypt_2blocks` path is exercised against real vectors, not only against the toy permutation. Unlike the ECB response file, the CBC one carries only the answer against a `tcId`, so the request and response files are joined; the 6 MCT groups are skipped and the count reported. These vectors were already in @@ -120,10 +120,10 @@ CFB128 (`Cfb`), SP 800-38A Sec 6.3 with `s = b`: with no copy and no second buffer. That costs one `usize` over `Cbc` (200/232/264 B for AES-128/192/256) to record how much of the current segment has been used. * **Decryption uses the forward cipher function.** Sec 6.3 applies `CIPH_K` in both directions, so - `Cfb<_, Decrypting, _, _>` never calls `decrypt_block` or `decrypt_blocks2`. This is pinned by a + `Cfb<_, Decrypting, _, _>` never calls `decrypt_block` or `decrypt_2blocks`. This is pinned by a test permutation whose inverse methods panic, run over both the pair and single-block paths -- so the claim is enforced rather than merely documented. -* **Parallel decryption**, via `encrypt_blocks8` / `encrypt_blocks2` (eights, then pairs, then a single block, like CBC): Sec 6.3 notes CFB decryption's forward cipher +* **Parallel decryption**, via `encrypt_blocks8` / `encrypt_2blocks` (eights, then pairs, then a single block, like CBC): Sec 6.3 notes CFB decryption's forward cipher calls "can be performed in parallel if the input blocks are first constructed (in series) from the IV and the ciphertext", and with `s = b` those input blocks simply *are* the IV followed by the ciphertext. Re-measured after the stream-cipher rewrite: against an otherwise identical @@ -247,7 +247,7 @@ CTR (`Ctr`), SP 800-38A Sec 6.5: * **Both directions are parallel**, the only mode here of which that is true. Sec 6.5: "In both CTR encryption and CTR decryption, the forward cipher functions can be performed in parallel." Counter blocks depend on nothing but the nonce and the index, so encryption batches through - `encrypt_blocks8` / `encrypt_blocks2` exactly as decryption does, and encryption and decryption are + `encrypt_blocks8` / `encrypt_2blocks` exactly as decryption does, and encryption and decryption are the same operation. Only the forward cipher function is ever used, as in the CFB modes. * The keystream block is the one buffer in this crate wrapped in `Secret`: a call may end part-way through a block and the remainder is kept for the next one, and unlike a chaining value that @@ -366,7 +366,7 @@ ECB (`Ecb`), SP 800-38A Sec 6.1: `core`: new `ElectronicCodeBook` trait (`crypto/core/src/traits.rs`), the raw keyed permutation -- `CIPH_K` / `CIPH^-1_K` of SP 800-38A Sec 5.1 -- that a mode is built on. -`new`, `encrypt_block`, `decrypt_block`, plus provided `encrypt_blocks2` / `decrypt_blocks2` that +`new`, `encrypt_block`, `decrypt_block`, plus provided `encrypt_2blocks` / `decrypt_2blocks` that default to two single-block calls and `encrypt_blocks8` / `decrypt_blocks8` that default to four pair calls, all of which bit-sliced implementations override (AES the pair form, SM4 both). The block methods are infallible; only `new` can fail, and only on the key. `bouncycastle-aes` implements @@ -609,7 +609,7 @@ Block cipher traits (PR #96): blocks rather than a `[[u8; BLOCK_LEN]; N]` array (it did at first): every whole number of blocks is valid, so there is no length invariant for a const parameter to carry, and batching -- singly, in pairs, in eights -- is the mode's decision. `do_{en,de}crypt` therefore hands the whole buffer to the hook in one call, and CBC - decryption chunks it into pairs for `decrypt_blocks2` itself. The data methods keep a + decryption chunks it into pairs for `decrypt_2blocks` itself. The data methods keep a `Result` only for modes with a per-initialization data limit (counter-based modes); CBC never fails them. Testing: diff --git a/crypto/aes/benches/aes_benches.rs b/crypto/aes/benches/aes_benches.rs index 9b010a54..15f42257 100644 --- a/crypto/aes/benches/aes_benches.rs +++ b/crypto/aes/benches/aes_benches.rs @@ -1,6 +1,6 @@ //! Criterion benchmarks for the bit-sliced AES engine. //! -//! The comparison that matters here is `encrypt_block` against `encrypt_blocks2` over the same +//! The comparison that matters here is `encrypt_block` against `encrypt_2blocks` over the same //! number of bytes. The bit-sliced state holds two blocks, so a single-block call does twice the //! necessary work; the two-block path should be close to twice the throughput. That ratio is the //! argument for modes of operation using the two-block entry points wherever their blocks are @@ -70,13 +70,13 @@ fn bench_aes128(c: &mut Criterion) { }) }); - group.bench_function("16KiB -- .encrypt_blocks2() x512", |b| { + group.bench_function("16KiB -- .encrypt_2blocks() x512", |b| { b.iter(|| { let mut buf = blocks.clone(); for pair in buf.chunks_exact_mut(2) { // `try_into` cannot fail: `chunks_exact_mut(2)` yields slices of length 2. let pair: &mut [[u8; BLOCK_LEN]; 2] = pair.try_into().unwrap(); - aes.encrypt_blocks2(black_box(pair)); + aes.encrypt_2blocks(black_box(pair)); } black_box(&buf); }) @@ -92,12 +92,12 @@ fn bench_aes128(c: &mut Criterion) { }) }); - group.bench_function("16KiB -- .decrypt_blocks2() x512", |b| { + group.bench_function("16KiB -- .decrypt_2blocks() x512", |b| { b.iter(|| { let mut buf = blocks.clone(); for pair in buf.chunks_exact_mut(2) { let pair: &mut [[u8; BLOCK_LEN]; 2] = pair.try_into().unwrap(); - aes.decrypt_blocks2(black_box(pair)); + aes.decrypt_2blocks(black_box(pair)); } black_box(&buf); }) @@ -123,12 +123,12 @@ fn bench_aes192(c: &mut Criterion) { }) }); - group.bench_function("16KiB -- .encrypt_blocks2() x512", |b| { + group.bench_function("16KiB -- .encrypt_2blocks() x512", |b| { b.iter(|| { let mut buf = blocks.clone(); for pair in buf.chunks_exact_mut(2) { let pair: &mut [[u8; BLOCK_LEN]; 2] = pair.try_into().unwrap(); - aes.encrypt_blocks2(black_box(pair)); + aes.encrypt_2blocks(black_box(pair)); } black_box(&buf); }) @@ -154,23 +154,23 @@ fn bench_aes256(c: &mut Criterion) { }) }); - group.bench_function("16KiB -- .encrypt_blocks2() x512", |b| { + group.bench_function("16KiB -- .encrypt_2blocks() x512", |b| { b.iter(|| { let mut buf = blocks.clone(); for pair in buf.chunks_exact_mut(2) { let pair: &mut [[u8; BLOCK_LEN]; 2] = pair.try_into().unwrap(); - aes.encrypt_blocks2(black_box(pair)); + aes.encrypt_2blocks(black_box(pair)); } black_box(&buf); }) }); - group.bench_function("16KiB -- .decrypt_blocks2() x512", |b| { + group.bench_function("16KiB -- .decrypt_2blocks() x512", |b| { b.iter(|| { let mut buf = blocks.clone(); for pair in buf.chunks_exact_mut(2) { let pair: &mut [[u8; BLOCK_LEN]; 2] = pair.try_into().unwrap(); - aes.decrypt_blocks2(black_box(pair)); + aes.decrypt_2blocks(black_box(pair)); } black_box(&buf); }) diff --git a/crypto/aes/src/aes.rs b/crypto/aes/src/aes.rs index d4a35cc0..32e75b81 100644 --- a/crypto/aes/src/aes.rs +++ b/crypto/aes/src/aes.rs @@ -20,7 +20,7 @@ pub const BLOCK_LEN: usize = 16; /// /// The only state is the key schedule, held in a [`Secret`] so that it is zeroized on drop and /// redacted from `Debug`. There is no direction flag and no initialisation state: both directions -/// work from the same schedule (see [`ElectronicCodeBook::decrypt_blocks2`]), and a constructed value is always +/// work from the same schedule (see [`ElectronicCodeBook::decrypt_2blocks`]), and a constructed value is always /// ready to use, so there is no `init()` or `reset()`. pub struct AES { schedule: Secret, @@ -131,15 +131,15 @@ impl AES

{ /// serially dependent. /// /// Infallible: a constructed [`AES`] is always usable and every input length is fixed. - pub(crate) fn encrypt_blocks2(&self, blocks: &mut [Block; 2]) { + pub(crate) fn encrypt_2blocks(&self, blocks: &mut [Block; 2]) { let mut q = pack(&blocks[0], &blocks[1]); self.encrypt2(&mut q); let (a, b) = blocks.split_at_mut(1); unpack(&q, &mut a[0], &mut b[0]); } - /// Decrypts two blocks in place. See [`ElectronicCodeBook::encrypt_blocks2`]. - pub(crate) fn decrypt_blocks2(&self, blocks: &mut [Block; 2]) { + /// Decrypts two blocks in place. See [`ElectronicCodeBook::encrypt_2blocks`]. + pub(crate) fn decrypt_2blocks(&self, blocks: &mut [Block; 2]) { let mut q = pack(&blocks[0], &blocks[1]); self.decrypt2(&mut q); let (a, b) = blocks.split_at_mut(1); @@ -150,7 +150,7 @@ impl AES

{ /// /// The bit-sliced state always holds two blocks, so a single-block call duplicates the block /// into both halves and discards one result: it does twice the necessary work. Use - /// [`ElectronicCodeBook::encrypt_blocks2`] where two blocks are available. + /// [`ElectronicCodeBook::encrypt_2blocks`] where two blocks are available. /// /// Duplicating the block costs exactly what filling the unused half with zeros would, and it /// buys a free self-check: the two halves must come out equal, which `debug_assert` verifies. @@ -227,7 +227,7 @@ impl Algorithm for AES_256 { // The three `ElectronicCodeBook` impls are one-line delegations to the inherent methods above. They // are written out longhand rather than generated, for the `cargo mutants` reason given above. // -// Each overrides `encrypt_blocks2` / `decrypt_blocks2`, because a pair of blocks is exactly what +// Each overrides `encrypt_2blocks` / `decrypt_2blocks`, because a pair of blocks is exactly what // the bit-sliced state holds: the pair form costs barely more than one block, where the default // (two single-block calls) would do four blocks' worth of work. @@ -241,11 +241,11 @@ impl ElectronicCodeBook<16, BLOCK_LEN> for AES_128 { fn decrypt_block(&self, block: &mut Block) { AES::decrypt_block(self, block) } - fn encrypt_blocks2(&self, blocks: &mut [Block; 2]) { - AES::encrypt_blocks2(self, blocks) + fn encrypt_2blocks(&self, blocks: &mut [Block; 2]) { + AES::encrypt_2blocks(self, blocks) } - fn decrypt_blocks2(&self, blocks: &mut [Block; 2]) { - AES::decrypt_blocks2(self, blocks) + fn decrypt_2blocks(&self, blocks: &mut [Block; 2]) { + AES::decrypt_2blocks(self, blocks) } } @@ -259,11 +259,11 @@ impl ElectronicCodeBook<24, BLOCK_LEN> for AES_192 { fn decrypt_block(&self, block: &mut Block) { AES::decrypt_block(self, block) } - fn encrypt_blocks2(&self, blocks: &mut [Block; 2]) { - AES::encrypt_blocks2(self, blocks) + fn encrypt_2blocks(&self, blocks: &mut [Block; 2]) { + AES::encrypt_2blocks(self, blocks) } - fn decrypt_blocks2(&self, blocks: &mut [Block; 2]) { - AES::decrypt_blocks2(self, blocks) + fn decrypt_2blocks(&self, blocks: &mut [Block; 2]) { + AES::decrypt_2blocks(self, blocks) } } @@ -277,11 +277,11 @@ impl ElectronicCodeBook<32, BLOCK_LEN> for AES_256 { fn decrypt_block(&self, block: &mut Block) { AES::decrypt_block(self, block) } - fn encrypt_blocks2(&self, blocks: &mut [Block; 2]) { - AES::encrypt_blocks2(self, blocks) + fn encrypt_2blocks(&self, blocks: &mut [Block; 2]) { + AES::encrypt_2blocks(self, blocks) } - fn decrypt_blocks2(&self, blocks: &mut [Block; 2]) { - AES::decrypt_blocks2(self, blocks) + fn decrypt_2blocks(&self, blocks: &mut [Block; 2]) { + AES::decrypt_2blocks(self, blocks) } } diff --git a/crypto/aes/src/lib.rs b/crypto/aes/src/lib.rs index bf7382a6..b764be9c 100644 --- a/crypto/aes/src/lib.rs +++ b/crypto/aes/src/lib.rs @@ -40,7 +40,7 @@ //! ## Two blocks at a time //! //! The bit-sliced state holds two blocks, so two independent blocks cost barely more than one. -//! Where a caller has two, [`ElectronicCodeBook::encrypt_blocks2`](bouncycastle_core::traits::ElectronicCodeBook::encrypt_blocks2) is roughly twice the throughput of two +//! Where a caller has two, [`ElectronicCodeBook::encrypt_2blocks`](bouncycastle_core::traits::ElectronicCodeBook::encrypt_2blocks) is roughly twice the throughput of two //! [`ElectronicCodeBook::encrypt_block`](bouncycastle_core::traits::ElectronicCodeBook::encrypt_block) calls: //! //! ``` @@ -53,8 +53,8 @@ //! let aes = AES_256::new(&key).expect("a valid AES-256 key"); //! //! let mut blocks = [[0u8; 16], [1u8; 16]]; -//! aes.encrypt_blocks2(&mut blocks); -//! aes.decrypt_blocks2(&mut blocks); +//! aes.encrypt_2blocks(&mut blocks); +//! aes.decrypt_2blocks(&mut blocks); //! assert_eq!(blocks, [[0u8; 16], [1u8; 16]]); //! ``` //! diff --git a/crypto/aes/summary.md b/crypto/aes/summary.md index 4943c216..456e6893 100644 --- a/crypto/aes/summary.md +++ b/crypto/aes/summary.md @@ -166,8 +166,8 @@ Per-call stack usage is independent of key length: 32 B of bit-sliced state for AES_128::new(&KeyMaterial<16>) -> Result // and 24 / 32 aes.encrypt_block(&mut [u8; 16]) // infallible aes.decrypt_block(&mut [u8; 16]) -aes.encrypt_blocks2(&mut [[u8; 16]; 2]) // the natural unit of work -aes.decrypt_blocks2(&mut [[u8; 16]; 2]) +aes.encrypt_2blocks(&mut [[u8; 16]; 2]) // the natural unit of work +aes.decrypt_2blocks(&mut [[u8; 16]; 2]) ``` No `init()`, no `reset()`, no direction flag: constructors set up state and a constructed value is @@ -175,7 +175,7 @@ always ready. There are no one-shot statics on the permutation because `AES_128::new(&key)?.encrypt_block(..)` already *is* the one shot; data-level one-shots belong to the modes, which take arbitrary-length input and generate their own initialisation data. -`encrypt_blocks2` / `decrypt_blocks2` are the pair form and roughly double throughput. A +`encrypt_2blocks` / `decrypt_2blocks` are the pair form and roughly double throughput. A single-block call duplicates the block into both halves and discards one result, so it does twice the necessary work — modes whose blocks are independent (CTR, and the decrypt direction of CBC and CFB) should prefer the pair form; CBC *encryption* cannot, since its blocks are serially dependent. @@ -410,7 +410,7 @@ files (see the ML-KEM and ML-DSA suites). | Item | Why | |---|---| -| `ElectronicCodeBook` trait impls, and `encrypt_blocks2`/`decrypt_blocks2` as trait methods | The trait does not exist in `crypto/core`, which has the mode-level `BlockCipher` / `BlockCipherEncryptor` / `BlockCipherDecryptor`. Introducing it is the plan's separate "PR A". The two-block entry points are inherent methods for now; promoting them to provided trait methods is a one-line delegation once the trait lands. | +| `ElectronicCodeBook` trait impls, and `encrypt_2blocks`/`decrypt_2blocks` as trait methods | The trait does not exist in `crypto/core`, which has the mode-level `BlockCipher` / `BlockCipherEncryptor` / `BlockCipherDecryptor`. Introducing it is the plan's separate "PR A". The two-block entry points are inherent methods for now; promoting them to provided trait methods is a one-line delegation once the trait lands. | | `core-test-framework` conformance test | Follows from the above — there is no test suite for a raw permutation yet. | | ACVP MCT (Monte Carlo) groups — 6 cases | Their expected `resultsArray` comes from a chained key/plaintext update rule defined in the ACVP AES specification, not in FIPS 197. Implementing it from anything other than that specification would be guesswork. The test reports the skip count so the gap is visible rather than silent. | | CLI subcommand | A bare permutation only does ECB. `aes128-cbc-*` / `-cfb-*` belong with the modes crate. | diff --git a/crypto/aes/tests/acvp_tests.rs b/crypto/aes/tests/acvp_tests.rs index c4d35005..1c9ae315 100644 --- a/crypto/aes/tests/acvp_tests.rs +++ b/crypto/aes/tests/acvp_tests.rs @@ -160,21 +160,21 @@ fn ecb_pairwise(key: &[u8], data: &[u8], encrypt: bool) -> Vec { let km = cipher_key::<16>(key); let aes = AES_128::new(&km).unwrap(); run_pairwise(&mut blocks, encrypt, |p, e| { - if e { aes.encrypt_blocks2(p) } else { aes.decrypt_blocks2(p) } + if e { aes.encrypt_2blocks(p) } else { aes.decrypt_2blocks(p) } }); } 24 => { let km = cipher_key::<24>(key); let aes = AES_192::new(&km).unwrap(); run_pairwise(&mut blocks, encrypt, |p, e| { - if e { aes.encrypt_blocks2(p) } else { aes.decrypt_blocks2(p) } + if e { aes.encrypt_2blocks(p) } else { aes.decrypt_2blocks(p) } }); } 32 => { let km = cipher_key::<32>(key); let aes = AES_256::new(&km).unwrap(); run_pairwise(&mut blocks, encrypt, |p, e| { - if e { aes.encrypt_blocks2(p) } else { aes.decrypt_blocks2(p) } + if e { aes.encrypt_2blocks(p) } else { aes.decrypt_2blocks(p) } }); } other => panic!("ACVP AES vectors should only use 16, 24 or 32 byte keys, got {other}"), @@ -253,13 +253,13 @@ fn acvp_aes_ecb_known_answer_tests() { assert_eq!( ecb_pairwise(&key, &pt, true), ct, - "tcId {tc_id}: AES-{} encrypt via encrypt_blocks2", + "tcId {tc_id}: AES-{} encrypt via encrypt_2blocks", key.len() * 8 ); assert_eq!( ecb_pairwise(&key, &ct, false), pt, - "tcId {tc_id}: AES-{} decrypt via decrypt_blocks2", + "tcId {tc_id}: AES-{} decrypt via decrypt_2blocks", key.len() * 8 ); diff --git a/crypto/aes/tests/electronic_code_book_tests.rs b/crypto/aes/tests/electronic_code_book_tests.rs index f387be95..3600983f 100644 --- a/crypto/aes/tests/electronic_code_book_tests.rs +++ b/crypto/aes/tests/electronic_code_book_tests.rs @@ -3,8 +3,8 @@ //! The framework checks the properties every implementor must have -- both directions are //! inverses, the permutation is injective, the pair methods are indistinguishable from two //! single-block calls *including their order*, and the key checks behave. That last pair of -//! properties matters here specifically: this crate overrides `encrypt_blocks2` and -//! `decrypt_blocks2`, so the default implementation is not what runs. +//! properties matters here specifically: this crate overrides `encrypt_2blocks` and +//! `decrypt_2blocks`, so the default implementation is not what runs. use bouncycastle_aes::{AES_128, AES_192, AES_256, BLOCK_LEN}; use bouncycastle_core_test_framework::electronic_code_book::TestFrameworkElectronicCodeBook; diff --git a/crypto/aes/tests/fips197_tests.rs b/crypto/aes/tests/fips197_tests.rs index 7ea4a818..7e626668 100644 --- a/crypto/aes/tests/fips197_tests.rs +++ b/crypto/aes/tests/fips197_tests.rs @@ -99,13 +99,13 @@ fn appendix_b_two_block_path_agrees_with_the_single_block_path() { aes.encrypt_block(&mut other_alone); let mut pair = [input, other]; - aes.encrypt_blocks2(&mut pair); + aes.encrypt_2blocks(&mut pair); assert_eq!(pair[0], expected); assert_eq!(pair[1], other_alone); // ...and in the other slot, which is a different bit position in the interleave. let mut pair = [other, input]; - aes.encrypt_blocks2(&mut pair); + aes.encrypt_2blocks(&mut pair); assert_eq!(pair[0], other_alone); assert_eq!(pair[1], expected); } diff --git a/crypto/aes/tests/sp800_38a_tests.rs b/crypto/aes/tests/sp800_38a_tests.rs index c13e4afb..1fd42cf1 100644 --- a/crypto/aes/tests/sp800_38a_tests.rs +++ b/crypto/aes/tests/sp800_38a_tests.rs @@ -150,11 +150,11 @@ fn two_block_path_matches_the_f_1_vectors() { for chunk in 0..2 { let (i, j) = (chunk * 2, chunk * 2 + 1); let mut pair = [block(PLAINTEXTS[i]), block(PLAINTEXTS[j])]; - aes.encrypt_blocks2(&mut pair); + aes.encrypt_2blocks(&mut pair); assert_eq!(pair[0], block(CIPHERTEXTS_128[i]), "pair {chunk} slot 0"); assert_eq!(pair[1], block(CIPHERTEXTS_128[j]), "pair {chunk} slot 1"); - aes.decrypt_blocks2(&mut pair); + aes.decrypt_2blocks(&mut pair); assert_eq!(pair[0], block(PLAINTEXTS[i])); assert_eq!(pair[1], block(PLAINTEXTS[j])); } @@ -167,8 +167,8 @@ fn two_block_path_is_slot_symmetric() { let mut forward = [block(PLAINTEXTS[0]), block(PLAINTEXTS[1])]; let mut reversed = [block(PLAINTEXTS[1]), block(PLAINTEXTS[0])]; - aes.encrypt_blocks2(&mut forward); - aes.encrypt_blocks2(&mut reversed); + aes.encrypt_2blocks(&mut forward); + aes.encrypt_2blocks(&mut reversed); assert_eq!(forward[0], reversed[1]); assert_eq!(forward[1], reversed[0]); diff --git a/crypto/core-test-framework/src/electronic_code_book.rs b/crypto/core-test-framework/src/electronic_code_book.rs index 4691e3f9..214d1e5d 100644 --- a/crypto/core-test-framework/src/electronic_code_book.rs +++ b/crypto/core-test-framework/src/electronic_code_book.rs @@ -30,8 +30,8 @@ impl TestFrameworkElectronicCodeBook { /// * `decrypt_block` inverts `encrypt_block` on every block of [`DUMMY_SEED`]; /// * the permutation actually permutes (a block is not left unchanged); /// * distinct inputs give distinct outputs, i.e. it is injective on the blocks tested; - /// * `encrypt_blocks2` agrees with two `encrypt_block` calls **including their order**, and - /// likewise for `decrypt_blocks2` -- this is what pins an override to the default's + /// * `encrypt_2blocks` agrees with two `encrypt_block` calls **including their order**, and + /// likewise for `decrypt_2blocks` -- this is what pins an override to the default's /// semantics, and it is the reason the pair methods are worth having in the trait at all; /// * the pair methods round-trip each other; /// * `encrypt_blocks8` / `decrypt_blocks8` likewise agree with eight single-block calls in @@ -93,21 +93,21 @@ impl TestFrameworkElectronicCodeBook { perm.encrypt_block(&mut singly[0]); perm.encrypt_block(&mut singly[1]); let mut paired = [*a, *b]; - perm.encrypt_blocks2(&mut paired); - assert_eq!(paired, singly, "encrypt_blocks2 must match two encrypt_block calls"); + perm.encrypt_2blocks(&mut paired); + assert_eq!(paired, singly, "encrypt_2blocks must match two encrypt_block calls"); let mut singly = [*a, *b]; perm.decrypt_block(&mut singly[0]); perm.decrypt_block(&mut singly[1]); let mut paired = [*a, *b]; - perm.decrypt_blocks2(&mut paired); - assert_eq!(paired, singly, "decrypt_blocks2 must match two decrypt_block calls"); + perm.decrypt_2blocks(&mut paired); + assert_eq!(paired, singly, "decrypt_2blocks must match two decrypt_block calls"); // Round-trip through the pair methods alone. let mut buf = [*a, *b]; - perm.encrypt_blocks2(&mut buf); - perm.decrypt_blocks2(&mut buf); - assert_eq!(buf, [*a, *b], "decrypt_blocks2 must invert encrypt_blocks2"); + perm.encrypt_2blocks(&mut buf); + perm.decrypt_2blocks(&mut buf); + assert_eq!(buf, [*a, *b], "decrypt_2blocks must invert encrypt_2blocks"); } // The eight-block methods must be indistinguishable from eight single-block calls, in every @@ -144,7 +144,7 @@ impl TestFrameworkElectronicCodeBook { // implementation whose two lanes are not actually independent. let block = blocks[0]; let mut buf = [block, block]; - perm.encrypt_blocks2(&mut buf); + perm.encrypt_2blocks(&mut buf); assert_eq!(buf[0], buf[1], "identical inputs must give identical outputs"); let mut single = block; perm.encrypt_block(&mut single); diff --git a/crypto/core-test-framework/summary.md b/crypto/core-test-framework/summary.md index 5effa4a0..df164c32 100644 --- a/crypto/core-test-framework/summary.md +++ b/crypto/core-test-framework/summary.md @@ -31,15 +31,15 @@ TestFrameworkElectronicCodeBook::new().test::(); | `decrypt_block` inverts `encrypt_block`, **and vice versa** | A direction implemented only one way round. A mode may call either direction first, so both orders are exercised. | | Neither direction is the identity | A stub, or a key schedule that never got applied. | | Distinct blocks give distinct outputs | An implementation that is not injective — e.g. one masking part of the block away. A permutation must be. | -| `encrypt_blocks2` == two `encrypt_block` calls, **including their order**; same for decrypt | The whole reason the pair methods are safe to override. See below. | +| `encrypt_2blocks` == two `encrypt_block` calls, **including their order**; same for decrypt | The whole reason the pair methods are safe to override. See below. | | The pair methods round-trip each other | A pair path correct in one direction only. | -| Identical inputs give identical outputs from `*_blocks2` | Lanes that are not actually independent — a real hazard for a bit-sliced implementation that interleaves two blocks in one word. | +| Identical inputs give identical outputs from `*_2blocks` | Lanes that are not actually independent — a real hazard for a bit-sliced implementation that interleaves two blocks in one word. | | A key of the wrong `KeyType` is rejected | A seed or MAC key being reused as a cipher key. | | The security-strength policy matches `BlockCipher::MAX_SECURITY_STRENGTH` | A `new()` that accepts a key weaker than the algorithm, or rejects one strong enough. | ### The order check is the load-bearing one -`ElectronicCodeBook::encrypt_blocks2` and `decrypt_blocks2` are *provided* methods: the default is +`ElectronicCodeBook::encrypt_2blocks` and `decrypt_2blocks` are *provided* methods: the default is two single-block calls, and implementations are free to override them. `bouncycastle-aes` does, because a pair of blocks is exactly what its bit-sliced state holds, so the pair form costs barely more than one block. diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 92dac3c7..41d4d4ab 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -357,15 +357,15 @@ pub trait ElectronicCodeBook: /// Modes whose structure is parallel -- CBC decryption, CFB decryption, CTR -- should prefer /// this. CBC and CFB *encryption* cannot use it: each input block depends on the previous /// output. - fn encrypt_blocks2(&self, blocks: &mut [[u8; BLOCK_LEN]; 2]) { + fn encrypt_2blocks(&self, blocks: &mut [[u8; BLOCK_LEN]; 2]) { let [a, b] = blocks; self.encrypt_block(a); self.encrypt_block(b); } /// The inverse cipher function on two *independent* blocks, in place. - /// See [`ElectronicCodeBook::encrypt_blocks2`]. - fn decrypt_blocks2(&self, blocks: &mut [[u8; BLOCK_LEN]; 2]) { + /// See [`ElectronicCodeBook::encrypt_2blocks`]. + fn decrypt_2blocks(&self, blocks: &mut [[u8; BLOCK_LEN]; 2]) { let [a, b] = blocks; self.decrypt_block(a); self.decrypt_block(b); @@ -373,7 +373,7 @@ pub trait ElectronicCodeBook: /// The forward cipher function on eight *independent* blocks, in place. /// - /// Provided as four [`ElectronicCodeBook::encrypt_blocks2`] calls, so an implementation that + /// Provided as four [`ElectronicCodeBook::encrypt_2blocks`] calls, so an implementation that /// overrides only the pair form gets its benefit here too. An engine whose natural unit is /// larger than a pair overrides this directly: a bit-sliced engine whose S-box circuit /// substitutes four blocks per pass runs eight blocks as two full passes rather than four @@ -388,7 +388,7 @@ pub trait ElectronicCodeBook: // Eight is a multiple of two, so the remainder is empty. let (pairs, _) = blocks.as_mut_slice().as_chunks_mut::<2>(); for pair in pairs { - self.encrypt_blocks2(pair); + self.encrypt_2blocks(pair); } } @@ -397,7 +397,7 @@ pub trait ElectronicCodeBook: fn decrypt_blocks8(&self, blocks: &mut [[u8; BLOCK_LEN]; 8]) { let (pairs, _) = blocks.as_mut_slice().as_chunks_mut::<2>(); for pair in pairs { - self.decrypt_blocks2(pair); + self.decrypt_2blocks(pair); } } } diff --git a/crypto/modes/benches/modes_benches.rs b/crypto/modes/benches/modes_benches.rs index e80c05b0..c6664b37 100644 --- a/crypto/modes/benches/modes_benches.rs +++ b/crypto/modes/benches/modes_benches.rs @@ -5,7 +5,7 @@ //! depends on the previous output), so it can only ever use the single-block path. *Decryption* in //! both is parallel, and this implementation hands blocks to the permutation's batch methods -- //! eights first, then pairs, then the remainder singly: for CBC that is `decrypt_blocks8` / -//! `decrypt_blocks2`, for CFB it is `encrypt_blocks8` / `encrypt_blocks2`, since CFB uses the +//! `decrypt_2blocks`, for CFB it is `encrypt_blocks8` / `encrypt_2blocks`, since CFB uses the //! forward function in both directions. AES overrides only the pair form, so its eights are four //! pairs. With the bit-sliced AES, whose two-block path costs barely more than one block, //! decryption should therefore run at roughly twice the throughput of encryption. That gap is the @@ -68,7 +68,7 @@ type Aes128Ecb

= Ecb; /// This exists purely to isolate the value of the pair path. Comparing `Cbc` against /// `Cbc` at the *same* `N` holds everything else fixed -- same cipher, same /// call granularity, same amount of data movement -- so the difference is attributable to -/// `decrypt_blocks2` and nothing else. +/// `decrypt_2blocks` and nothing else. /// /// Comparing `N = 1` against `N = 8` does *not* isolate it: encryption, which can never pair, also /// speeds up substantially between those two, so call granularity dominates that comparison. @@ -89,7 +89,7 @@ impl ElectronicCodeBook<16, BLOCK_LEN> for UnpairedAes128 { fn decrypt_block(&self, block: &mut [u8; BLOCK_LEN]) { >::decrypt_block(&self.0, block) } - // encrypt_blocks2 / decrypt_blocks2 deliberately left as the trait defaults. + // encrypt_2blocks / decrypt_2blocks deliberately left as the trait defaults. } type UnpairedAes128Cbc = Cbc; @@ -145,7 +145,7 @@ fn bench_aes128(c: &mut Criterion) { ) }); - // ---- decryption: parallel, uses decrypt_blocks2 for every pair ---- + // ---- decryption: parallel, uses decrypt_2blocks for every pair ---- let (mut enc, iv) = Aes128Cbc::::do_encrypt_init(&k).unwrap(); let mut ciphertext = blocks.clone(); for chunk in ciphertext.chunks_exact_mut(8) { @@ -169,7 +169,7 @@ fn bench_aes128(c: &mut Criterion) { }); // N=2 is one pair and N=8 one eight (four pairs, for AES), so every block goes through - // decrypt_blocks2. + // decrypt_2blocks. group.bench_function("16KiB decrypt -- N=2 (all pairs)", |b| { b.iter_batched( || ciphertext.clone(), @@ -220,8 +220,8 @@ fn bench_aes128(c: &mut Criterion) { }); // The controlled comparison: identical N, identical cipher, pair methods overridden vs not. - // This pair of numbers -- and only this pair -- measures what `decrypt_blocks2` buys. - group.bench_function("16KiB decrypt -- N=8, pair path (blocks2 overridden)", |b| { + // This pair of numbers -- and only this pair -- measures what `decrypt_2blocks` buys. + group.bench_function("16KiB decrypt -- N=8, pair path (2blocks overridden)", |b| { b.iter_batched( || ciphertext.clone(), |mut scratch| { @@ -368,7 +368,7 @@ fn bench_cfb_aes128(c: &mut Criterion) { }); } - // ---- decryption: parallel, and uses `encrypt_blocks8` / `encrypt_blocks2` -- the FORWARD + // ---- decryption: parallel, and uses `encrypt_blocks8` / `encrypt_2blocks` -- the FORWARD // batch methods ---- let (mut enc, iv) = Aes128Cfb::::do_encrypt_init(&k).unwrap(); let mut ciphertext = flat.clone(); @@ -401,8 +401,8 @@ fn bench_cfb_aes128(c: &mut Criterion) { } // The controlled comparison: identical N, identical cipher, pair methods overridden vs not. - // This pair of numbers -- and only this pair -- measures what `encrypt_blocks2` buys CFB. - group.bench_function("16KiB decrypt -- N=8, pair path (blocks2 overridden)", |b| { + // This pair of numbers -- and only this pair -- measures what `encrypt_2blocks` buys CFB. + group.bench_function("16KiB decrypt -- N=8, pair path (2blocks overridden)", |b| { b.iter_batched( || ciphertext.clone(), |mut scratch| { @@ -485,7 +485,7 @@ fn bench_cfb_aes256(c: &mut Criterion) { /// CFB8: one forward cipher per byte, so ~1/16 of CFB's throughput on a 16-byte block. /// /// Encryption is strictly serial. Decryption builds its input blocks in series and then runs them -/// through `encrypt_blocks8` / `encrypt_blocks2` (SP 800-38A Sec 6.3's parallel decryption), so it +/// through `encrypt_blocks8` / `encrypt_2blocks` (SP 800-38A Sec 6.3's parallel decryption), so it /// should be substantially faster than encryption -- the same batch effect CBC and CFB show, at /// byte granularity. fn bench_cfb8_aes128(c: &mut Criterion) { diff --git a/crypto/modes/src/cbc.rs b/crypto/modes/src/cbc.rs index a5ea5ce1..9ad9e402 100644 --- a/crypto/modes/src/cbc.rs +++ b/crypto/modes/src/cbc.rs @@ -28,7 +28,7 @@ //! //! This implementation uses that: decryption walks the ciphertext eight blocks at a time through //! [`ElectronicCodeBook::decrypt_blocks8`], then any remaining pair through -//! [`ElectronicCodeBook::decrypt_blocks2`], then the last block singly. A bit-sliced engine +//! [`ElectronicCodeBook::decrypt_2blocks`], then the last block singly. A bit-sliced engine //! computes a pair (AES) or eight blocks (SM4) for barely more than the cost of one. Encryption //! cannot, and does not. @@ -94,7 +94,7 @@ where self.chain = cj; } - /// Decrypts two consecutive blocks with one [`ElectronicCodeBook::decrypt_blocks2`] call. + /// Decrypts two consecutive blocks with one [`ElectronicCodeBook::decrypt_2blocks`] call. /// /// Writing the pair as `Cj, Cj+1` with `Cj-1` the incoming chaining value, Sec 6.2 gives /// @@ -110,7 +110,7 @@ where #[inline] fn decrypt_pair(&mut self, blocks: &mut [[u8; BLOCK_LEN]; 2]) { let [cj, cj1] = *blocks; - self.perm.decrypt_blocks2(blocks); + self.perm.decrypt_2blocks(blocks); let [pj, pj1] = blocks; for (b, chain) in pj.iter_mut().zip(self.chain.iter()) { @@ -213,7 +213,7 @@ where /// The implementor hook (the flat `do_decrypt` is provided over it). /// - /// Walks the input in eights through `decrypt_blocks8`, then pairs through `decrypt_blocks2`, + /// Walks the input in eights through `decrypt_blocks8`, then pairs through `decrypt_2blocks`, /// then the at-most-one block left over: Sec 6.2's parallelism, in the units the permutation /// offers. `as_chunks_mut` splits into exactly those shapes with no runtime length check and no /// indexing arithmetic. Never fails: CBC has no per-IV data limit. diff --git a/crypto/modes/src/cfb.rs b/crypto/modes/src/cfb.rs index 76178b1d..40e9473b 100644 --- a/crypto/modes/src/cfb.rs +++ b/crypto/modes/src/cfb.rs @@ -101,7 +101,7 @@ //! applied to each input block to produce the output blocks." //! //! So [`Cfb`](Cfb) never calls [`ElectronicCodeBook::decrypt_block`], -//! [`ElectronicCodeBook::decrypt_blocks2`] or [`ElectronicCodeBook::decrypt_blocks8`]. A +//! [`ElectronicCodeBook::decrypt_2blocks`] or [`ElectronicCodeBook::decrypt_blocks8`]. A //! permutation could implement only the forward direction and still work here; `cfb_tests.rs` pins //! that with a toy whose inverse panics. The mode XORs a keystream in both directions, and the two //! directions differ only in which of the two values -- the byte that came in, or the byte that @@ -118,7 +118,7 @@ //! Constructing them "in series" is trivial here: with `s = b` the input blocks *are* the IV //! followed by the ciphertext blocks, already in hand. Decryption therefore walks the //! block-aligned part of the data in eights through [`ElectronicCodeBook::encrypt_blocks8`] and -//! pairs through [`ElectronicCodeBook::encrypt_blocks2`], which a bit-sliced engine computes for +//! pairs through [`ElectronicCodeBook::encrypt_2blocks`], which a bit-sliced engine computes for //! barely more than the cost of one block. Encryption cannot, and does not. Only the bytes that //! complete an open segment, and the bytes that open the final short one, go singly. @@ -251,7 +251,7 @@ where self.buf = cj; } - /// Decrypts two consecutive blocks with one [`ElectronicCodeBook::encrypt_blocks2`] call. + /// Decrypts two consecutive blocks with one [`ElectronicCodeBook::encrypt_2blocks`] call. /// /// Writing the pair as `Cj, Cj+1` with `Ij` the incoming input block, the `s = b` equations /// give @@ -273,7 +273,7 @@ where debug_assert_eq!(self.used, BLOCK_LEN, "the block path needs a segment boundary"); // The two input blocks, constructed in series: Ij (already held) and Ij+1 (= Cj). let mut o = [self.buf, blocks[0]]; - self.perm.encrypt_blocks2(&mut o); + self.perm.encrypt_2blocks(&mut o); // I_{j+2} = Cj+1, read before the XOR below turns it into Pj+1. self.buf = blocks[1]; diff --git a/crypto/modes/src/cfb8.rs b/crypto/modes/src/cfb8.rs index 717e6bf9..545d2491 100644 --- a/crypto/modes/src/cfb8.rs +++ b/crypto/modes/src/cfb8.rs @@ -77,7 +77,7 @@ //! successive states in series -- byte shuffling, no cipher calls -- and then run the forward //! ciphers together. This implementation does exactly that, in eights through //! [`ElectronicCodeBook::encrypt_blocks8`] and then pairs through -//! [`ElectronicCodeBook::encrypt_blocks2`], which is where a bit-sliced engine earns back a large +//! [`ElectronicCodeBook::encrypt_2blocks`], which is where a bit-sliced engine earns back a large //! part of what the mode costs. Encryption cannot: `Ij` needs `C_{j-1}`, which is the output of the //! previous cipher call. @@ -263,7 +263,7 @@ where } let (pairs, tail) = rest.as_chunks_mut::<2>(); for pair in pairs.iter_mut() { - self.decrypt_batch(pair, P::encrypt_blocks2); + self.decrypt_batch(pair, P::encrypt_2blocks); } for byte in tail.iter_mut() { let c = *byte; diff --git a/crypto/modes/src/ctr.rs b/crypto/modes/src/ctr.rs index a5545f44..4cd3c12e 100644 --- a/crypto/modes/src/ctr.rs +++ b/crypto/modes/src/ctr.rs @@ -93,7 +93,7 @@ //! performed in parallel". Counter blocks depend on nothing but the nonce and the index, so unlike //! CBC and CFB there is no serial direction at all: **both** directions walk the block-aligned part //! of the data in eights through [`ElectronicCodeBook::encrypt_blocks8`], then in pairs through -//! [`ElectronicCodeBook::encrypt_blocks2`]. Only the bytes that finish a partially-used keystream +//! [`ElectronicCodeBook::encrypt_2blocks`]. Only the bytes that finish a partially-used keystream //! block, and the short tail at the end, go one block at a time. //! //! Like the rest of CFB and CTR, only the **forward** cipher function is ever used, in both @@ -373,7 +373,7 @@ where } let (pairs, single) = rest_blocks.as_chunks_mut::<2>(); for pair in pairs.iter_mut() { - self.apply_batch(pair, P::encrypt_blocks2); + self.apply_batch(pair, P::encrypt_2blocks); } for block in single.iter_mut() { self.apply_one(block); diff --git a/crypto/modes/src/ecb.rs b/crypto/modes/src/ecb.rs index 49d338f4..d986e4f3 100644 --- a/crypto/modes/src/ecb.rs +++ b/crypto/modes/src/ecb.rs @@ -41,7 +41,7 @@ //! Sec 6.1: "In ECB encryption and ECB decryption, multiple forward cipher functions and inverse //! cipher functions can be computed in parallel." Unlike CBC and CFB, whose encryption is serial, //! both directions here batch through the permutation's eight-block and pair methods -//! ([`ElectronicCodeBook::encrypt_blocks8`] / [`ElectronicCodeBook::encrypt_blocks2`] and their +//! ([`ElectronicCodeBook::encrypt_blocks8`] / [`ElectronicCodeBook::encrypt_2blocks`] and their //! inverses), then finish the remaining block singly. use crate::{Decrypting, Encrypting}; @@ -140,7 +140,7 @@ where } let (pairs, tail) = rest.as_chunks_mut::<2>(); for pair in pairs.iter_mut() { - self.perm.encrypt_blocks2(pair); + self.perm.encrypt_2blocks(pair); } for block in tail.iter_mut() { self.perm.encrypt_block(block); @@ -176,7 +176,7 @@ where } let (pairs, tail) = rest.as_chunks_mut::<2>(); for pair in pairs.iter_mut() { - self.perm.decrypt_blocks2(pair); + self.perm.decrypt_2blocks(pair); } for block in tail.iter_mut() { self.perm.decrypt_block(block); diff --git a/crypto/modes/tests/acvp_cfb8_tests.rs b/crypto/modes/tests/acvp_cfb8_tests.rs index d7724e3d..8ecb55e1 100644 --- a/crypto/modes/tests/acvp_cfb8_tests.rs +++ b/crypto/modes/tests/acvp_cfb8_tests.rs @@ -23,7 +23,7 @@ //! that reach the batch paths. Every case is run **four times**: as one call over the whole //! payload, byte by byte, in 8-byte calls, and in 3-byte calls that never line up with the //! 8-byte batch. Between them those put the multi-byte cases through -//! [`ElectronicCodeBook::encrypt_blocks8`] and [`ElectronicCodeBook::encrypt_blocks2`] -- the +//! [`ElectronicCodeBook::encrypt_blocks8`] and [`ElectronicCodeBook::encrypt_2blocks`] -- the //! *forward* function, even on the decrypt side -- and through the single-byte path, with the //! shift register carried across calls at every alignment. So all of that is exercised against real //! vectors and not only against the toys in `cfb8_tests.rs`. diff --git a/crypto/modes/tests/acvp_cfb_tests.rs b/crypto/modes/tests/acvp_cfb_tests.rs index a2320a81..3f389964 100644 --- a/crypto/modes/tests/acvp_cfb_tests.rs +++ b/crypto/modes/tests/acvp_cfb_tests.rs @@ -25,7 +25,7 @@ //! block, in pairs with a one-block remainder for odd lengths, as one call over the whole payload, //! and in 5-byte calls that never line up with a block. The second and third passes are what put //! the multi-block cases through the pair and eight-block paths -- which for CFB are -//! [`ElectronicCodeBook::encrypt_blocks2`] and [`ElectronicCodeBook::encrypt_blocks8`], the +//! [`ElectronicCodeBook::encrypt_2blocks`] and [`ElectronicCodeBook::encrypt_blocks8`], the //! *forward* function, even on the decrypt side -- and the fourth is what puts them through the //! byte path with segments left open between calls. So all of that is exercised against real //! vectors and not only against the toys in `cfb_tests.rs`. Every ACVP CFB128 payload is a whole diff --git a/crypto/modes/tests/acvp_tests.rs b/crypto/modes/tests/acvp_tests.rs index 463aa283..980cffab 100644 --- a/crypto/modes/tests/acvp_tests.rs +++ b/crypto/modes/tests/acvp_tests.rs @@ -21,7 +21,7 @@ //! 2150 AFT (Algorithm Functional Test) cases across all three key lengths and both directions, //! including 60 whose payload spans 2 to 10 blocks. Every case is run **twice**: once block by //! block, and once in pairs with a one-block remainder for odd lengths. The second pass is what -//! puts the multi-block cases through `ElectronicCodeBook::decrypt_blocks2`, so the pair path is +//! puts the multi-block cases through `ElectronicCodeBook::decrypt_2blocks`, so the pair path is //! exercised against real vectors and not only against the toy in `cbc_tests.rs`. //! //! The 6 MCT (Monte Carlo Test) groups are **not** implemented: their expected output is a diff --git a/crypto/modes/tests/cbc_tests.rs b/crypto/modes/tests/cbc_tests.rs index b62c002a..361ebd4b 100644 --- a/crypto/modes/tests/cbc_tests.rs +++ b/crypto/modes/tests/cbc_tests.rs @@ -153,7 +153,7 @@ fn call_grouping_does_not_change_the_result() { /// The pair path in `do_decrypt_blocks` must actually be taken. /// /// [`SwappedPairToy`] returns its two pair results in the wrong order while its single-block -/// methods are correct. So a CBC decryptor that uses `decrypt_blocks2` gives the wrong answer for +/// methods are correct. So a CBC decryptor that uses `decrypt_2blocks` gives the wrong answer for /// even-length input, and the right answer for a single block. If both came out right, the pair /// path would be dead code and every claim about it would be untested. #[test] @@ -176,7 +176,7 @@ fn the_pair_path_is_really_used() { assert_ne!( dec_blocks(&mut dec, &ct), plaintext, - "decrypting a pair must go through decrypt_blocks2" + "decrypting a pair must go through decrypt_2blocks" ); // Decrypting one block at a time avoids the pair path, so it is correct even for this toy. diff --git a/crypto/modes/tests/cfb8_tests.rs b/crypto/modes/tests/cfb8_tests.rs index 6e5c224c..c4560467 100644 --- a/crypto/modes/tests/cfb8_tests.rs +++ b/crypto/modes/tests/cfb8_tests.rs @@ -249,7 +249,7 @@ fn the_ciphertext_of_a_prefix_is_a_prefix_of_the_ciphertext() { /// SP 800-38A Sec 6.3: "The *forward cipher* function is applied to each input block to produce the /// output blocks" -- in CFB *decryption* as well as encryption. /// -/// [`ForwardOnlyToy`] panics from `decrypt_block`, `decrypt_blocks2` and `decrypt_blocks8`, so this +/// [`ForwardOnlyToy`] panics from `decrypt_block`, `decrypt_2blocks` and `decrypt_blocks8`, so this /// test fails loudly if either direction of the mode ever reaches the inverse cipher. Every decrypt /// path is exercised -- eights, pairs and single bytes -- and the result is required to agree with /// the plain [`Toy`], otherwise the test could pass by not really encrypting anything. @@ -419,10 +419,10 @@ fn aes_chunking_matches_a_single_call() { /// The pair path in `do_decrypt` must actually be taken. /// /// [`SwappedPairToy`] returns its two pair results in the wrong order while its single-block method -/// is correct. CFB8 decryption batches through `encrypt_blocks2`, so with this permutation six +/// is correct. CFB8 decryption batches through `encrypt_2blocks`, so with this permutation six /// bytes handed over together come out wrong while the same bytes one at a time come out right. /// -/// Six, not eight: the trait's default `encrypt_blocks8` is four `encrypt_blocks2` calls, so eight +/// Six, not eight: the trait's default `encrypt_blocks8` is four `encrypt_2blocks` calls, so eight /// bytes would also be wrong and would not distinguish the two paths. #[test] fn the_pair_path_is_really_used() { @@ -441,7 +441,7 @@ fn the_pair_path_is_really_used() { // ...but decrypting six bytes together must now be wrong, because the pair path is used. let mut d = SwappedCfb8::::do_decrypt_init(&key, &iv).unwrap(); - assert_ne!(dec(&mut d, &ct), plaintext, "three pairs must go through encrypt_blocks2"); + assert_ne!(dec(&mut d, &ct), plaintext, "three pairs must go through encrypt_2blocks"); // One byte at a time avoids the pair path, so it is correct even for this toy. let mut d = SwappedCfb8::::do_decrypt_init(&key, &iv).unwrap(); diff --git a/crypto/modes/tests/cfb_tests.rs b/crypto/modes/tests/cfb_tests.rs index 94734859..ff68a563 100644 --- a/crypto/modes/tests/cfb_tests.rs +++ b/crypto/modes/tests/cfb_tests.rs @@ -264,7 +264,7 @@ fn the_ciphertext_of_a_prefix_is_a_prefix_of_the_ciphertext() { /// SP 800-38A Sec 6.3: "The *forward cipher* function is applied to each input block to produce the /// output blocks" -- in CFB *decryption* as well as encryption. /// -/// [`ForwardOnlyToy`] panics from `decrypt_block`, `decrypt_blocks2` and `decrypt_blocks8`, so this +/// [`ForwardOnlyToy`] panics from `decrypt_block`, `decrypt_2blocks` and `decrypt_blocks8`, so this /// test fails loudly if either direction of the mode ever reaches the inverse cipher. Every /// decrypt path is exercised -- the eight-block, pair, single-block and byte paths -- and the result /// is required to agree with the plain [`Toy`], otherwise the test could pass by not really @@ -449,7 +449,7 @@ fn aes_chunking_matches_a_single_call() { /// at a segment boundary. /// /// [`SwappedPairToy`] returns its two pair results in the wrong order while its single-block methods -/// are correct. CFB decryption pairs through `encrypt_blocks2`, so with this permutation two blocks +/// are correct. CFB decryption pairs through `encrypt_2blocks`, so with this permutation two blocks /// handed over together come out wrong, while the same bytes handed over one block at a time, or /// offset by a partial segment so that no two whole blocks line up, come out right. If everything /// came out right, the pair path would be dead code and every claim about it would be untested. @@ -464,14 +464,14 @@ fn the_pair_path_is_really_used() { assert_eq!(dec(&mut pinned_decryptor(iv), &ct), plaintext); // The swapped-pair toy encrypts identically -- CFB encryption is serial and never pairs, so its - // `encrypt_blocks2` override is not reached from the encryptor at all. + // `encrypt_2blocks` override is not reached from the encryptor at all. let (mut e, _) = SwappedCfb::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)).unwrap(); assert_eq!(enc(&mut e, &plaintext), ct, "CFB encryption must not use the pair path"); // ...but decrypting the pair together must now be wrong, because the pair path is used. let mut d = SwappedCfb::::do_decrypt_init(&key, &iv).unwrap(); - assert_ne!(dec(&mut d, &ct), plaintext, "decrypting a pair must go through encrypt_blocks2"); + assert_ne!(dec(&mut d, &ct), plaintext, "decrypting a pair must go through encrypt_2blocks"); // Decrypting one block at a time avoids the pair path, so it is correct even for this toy. let mut d = SwappedCfb::::do_decrypt_init(&key, &iv).unwrap(); diff --git a/crypto/modes/tests/common/mod.rs b/crypto/modes/tests/common/mod.rs index 306b3052..da198150 100644 --- a/crypto/modes/tests/common/mod.rs +++ b/crypto/modes/tests/common/mod.rs @@ -80,7 +80,7 @@ impl ElectronicCodeBook for Toy { /// A deliberately broken toy whose pair methods **swap** their two results. /// /// Used to prove that the mode really does take the pair path: with this permutation, a CBC -/// decryptor that uses `decrypt_blocks2` must produce something other than the correct plaintext. +/// decryptor that uses `decrypt_2blocks` must produce something other than the correct plaintext. /// If a test using this still round-trips, the pair path is dead code and the coverage claimed for /// it is false. /// @@ -107,13 +107,13 @@ impl ElectronicCodeBook for SwappedPairToy { self.inner.decrypt_block(block); } - fn encrypt_blocks2(&self, blocks: &mut [[u8; TOY_LEN]; 2]) { + fn encrypt_2blocks(&self, blocks: &mut [[u8; TOY_LEN]; 2]) { self.inner.encrypt_block(&mut blocks[0]); self.inner.encrypt_block(&mut blocks[1]); blocks.swap(0, 1); } - fn decrypt_blocks2(&self, blocks: &mut [[u8; TOY_LEN]; 2]) { + fn decrypt_2blocks(&self, blocks: &mut [[u8; TOY_LEN]; 2]) { self.inner.decrypt_block(&mut blocks[0]); self.inner.decrypt_block(&mut blocks[1]); blocks.swap(0, 1); @@ -123,7 +123,7 @@ impl ElectronicCodeBook for SwappedPairToy { /// A toy whose **inverse cipher function panics**. /// /// SP 800-38A Sec 6.3 applies the forward cipher function in both directions of CFB, so a correct -/// `Cfb` never touches `decrypt_block`, `decrypt_blocks2` or `decrypt_blocks8`. Running a full CFB round trip over this +/// `Cfb` never touches `decrypt_block`, `decrypt_2blocks` or `decrypt_blocks8`. Running a full CFB round trip over this /// permutation turns that claim into a test: if either decryption entry point is ever reached, the /// test panics with the message below rather than quietly producing a right answer for the wrong /// reason. @@ -154,11 +154,11 @@ impl ElectronicCodeBook for ForwardOnlyToy { panic!("CFB must never call the inverse cipher function (SP 800-38A Sec 6.3)"); } - fn encrypt_blocks2(&self, blocks: &mut [[u8; TOY_LEN]; 2]) { - self.inner.encrypt_blocks2(blocks); + fn encrypt_2blocks(&self, blocks: &mut [[u8; TOY_LEN]; 2]) { + self.inner.encrypt_2blocks(blocks); } - fn decrypt_blocks2(&self, _blocks: &mut [[u8; TOY_LEN]; 2]) { + fn decrypt_2blocks(&self, _blocks: &mut [[u8; TOY_LEN]; 2]) { panic!("CFB must never call the inverse cipher pair function (SP 800-38A Sec 6.3)"); } diff --git a/crypto/modes/tests/ctr_tests.rs b/crypto/modes/tests/ctr_tests.rs index 3b386ffe..4a85477f 100644 --- a/crypto/modes/tests/ctr_tests.rs +++ b/crypto/modes/tests/ctr_tests.rs @@ -566,7 +566,7 @@ fn the_pair_path_is_really_used_in_both_directions() { let ct = enc(&mut pinned_encryptor(nonce), &plaintext); assert_eq!(dec(&mut pinned_decryptor(nonce), &ct), plaintext); - // Encryption: two blocks together must go through encrypt_blocks2, so the swapped toy differs. + // Encryption: two blocks together must go through encrypt_2blocks, so the swapped toy differs. let (mut e, _) = SwappedCtr::::do_encrypt_init_rng(&key, &mut pinned_rng(nonce)).unwrap(); let mut swapped = plaintext.clone(); diff --git a/crypto/modes/tests/ecb_tests.rs b/crypto/modes/tests/ecb_tests.rs index 73790bea..40d964fc 100644 --- a/crypto/modes/tests/ecb_tests.rs +++ b/crypto/modes/tests/ecb_tests.rs @@ -217,10 +217,10 @@ fn the_pair_path_is_used_in_both_directions() { let plaintext = [[0xA5u8; TOY_LEN], [0x5Au8; TOY_LEN]]; let ct = enc_blocks(&mut encryptor(), &plaintext); - // Encryption: a pair goes through encrypt_blocks2, so the swapped toy returns them swapped. + // Encryption: a pair goes through encrypt_2blocks, so the swapped toy returns them swapped. let (mut enc, _) = SwappedEcb::::do_encrypt_init(&key).unwrap(); let swapped_ct = enc_blocks(&mut enc, &plaintext); - assert_eq!(swapped_ct, [ct[1], ct[0]], "encrypting a pair must go through encrypt_blocks2"); + assert_eq!(swapped_ct, [ct[1], ct[0]], "encrypting a pair must go through encrypt_2blocks"); // ...and one block at a time avoids the pair path. let (mut enc, _) = SwappedEcb::::do_encrypt_init(&key).unwrap(); @@ -231,7 +231,7 @@ fn the_pair_path_is_used_in_both_directions() { assert_eq!( dec_blocks(&mut dec, &ct), [plaintext[1], plaintext[0]], - "decrypting a pair must go through decrypt_blocks2" + "decrypting a pair must go through decrypt_2blocks" ); let mut dec = SwappedEcb::::do_decrypt_init(&key, &[]).unwrap(); assert_eq!([dec_flat(&mut dec, &ct[0]), dec_flat(&mut dec, &ct[1])], plaintext); diff --git a/mem_usage_benches/bench_aes_mem_usage.rs b/mem_usage_benches/bench_aes_mem_usage.rs index 59df3bd0..8813c2a0 100644 --- a/mem_usage_benches/bench_aes_mem_usage.rs +++ b/mem_usage_benches/bench_aes_mem_usage.rs @@ -110,12 +110,12 @@ fn bench_aes256_decrypt_block() { print!("{block:x?}"); } -fn bench_aes256_encrypt_blocks2() { - eprintln!("AES_256::encrypt_blocks2"); +fn bench_aes256_encrypt_2blocks() { + eprintln!("AES_256::encrypt_2blocks"); let aes = AES_256::new(&key::<32>()).unwrap(); let mut blocks = [[0x11u8; 16], [0x22u8; 16]]; - aes.encrypt_blocks2(&mut blocks); + aes.encrypt_2blocks(&mut blocks); print!("{blocks:x?}"); } @@ -128,5 +128,5 @@ fn main() { // bench_aes128_encrypt_block() // bench_aes256_encrypt_block() // bench_aes256_decrypt_block() - // bench_aes256_encrypt_blocks2() + // bench_aes256_encrypt_2blocks() } From 2845101b67392147696174e72fd72fd03be71d17 Mon Sep 17 00:00:00 2001 From: David Hook Date: Wed, 9 Sep 2026 13:15:52 +1000 Subject: [PATCH 09/14] aes: cipher2 / inv_cipher2 after FIPS 197's CIPHER() and INVCIPHER(), a debug self-check that sub_word's eight broadcast planes agree, in-place benches with decrypt paths for every key length and key-expansion throughput, summary.md removed, and acvp_tests.rs becomes bc-test-data.rs; the rest of Mike Ounsworth's 736b0ac review that still applied --- crypto/aes/Cargo.toml | 2 +- crypto/aes/benches/aes_benches.rs | 112 ++-- crypto/aes/src/aes.rs | 12 +- crypto/aes/src/schedule.rs | 2 + crypto/aes/summary.md | 487 ------------------ .../tests/{acvp_tests.rs => bc-test-data.rs} | 4 +- crypto/aes/tests/fips197_tests.rs | 2 +- crypto/aes/tests/sp800_38a_tests.rs | 2 +- 8 files changed, 45 insertions(+), 578 deletions(-) delete mode 100644 crypto/aes/summary.md rename crypto/aes/tests/{acvp_tests.rs => bc-test-data.rs} (98%) diff --git a/crypto/aes/Cargo.toml b/crypto/aes/Cargo.toml index f1bd1678..2e2f8d68 100644 --- a/crypto/aes/Cargo.toml +++ b/crypto/aes/Cargo.toml @@ -16,7 +16,7 @@ bouncycastle-core-test-framework.workspace = true bouncycastle-hex.workspace = true bouncycastle-rng.workspace = true criterion.workspace = true -serde_json = "1.0" +serde_json = "1.0" # for parsing the bc-test-data ACVP vector files [[bench]] name = "aes_benches" diff --git a/crypto/aes/benches/aes_benches.rs b/crypto/aes/benches/aes_benches.rs index 15f42257..cf8e4af4 100644 --- a/crypto/aes/benches/aes_benches.rs +++ b/crypto/aes/benches/aes_benches.rs @@ -1,16 +1,21 @@ -//! Criterion benchmarks for the bit-sliced AES engine. +//! Criterion benchmarks for the bit-sliced AES permutation. //! //! The comparison that matters here is `encrypt_block` against `encrypt_2blocks` over the same //! number of bytes. The bit-sliced state holds two blocks, so a single-block call does twice the //! necessary work; the two-block path should be close to twice the throughput. That ratio is the //! argument for modes of operation using the two-block entry points wherever their blocks are //! independent (CTR, and the decrypt direction of CBC and CFB). +//! +//! The data benches work in place on one buffer across iterations, so a `clone` never sits inside +//! the timed closure. The permutation is a bijection, so the buffer stays random whichever +//! direction ran last, and the contents never influence the timing of a constant-time cipher. use bouncycastle_aes::{AES_128, AES_192, AES_256, BLOCK_LEN}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ElectronicCodeBook, RNG}; use bouncycastle_rng as rng; -use criterion::{Criterion, Throughput, criterion_group, criterion_main}; +use criterion::measurement::WallTime; +use criterion::{BenchmarkGroup, Criterion, Throughput, criterion_group, criterion_main}; use std::hint::black_box; /// 16 KiB of data, i.e. 1024 AES blocks. @@ -36,16 +41,19 @@ fn bench_key_expansion(c: &mut Criterion) { let mut group = c.benchmark_group("aes::key expansion"); let key128 = key::<16>(); + group.throughput(Throughput::Bytes(16)); group.bench_function("AES_128::new()", |b| { b.iter(|| black_box(AES_128::new(black_box(&key128)).unwrap())) }); let key192 = key::<24>(); + group.throughput(Throughput::Bytes(24)); group.bench_function("AES_192::new()", |b| { b.iter(|| black_box(AES_192::new(black_box(&key192)).unwrap())) }); let key256 = key::<32>(); + group.throughput(Throughput::Bytes(32)); group.bench_function("AES_256::new()", |b| { b.iter(|| black_box(AES_256::new(black_box(&key256)).unwrap())) }); @@ -53,129 +61,73 @@ fn bench_key_expansion(c: &mut Criterion) { group.finish(); } -fn bench_aes128(c: &mut Criterion) { - let aes = AES_128::new(&key::<16>()).unwrap(); - let blocks = random_blocks(); - - let mut group = c.benchmark_group("aes::AES_128"); +/// The four data benches every key length gets: 16 KiB through the one-block and two-block entry +/// points, in each direction. +fn bench_data_paths>( + group: &mut BenchmarkGroup<'_, WallTime>, + aes: &C, +) { + let mut blocks = random_blocks(); group.throughput(Throughput::Bytes(DATA_LEN as u64)); group.bench_function("16KiB -- .encrypt_block() x1024", |b| { b.iter(|| { - let mut buf = blocks.clone(); - for block in buf.iter_mut() { + for block in blocks.iter_mut() { aes.encrypt_block(black_box(block)); } - black_box(&buf); + black_box(&blocks); }) }); group.bench_function("16KiB -- .encrypt_2blocks() x512", |b| { b.iter(|| { - let mut buf = blocks.clone(); - for pair in buf.chunks_exact_mut(2) { + for pair in blocks.chunks_exact_mut(2) { // `try_into` cannot fail: `chunks_exact_mut(2)` yields slices of length 2. let pair: &mut [[u8; BLOCK_LEN]; 2] = pair.try_into().unwrap(); aes.encrypt_2blocks(black_box(pair)); } - black_box(&buf); + black_box(&blocks); }) }); group.bench_function("16KiB -- .decrypt_block() x1024", |b| { b.iter(|| { - let mut buf = blocks.clone(); - for block in buf.iter_mut() { + for block in blocks.iter_mut() { aes.decrypt_block(black_box(block)); } - black_box(&buf); + black_box(&blocks); }) }); group.bench_function("16KiB -- .decrypt_2blocks() x512", |b| { b.iter(|| { - let mut buf = blocks.clone(); - for pair in buf.chunks_exact_mut(2) { + for pair in blocks.chunks_exact_mut(2) { let pair: &mut [[u8; BLOCK_LEN]; 2] = pair.try_into().unwrap(); aes.decrypt_2blocks(black_box(pair)); } - black_box(&buf); + black_box(&blocks); }) }); +} +fn bench_aes128(c: &mut Criterion) { + let aes = AES_128::new(&key::<16>()).unwrap(); + let mut group = c.benchmark_group("aes::AES_128"); + bench_data_paths(&mut group, &aes); group.finish(); } fn bench_aes192(c: &mut Criterion) { let aes = AES_192::new(&key::<24>()).unwrap(); - let blocks = random_blocks(); - let mut group = c.benchmark_group("aes::AES_192"); - group.throughput(Throughput::Bytes(DATA_LEN as u64)); - - group.bench_function("16KiB -- .encrypt_block() x1024", |b| { - b.iter(|| { - let mut buf = blocks.clone(); - for block in buf.iter_mut() { - aes.encrypt_block(black_box(block)); - } - black_box(&buf); - }) - }); - - group.bench_function("16KiB -- .encrypt_2blocks() x512", |b| { - b.iter(|| { - let mut buf = blocks.clone(); - for pair in buf.chunks_exact_mut(2) { - let pair: &mut [[u8; BLOCK_LEN]; 2] = pair.try_into().unwrap(); - aes.encrypt_2blocks(black_box(pair)); - } - black_box(&buf); - }) - }); - + bench_data_paths(&mut group, &aes); group.finish(); } fn bench_aes256(c: &mut Criterion) { let aes = AES_256::new(&key::<32>()).unwrap(); - let blocks = random_blocks(); - let mut group = c.benchmark_group("aes::AES_256"); - group.throughput(Throughput::Bytes(DATA_LEN as u64)); - - group.bench_function("16KiB -- .encrypt_block() x1024", |b| { - b.iter(|| { - let mut buf = blocks.clone(); - for block in buf.iter_mut() { - aes.encrypt_block(black_box(block)); - } - black_box(&buf); - }) - }); - - group.bench_function("16KiB -- .encrypt_2blocks() x512", |b| { - b.iter(|| { - let mut buf = blocks.clone(); - for pair in buf.chunks_exact_mut(2) { - let pair: &mut [[u8; BLOCK_LEN]; 2] = pair.try_into().unwrap(); - aes.encrypt_2blocks(black_box(pair)); - } - black_box(&buf); - }) - }); - - group.bench_function("16KiB -- .decrypt_2blocks() x512", |b| { - b.iter(|| { - let mut buf = blocks.clone(); - for pair in buf.chunks_exact_mut(2) { - let pair: &mut [[u8; BLOCK_LEN]; 2] = pair.try_into().unwrap(); - aes.decrypt_2blocks(black_box(pair)); - } - black_box(&buf); - }) - }); - + bench_data_paths(&mut group, &aes); group.finish(); } diff --git a/crypto/aes/src/aes.rs b/crypto/aes/src/aes.rs index 32e75b81..d6dbbc42 100644 --- a/crypto/aes/src/aes.rs +++ b/crypto/aes/src/aes.rs @@ -71,7 +71,7 @@ impl AES

{ /// /// Algorithm 1 line by line: line 3 is the initial ADDROUNDKEY() with `w[0..3]`; lines 4-9 are /// the `Nr - 1` full rounds; lines 10-13 are the final round, which omits MIXCOLUMNS(). - fn encrypt2(&self, q: &mut Planes) { + fn cipher2(&self, q: &mut Planes) { // line 3: state = state XOR w[0..3] add_round_key(q, &round_key::

(&self.schedule, 0)); @@ -104,7 +104,7 @@ impl AES

{ /// /// Line by line: line 3 is ADDROUNDKEY() with the last round key; lines 4-9 are the /// `Nr - 1` full inverse rounds; lines 10-13 are the final one, which omits INVMIXCOLUMNS(). - fn decrypt2(&self, q: &mut Planes) { + fn inv_cipher2(&self, q: &mut Planes) { // line 3: state = state XOR w[4*Nr .. 4*Nr+3] add_round_key(q, &round_key::

(&self.schedule, P::NR)); @@ -133,7 +133,7 @@ impl AES

{ /// Infallible: a constructed [`AES`] is always usable and every input length is fixed. pub(crate) fn encrypt_2blocks(&self, blocks: &mut [Block; 2]) { let mut q = pack(&blocks[0], &blocks[1]); - self.encrypt2(&mut q); + self.cipher2(&mut q); let (a, b) = blocks.split_at_mut(1); unpack(&q, &mut a[0], &mut b[0]); } @@ -141,7 +141,7 @@ impl AES

{ /// Decrypts two blocks in place. See [`ElectronicCodeBook::encrypt_2blocks`]. pub(crate) fn decrypt_2blocks(&self, blocks: &mut [Block; 2]) { let mut q = pack(&blocks[0], &blocks[1]); - self.decrypt2(&mut q); + self.inv_cipher2(&mut q); let (a, b) = blocks.split_at_mut(1); unpack(&q, &mut a[0], &mut b[0]); } @@ -158,7 +158,7 @@ impl AES

{ /// half is never returned either way. pub(crate) fn encrypt_block(&self, block: &mut Block) { let mut q = pack(block, block); - self.encrypt2(&mut q); + self.cipher2(&mut q); let mut discard = [0u8; BLOCK_LEN]; unpack(&q, block, &mut discard); debug_assert_eq!(*block, discard, "the two interleaved halves must agree"); @@ -167,7 +167,7 @@ impl AES

{ /// Decrypts one block in place. See [`ElectronicCodeBook::encrypt_block`] for the two-blocks-at-once caveat. pub(crate) fn decrypt_block(&self, block: &mut Block) { let mut q = pack(block, block); - self.decrypt2(&mut q); + self.inv_cipher2(&mut q); let mut discard = [0u8; BLOCK_LEN]; unpack(&q, block, &mut discard); debug_assert_eq!(*block, discard, "the two interleaved halves must agree"); diff --git a/crypto/aes/src/schedule.rs b/crypto/aes/src/schedule.rs index 8043d75b..9559c786 100644 --- a/crypto/aes/src/schedule.rs +++ b/crypto/aes/src/schedule.rs @@ -135,6 +135,8 @@ fn sub_word(word: u32) -> u32 { ortho(&mut q); sbox(&mut q); ortho(&mut q); + // The word was broadcast into all eight planes, so all eight must carry the same answer. + debug_assert!(q.iter().all(|&plane| plane == q[0]), "the eight broadcast planes must agree"); q[0] } diff --git a/crypto/aes/summary.md b/crypto/aes/summary.md deleted file mode 100644 index 456e6893..00000000 --- a/crypto/aes/summary.md +++ /dev/null @@ -1,487 +0,0 @@ -# `crypto/aes` — implementation summary - -A constant-time, table-free AES block cipher engine (NIST FIPS 197), added on branch -`feature/officialfrancismendoza/100-AES-lightengine-CBC-mode`. - -This document is the reviewer's orientation: what was built, why the design is the way it is, what -was verified and how, and — importantly — the three places where the working plan or model recall -turned out to be wrong. For end-user documentation see the crate docs in -[`src/lib.rs`](src/lib.rs); for the reasoning behind each individual constant, see the module docs -in [`src/bitslice.rs`](src/bitslice.rs) and [`src/round.rs`](src/round.rs), which are the right -place to start reading the source. - ---- - -## 1. What this crate is (and is not) - -It provides the **raw AES keyed permutation** — `AES_128`, `AES_192`, `AES_256` — transforming exactly -16 bytes at a time. It is not something you can encrypt data with: used directly on data it *is* -ECB, which is not confidential. Modes of operation and padding are separate layers. - -Consistent with the earlier scoping decision for the AES engine, the crate deliberately ships: - -* **no CLI subcommand** — a bare permutation can only offer ECB, -* **no factory registration**, -* **no `core` cipher-trait implementations** (`BlockCipherEncryptor` / - `BlockCipherDecryptor`) — those traits are about encrypting *data* and generating initialisation - data, which are mode-of-operation concerns, -* **no `AlgorithmOID`** — NIST CSOR assigns AES OIDs per mode, never to the bare cipher. - -It does implement `core::traits::Algorithm` (name and maximum security strength), which is -metadata rather than a data-encryption API. - ---- - -## 2. Design - -### 2.1 Why there is no lookup table - -FIPS 197 Sec 5.1.1 presents the S-box as a 256-entry table (Table 4), and almost every AES -implementation stores it as one — 256 bytes, or 2–8 KiB for the "T-table" variants that fold -MixColumns in. A table indexed by a byte of the state is indexed by **secret data**, so on any CPU -with a data cache the access pattern, and therefore the timing, depends on the key. That is the -standard, repeatedly-demonstrated AES cache-timing attack, and it cannot be fixed while the lookup -remains. - -Bouncy Castle's `AESLightEngine` in the Java and C# ports keeps two 256-byte S-box tables in order -to be *small*, not to be constant-time, and leaks through both the cipher and the key schedule. - -This crate has no tables at all. The consequence worth stating plainly: **the low-memory AES and -the constant-time AES are the same implementation here.** Removing the tables is what makes it both. - -### 2.2 Bit-slicing - -The state is transposed so that each of eight `u32` words holds one *bit position* of every byte: -word `q[k]` collects bit `k` of all the bytes. In that representation the S-box becomes a fixed -Boolean circuit and one `&` or `^` applies a gate to every byte position at once. Nothing is ever -indexed by a secret and nothing branches on one. - -Eight 32-bit words hold 256 bits = 32 bytes = **two** AES blocks, so blocks are processed in pairs. -ShiftRows and MixColumns become masks and rotations in the same representation, and the key -schedule is stored already bit-sliced, so no transposition happens inside the round loop. - -### 2.3 The bit layout — derived, not assumed - -`ortho` transposes, within each byte-lane of the eight words, the 8×8 bit matrix indexed by -(word number, bit number within the lane): - -``` -after ortho: q[k] bit (8L + i) == before ortho: q[i] bit (8L + k) -``` - -`pack` loads block A as four little-endian `u32`s into the even words and block B into the odd -words, so before `ortho` byte-lane `L` of word `2c` holds `A[4c + L]`. Substituting `j = 4c + L` -and FIPS 197 Eq (3.6) `s[r,c] = in[r + 4c]` — which makes `r = j mod 4`, `c = j div 4` — gives: - -``` -q[k] bit (8r + 2c) == bit k of s[r,c] of block A -q[k] bit (8r + 2c + 1) == bit k of s[r,c] of block B -``` - -**The byte-lane of the word selects the state row `r`; the bit-pair within that lane selects the -state column `c`; the low bit of the pair is block A and the high bit is block B.** - -``` - c=0 c=1 c=2 c=3 - r=0 | 0 2 4 6 - r=1 | 8 10 12 14 (bit position of block A; - r=2 | 16 18 20 22 add 1 for block B) - r=3 | 24 26 28 30 -``` - -Everything else follows from this table: - -* **ShiftRows** only permutes within rows, and a row is a byte-lane, so it is a rotation *inside* - each byte-lane by `2r` positions (one column = two bit positions). -* **MixColumns** combines the four rows of a column, and `rotate_right(8)` moves one row, so it is - expressible with rotations by 8 and 16 plus the `{1b}` reduction, with no shuffling. - -`test_layout_matches_the_documented_table` pins this exhaustively. Every mask in the crate is only -correct relative to it, which is why it is written down rather than left implicit. - -### 2.4 Both directions from one key schedule - -Decryption follows **FIPS 197 Algorithm 3** (the straight inverse cipher), not the equivalent -inverse cipher of Sec 5.3.5. Algorithm 3 applies InvMixColumns *after* AddRoundKey, so it uses the -**unmodified** key schedule; Sec 5.3.5 reorders the round and needs a separate schedule with -InvMixColumns applied to every round key (Algorithm 5, `KEYEXPANSIONEIC()`). - -Following Algorithm 3 is what lets one `AES` value encrypt *and* decrypt from a single stored -schedule — no second copy, no transformation at construction time, no direction flag. That is the -whole reason both directions are available at 176–240 bytes of state. - -### 2.5 Typing the three key sizes - -The schedule length `4·(Nr+1)` (44/52/60 words) cannot be written as an expression over another -const generic parameter, so a params trait is used instead — the same pattern as the -`HashDRBG80090AParams_*` types in `bouncycastle-rng`: - -```rust -pub trait AESParams: AESParamsInternalTrait { - const KEY_LEN: usize; // 16 | 24 | 32 (FIPS 197 Sec 6.1) - const NK: usize; // 4 | 6 | 8 - const NR: usize; // 10 | 12 | 14 - const ALG_NAME: &'static str; - type Schedule: ZeroizablePrimitive + AsRef<[u32]> + AsMut<[u32]>; -} -``` - -`AESParams` has a **private** supertrait, so only the three types in `schedule.rs` can implement -it and no downstream crate can instantiate the cipher with an unapproved key length or round count. -(This is what `#![allow(private_bounds)]` in `lib.rs` is for.) - -The three `new` constructors and `Algorithm` impls are written out **longhand rather than with -`macro_rules!`**, because `cargo mutants` cannot see into macro bodies and a macro would hide the -key checks and security-strength constants from mutation testing. - -### 2.6 Memory - -No lookup tables, no heap allocation. The only persistent state is the key schedule, stored in a -compressed bit-sliced form: bit-slicing is a permutation of bits so it does not change the size, and -because both interleaved blocks use the same key the two halves of a bit-sliced round key are -identical, so one word of each pair is redundant. `round_key` re-doubles a single round key onto the -stack when the round loop needs it. - -| Type | Key | `Nr` | Schedule (persistent) | Tables | -|---|---|---|---|---| -| `AES_128` | 16 B | 10 | 176 B | 0 B | -| `AES_192` | 24 B | 12 | 208 B | 0 B | -| `AES_256` | 32 B | 14 | 240 B | 0 B | - -These are **measured**, not asserted — `cargo run --release -p mem_usage_benches --bin bench_aes_mem_usage` -prints exactly 176/208/240, and `test_engine_sizes_match_the_documented_memory_table` pins them so -the doc table cannot drift. - -Two things deliberately avoided: storing the doubled 8-plane schedule (352/416/480 B), and -mirroring BearSSL's `uint32_t skey[120]` 480-byte scratch buffer during expansion. `expand` writes -the classical schedule into the final array and then rewrites it in place, one round key at a time, -using eight words of stack. - -Per-call stack usage is independent of key length: 32 B of bit-sliced state for the two blocks, -32 B for the expanded round key, plus circuit temporaries that mostly stay in registers. - -### 2.7 API surface - -```rust -AES_128::new(&KeyMaterial<16>) -> Result // and 24 / 32 -aes.encrypt_block(&mut [u8; 16]) // infallible -aes.decrypt_block(&mut [u8; 16]) -aes.encrypt_2blocks(&mut [[u8; 16]; 2]) // the natural unit of work -aes.decrypt_2blocks(&mut [[u8; 16]; 2]) -``` - -No `init()`, no `reset()`, no direction flag: constructors set up state and a constructed value is -always ready. There are no one-shot statics on the permutation because -`AES_128::new(&key)?.encrypt_block(..)` already *is* the one shot; data-level one-shots belong to the -modes, which take arbitrary-length input and generate their own initialisation data. - -`encrypt_2blocks` / `decrypt_2blocks` are the pair form and roughly double throughput. A -single-block call duplicates the block into both halves and discards one result, so it does twice -the necessary work — modes whose blocks are independent (CTR, and the decrypt direction of CBC and -CFB) should prefer the pair form; CBC *encryption* cannot, since its blocks are serially dependent. - -Duplicating rather than zero-filling the unused half costs the same and buys a free self-check (the -two halves must agree, which `debug_assert` verifies). It is not a security property — the unused -half is never returned either way. - ---- - -## 3. Files - -### New crate - -| File | Lines | Contents | -|---|---|---| -| `Cargo.toml` | 18 | deps: `core`, `utils`; dev-deps: `hex`, `rng`, `criterion`, `serde_json` | -| [`src/lib.rs`](src/lib.rs) | 175 | Crate docs: Usage Examples, Design, Memory Usage, Security Considerations, Provenance | -| [`src/bitslice.rs`](src/bitslice.rs) | 210 | `ortho`, `pack`, `unpack`; the layout table and its exhaustive test | -| [`src/sbox.rs`](src/sbox.rs) | 377 | The 113-gate circuit; `inv_sbox`; Tables 4 and 6 for tests | -| [`src/round.rs`](src/round.rs) | 507 | AddRoundKey, ShiftRows, MixColumns and inverses; byte-wise references | -| [`src/schedule.rs`](src/schedule.rs) | 456 | `AESParams`, `expand` (Alg 2), `round_key`; Appendix A tables | -| [`src/aes.rs`](src/aes.rs) | 276 | `AES

`, the three aliases, Alg 1 and Alg 3, key validation | -| [`tests/fips197_tests.rs`](tests/fips197_tests.rs) | 230 | Appendix B; two-block path; key handling | -| [`tests/sp800_38a_tests.rs`](tests/sp800_38a_tests.rs) | 176 | SP 800-38A F.1.1–F.1.6 | -| [`tests/acvp_tests.rs`](tests/acvp_tests.rs) | 266 | NIST ACVP `ACVP-AES-ECB` loader | -| [`benches/aes_benches.rs`](benches/aes_benches.rs) | 183 | criterion; key expansion and 16 KiB throughput, 1-block vs 2-block | - -### Changed elsewhere - -* `Cargo.toml` — `bouncycastle-aes` in `workspace.dependencies` and in the umbrella - `[dependencies]`. -* `src/lib.rs` — `pub use bouncycastle_aes as aes;`. -* `mem_usage_benches/bench_aes_mem_usage.rs` (new, 131 lines), plus its `[[bin]]` entry in - `mem_usage_benches/Cargo.toml` and a `mod` line in `mem_usage_benches/lib.rs`. -* `alpha_0.1.3_release_notes.md` — a "Major features" entry. - ---- - -## 4. Verification - -58 tests, all passing. The strategy is that **no expected value anywhere was written from -recall** — every one is transcribed from a downloaded specification PDF or an official vector file. - -| Source | What is checked | -|---|---| -| FIPS 197 Table 4 / Table 6 | **Exhaustive**: all 256 inputs to `sbox` and `inv_sbox`. This is what makes the 113 gates trustworthy, so it must stay exhaustive. | -| FIPS 197 Sec 5.1.1 | The worked example `S[{53}] = {ed}`. | -| FIPS 197 Eq 5.5 / 5.8 / 5.12 / 5.15 | ShiftRows and MixColumns and their inverses, against byte-wise references written from the equations — plus a second literal transcription of Eq 5.8/5.15 cross-checking the matrix form. | -| FIPS 197 Sec 4.2 / Eq 4.5 | The test-only `xtimes`/`gf_mul` helpers against the Sec 4.2 worked chain and `{57}·{13} = {fe}`. | -| FIPS 197 Table 5 | `Rcon` re-derived by repeated XTIMES and compared. | -| FIPS 197 Appendix A.1/A.2/A.3 | **Every one of the 156 schedule words**, for all three key lengths. | -| FIPS 197 Appendix B | The worked AES-128 block, both directions, and via the two-block path in both slots. | -| SP 800-38A F.1.1–F.1.6 | ECB known answers, all three key lengths, both directions. | -| NIST ACVP `ACVP-AES-ECB` | **2138 cases** (AES-128: 588, AES-192: 720, AES-256: 830), each checked in *both* directions and through both the single-block and two-block paths. | - -### Why Appendix A is tested inside `src/schedule.rs` - -The key schedule is deliberately not public API (a `Secret` field). A round-trip through the cipher -**cannot** validate it: a wrong `w[i]` is used by encryption and decryption alike, so the round trip -still succeeds. The Appendix A tests therefore live in the module, where `round_key` + `ortho` -decompress the stored schedule back to classical words so every `w[i]` can be compared against the -appendix directly. `tests/fips197_tests.rs` says so explicitly, so nobody mistakes its round-trip -test for schedule validation. - -### The ACVP loader - -Vectors come from `bc-test-data` at `crypto/aes_tdes_vectors/AES/ACVP-AES-ECB.4014527.rsp.json`. -If that repository is not checked out the test prints a warning and passes, matching the ML-KEM / -ML-DSA convention — `cargo test` stays green for someone who has only cloned this repo. A -`checked > 1000` assertion guards against a silently-empty run. - -The response file records `key`, `pt` and `ct` for every case regardless of the group's declared -direction, so each is checked both ways; the request file's group metadata is not needed. - -Two details worth knowing: - -* Some AFT cases have multi-block plaintexts, so the loader iterates blocks (ECB). -* The set includes **all-zero keys** (the GFSbox-style groups). `KeyMaterial` tags an all-zero - buffer `Zeroized` and refuses to promote it outside a hazardous closure — which is the right - default, and `AES_128::new` rejecting it is itself tested. The *test* opts in via - `do_hazardous_operations`; the engine's guard was **not** weakened to accommodate NIST. - -### Only the ECB file belongs to this crate - -`bc-test-data` ships thirteen ACVP AES vector sets, one per mode. This crate consumes only -`ACVP-AES-ECB`, because that is the set that tests the permutation rather than a mode. -`ACVP-AES-CBC` is consumed by [`crypto/modes/tests/acvp_tests.rs`](../modes/tests/acvp_tests.rs) -(2150 AFT cases), `ACVP-AES-CFB128` by -[`crypto/modes/tests/acvp_cfb_tests.rs`](../modes/tests/acvp_cfb_tests.rs) and `ACVP-AES-CFB8` by -[`crypto/modes/tests/acvp_cfb8_tests.rs`](../modes/tests/acvp_cfb8_tests.rs) (2138 AFT cases -each). The remaining nine — `CBC-CS1/2/3`, `CFB1`, `OFB`, `CTR`, `KW`, `KWP`, `FF1`, `FF3-1` — are -unused because those modes are unimplemented, not because they are untested. The table in the ACVP test module's docs records which file goes where, so adding a mode -includes wiring up its file. - -### Constant-time hygiene audit - -Mechanically checked, not merely claimed: - -* **Every** indexing expression in non-test code is a literal constant (`q[0]`…`q[7]`), a loop - counter over a fixed public range, or `4*round + j` where `round` counts over the public `Nr`. - Not one index is derived from key or state bytes. -* The only branches in non-test code are on `i % Nk` and `Nk > 6` (public parameters) in the key - expansion, and on key *metadata* (type, length, security strength) once at construction. None on - key or state bytes. -* `SUBWORD()` in the key expansion goes through the same bit-sliced circuit as `SUBBYTES()`. A - table-driven "light" AES that removes the tables only from the cipher still leaks through the - schedule; this one does not. - -Caveats are stated in the crate docs rather than glossed: the compiler is not contractually obliged -to preserve straight-line codegen; the 32-byte working state is not scrubbed after a block (only the -schedule is `Secret`); and constant-time execution says nothing about power or EM side channels. - -### Gates - -* `cargo fmt --all -- --check` — clean. -* `cargo build --workspace`, `cargo test --workspace` — clean, no failures. -* `cargo doc -p bouncycastle-aes --no-deps` — **zero warnings**. -* `cargo clippy -p bouncycastle-aes --all-targets` — **zero warnings** for this crate. -* `./dev_scripts/quality_stats.sh ./crypto/aes` — `Err()` in core code: **3**, exactly the - three key rejections in `validate`. `unwrap()` in core code: 4, each a - `try_into()` on a fixed-size window of a fixed-size array with a preceding justification comment. - (Note: `cloc` and `bc` are not installed locally, so the line-count and ratio fields print 0.) - -### Mutation testing - -`cargo mutants -p bouncycastle-aes` — complete run, 32 minutes: - -``` -791 mutants tested: 762 caught, 19 missed, 10 unviable, 0 timeouts -``` - -Every one of the 19 misses was investigated. **18 are provable XOR/OR equivalences and no test can -kill them; 1 was a real coverage gap, since fixed.** - -#### The 18 equivalences - -| Count | Site | Mutation | -|---|---|---| -| 6 | `round.rs` `shift_rows` | `\|` → `^` | -| 6 | `round.rs` `inv_shift_rows` | `\|` → `^` | -| 2 | `bitslice.rs` `ortho::swap` | `\|` → `^` | -| 2 | `schedule.rs` `round_key` | `\|` → `^` | -| 1 | `schedule.rs` `expand` | `\|` → `^` | -| 1 | `sbox.rs` `sbox` (the `t37` gate) | `^` → `\|` | - -`a | b` and `a ^ b` differ only where both operands have a set bit, so wherever the operands are -provably disjoint the two are the same function and no test can distinguish them. This is the -"XOR/OR equivalences in crypto code are acceptable" category named in `CLAUDE.md`. Each site is -disjoint for a different reason: - -* **`shift_rows` / `inv_shift_rows`** — the seven masked terms have pairwise-disjoint destination - bit ranges that together cover all 32 bits. -* **`ortho::swap`** — the masks are complementary and the shift equals the field width. -* **`expand`** — the compression combines `& 0x5555_5555` with `& 0xAAAA_AAAA`, complementary masks. -* **`round_key`** — `even` occupies only even bit positions and `even << 1` only odd ones (and - conversely for `odd`). -* **`sbox`, the `t37 = t36 ^ t34` gate** — the interesting one, because it is a gate *inside* the - circuit rather than a mask combination, and because a surviving mutant there would suggest the - exhaustive Table 4 test had a hole. It does not: brute-forcing all 256 inputs shows `t36` and - `t34` are **never both 1**, so XOR and OR agree, and the mutant changes the output for 0 of 256 - inputs. Sweeping the same mutation across every XOR gate confirms `t37` is the **only one of the - 77** with that property — every other `^ → |` mutant in the circuit is killed. So the exhaustive - test is exactly as strong as claimed; this gate just happens to have disjoint operands. - -Rather than leave the `shift_rows` case as an assertion, the underlying invariant is now tested: -`test_shift_rows_is_a_bit_permutation` pushes a single set bit through and requires exactly one bit -out, with the induced map a bijection on all 32 positions — precisely the disjointness and coverage -property, and it *would* fail if a mask ever overlapped or failed to cover. Every one of the six -sites also carries an in-code comment explaining why its mutant survives, so the next reader does -not have to repeat this investigation. - -#### The one real gap, fixed - -**`< → >` in `AES

::validate`.** There was no test for a key whose security strength is *below* -the level its length implies; because `from_bytes_as_type` always tags a key at its length-implied -strength, neither `<` nor `>` was ever true and the two comparisons behaved identically. -`a_key_carrying_too_low_a_security_strength_is_rejected` now covers it (a 32-byte key lowered to -128-bit must be rejected by `AES_256::new`), and the fix was confirmed by hand-applying the mutation -and watching that test fail, then reverting. - -This mutant still appears in the run output above, which analysed the pre-fix source — the fix -landed while the run was in flight. Re-running `cargo mutants` should therefore report **18 missed, -763 caught**, all 18 being the documented equivalences. - -#### Unviable - -The 10 unviable mutants are all `replace with Err(...)` / `with ()` on functions whose return -type does not admit the substituted value (`validate`, `Debug::fmt`, `encrypt2`). `cargo mutants` -counts these as unviable rather than missed; they are a property of the config's `error_values` -list, not a coverage gap. - ---- - -## 5. Three corrections worth flagging to reviewers - -### 5.1 The working plan's bit-layout claim is wrong - -`bc-rust-aes-lowmemory-plan.md` §2 states the layout is "`q[k]` bit `2·j` is bit k of byte j of -block A". That is **false**. The correct layout, derived in §2.3 above and pinned exhaustively, is -`q[k]` bit `(8r + 2c)`. Anyone checking the ShiftRows or MixColumns constants against the plan's -version will conclude, wrongly, that they are all broken. The plan's own instruction — "Any place -BearSSL's constants and your FIPS 197 derivation disagree: the spec wins; re-derive, then look for -the misunderstanding (it will be in the layout table)" — turned out to point at the plan itself. - -### 5.2 FIPS 197 Eq 5.6 is `[{02},{01},{01},{03}]` - -Not `[{02},{03},{01},{01}]`, which is the first *row* of the Eq 5.7 matrix rather than the defining -word of Sec 4.3. Sec 4.3 Eq (4.8) defines matrix entry `(r,k)` as `a[(r-k) mod 4]`, and both -MixColumns and InvMixColumns use that same convention — Eq 5.13's `[{0e},{09},{0d},{0b}]` is -correct as printed. - -This one was written into a test constant from memory and caught by the failing test. It is worth -recording because of *how* it fails: supplying the matrix row instead of the defining word silently -transposes the matrix, which leaves the InvMixColumns test **passing**, so only the forward test -detects it. A literal transcription of Eq 5.8 and Eq 5.15 was added as a second, independent -reference (`test_the_two_reference_forms_agree`) so the convention is pinned from both directions, -and `MIX_COEFFS` carries a comment about the trap. - -### 5.3 The plan's "PR B" is unnecessary - -The plan calls for downloading CAVP AESAVS `.rsp` files and opening a PR against `bcgit/bc-test-data` -to add them. `bc-test-data` **already** ships NIST ACVP AES vectors for every mode, including -`crypto/aes_tdes_vectors/AES/ACVP-AES-ECB.4014527.{req,rsp}.json` — 2138 AFT cases across all three -key lengths, more coverage than the AESAVS KAT/MMT files would have provided. No PR to -`bc-test-data` is needed. `serde_json` as a dev-dependency is the established way to read these -files (see the ML-KEM and ML-DSA suites). - ---- - -## 6. Scope deliberately not implemented - -| Item | Why | -|---|---| -| `ElectronicCodeBook` trait impls, and `encrypt_2blocks`/`decrypt_2blocks` as trait methods | The trait does not exist in `crypto/core`, which has the mode-level `BlockCipher` / `BlockCipherEncryptor` / `BlockCipherDecryptor`. Introducing it is the plan's separate "PR A". The two-block entry points are inherent methods for now; promoting them to provided trait methods is a one-line delegation once the trait lands. | -| `core-test-framework` conformance test | Follows from the above — there is no test suite for a raw permutation yet. | -| ACVP MCT (Monte Carlo) groups — 6 cases | Their expected `resultsArray` comes from a chained key/plaintext update rule defined in the ACVP AES specification, not in FIPS 197. Implementing it from anything other than that specification would be guesswork. The test reports the skip count so the gap is visible rather than silent. | -| CLI subcommand | A bare permutation only does ECB. `aes128-cbc-*` / `-cfb-*` belong with the modes crate. | -| Factory registration | No `BlockCipherFactory` exists; not adding one here. | -| bc-java `AESLightEngine` cross-check | The plan marks it developer-local rather than committed, and 2138 ACVP vectors plus the spec appendices make it redundant. | - ---- - -## 7. Provenance and attribution - -* **Normative reference: NIST FIPS 197** (including Update 1). Every transformation cites its - section, algorithm and equation numbers, verified against a freshly downloaded copy of the PDF. -* **The S-box circuit** is the 113-gate straight-line program `SLP_AES_113.txt` from Peralta's - circuit collection — 32 AND, 77 XOR, 4 XNOR — described in J. Boyar and R. Peralta, "A new - combinational logic minimization technique with applications to cryptology", - . The gate list was transcribed **mechanically** from the - SLP file (`+` → `^`, `x` → `&`, `#` → `!(..^..)`, names unchanged apart from case) and the result - diffed against the generator output to rule out transcription error. It is not meaningful line by - line and should not be "tidied"; it is verified as a whole by the exhaustive Table 4 test. -* **The bit-sliced two-block structure**, the transpose, and the ShiftRows/MixColumns mask and - rotation constants are translated from BearSSL's `aes_ct` implementation by Thomas Pornin - (`src/symcipher/aes_ct.c`, `aes_ct_enc.c`, `aes_ct_dec.c`, `aes_ct_cbcdec.c`), **MIT licensed**. - Each constant is re-derived from the documented layout in the comments and pinned by a test - against a byte-wise reference written from the FIPS 197 equations. - -Two notes on where the sources disagree, both resolved in favour of the SLP file: - -* Its bottom linear transformation (`tc1..tc26`) **differs from** BearSSL's (`t46..t67`), and its - `t17`/`t21` are re-associated relative to BearSSL's. Both compute the same S-box. -* The SLP numbers inputs and outputs with `U0`/`S0` as the **most significant** bit, so `U0` is - plane `q[7]`. Reversing this produces a wrong S-box, not a subtly different one; the exhaustive - Table 4 test is what pins it. - -**Open question for maintainers:** how attribution for the BearSSL translation and the -Boyar–Peralta circuit should be recorded — file headers only (current state), a top-level `NOTICE` -file, or both. This is a licensing/policy call rather than a technical one. - ---- - -## 8. Reproducing the checks - -```sh -cargo build -p bouncycastle-aes -cargo test -p bouncycastle-aes # 58 tests -cargo test -p bouncycastle-aes --test acvp_tests -- --nocapture # prints the ACVP count -cargo doc -p bouncycastle-aes --no-deps # expect zero warnings -cargo clippy -p bouncycastle-aes --all-targets -cargo fmt --all -- --check -cargo bench -p bouncycastle-aes -cargo mutants -p bouncycastle-aes -./dev_scripts/quality_stats.sh ./crypto/aes - -# struct sizes; add the massif recipe in the file header for stack measurement -cargo run --release -p mem_usage_benches --bin bench_aes_mem_usage -``` - -The ACVP tests additionally need `bc-test-data` cloned as a sibling of this repository; without it -they print a warning and pass. - ---- - -## 9. Open items before merge - -1. **Decide the attribution form** for the BearSSL translation and the Boyar–Peralta circuit (§7): - file headers only (current state), a top-level `NOTICE`, or both. A licensing/policy call rather - than a technical one. -2. **Confirm the PR base branch.** The plan specifies `release/0.1.3alpha`, set explicitly — GitHub - defaults to `main`. -3. Decide whether `ElectronicCodeBook` (plan PR A) lands before or after this crate, since it - determines whether the two-block entry points become trait methods now or later (§6). -4. Note in the PR description that the plan's layout claim (§5.1) and PR B (§5.3) are superseded, so - the plan document does not mislead the next reader. -5. Optionally re-run `cargo mutants` to confirm the expected 18 missed / 763 caught (§4). The 19th - miss was fixed while the recorded run was in flight, so the numbers above under-report by one. diff --git a/crypto/aes/tests/acvp_tests.rs b/crypto/aes/tests/bc-test-data.rs similarity index 98% rename from crypto/aes/tests/acvp_tests.rs rename to crypto/aes/tests/bc-test-data.rs index 1c9ae315..c94df200 100644 --- a/crypto/aes/tests/acvp_tests.rs +++ b/crypto/aes/tests/bc-test-data.rs @@ -23,7 +23,7 @@ //! | `ACVP-AES-CFB128` | `crypto/modes/tests/acvp_cfb_tests.rs` | //! | `ACVP-AES-CFB8` | `crypto/modes/tests/acvp_cfb8_tests.rs` | //! | `ACVP-AES-OFB` | nothing yet (OFB is unimplemented) | -//! | `ACVP-AES-CTR` | nothing yet (CTR is unimplemented) | +//! | `ACVP-AES-CTR` | `crypto/modes/tests/acvp_ctr_tests.rs` | //! | `ACVP-AES-KW` / `-KWP` | nothing yet (key wrap is unimplemented) | //! | `ACVP-AES-FF1` / `-FF3-1` | nothing yet (format-preserving encryption is unimplemented) | //! @@ -62,7 +62,7 @@ const TEST_DATA_PATHS: [&str; 2] = [ const RESPONSE_FILE: &str = "ACVP-AES-ECB.4014527.rsp.json"; -/// Locates the ACVP AES directory, or `None` if `bc-test-data` is not checked out. +/// Locates the AES directory of `bc-test-data`, or `None` if that repository is not checked out. fn test_data_dir() -> Option { for candidate in TEST_DATA_PATHS { let path = Path::new(candidate); diff --git a/crypto/aes/tests/fips197_tests.rs b/crypto/aes/tests/fips197_tests.rs index 7e626668..f9353218 100644 --- a/crypto/aes/tests/fips197_tests.rs +++ b/crypto/aes/tests/fips197_tests.rs @@ -10,7 +10,7 @@ //! `src/schedule.rs`, where the stored schedule can be decompressed and compared directly. //! //! Known-answer coverage for AES-192 and AES-256, which Appendix B does not reach, is in -//! `sp800_38a_tests.rs` and `acvp_tests.rs`. +//! `sp800_38a_tests.rs` and `bc-test-data.rs`. //! //! All values here are transcribed from the published FIPS 197 (Update 1) PDF. diff --git a/crypto/aes/tests/sp800_38a_tests.rs b/crypto/aes/tests/sp800_38a_tests.rs index 1fd42cf1..f8afa817 100644 --- a/crypto/aes/tests/sp800_38a_tests.rs +++ b/crypto/aes/tests/sp800_38a_tests.rs @@ -3,7 +3,7 @@ //! These are the only NIST-published known-answer vectors for AES-192 and AES-256 that live in a //! specification document rather than a separate vector file -- FIPS 197 Appendix B only covers //! AES-128, and FIPS 197 (Update 1) removed the Appendix C example vectors in favour of a pointer -//! to the CSRC website. `acvp_tests.rs` covers far more cases, but only when the `bc-test-data` +//! to the CSRC website. `bc-test-data.rs` covers far more cases, but only when the `bc-test-data` //! repository is present, so these vectors are the always-available known-answer floor. //! //! ECB applies the raw permutation to each block independently, so an ECB example vector *is* a From 69cfe5b72e082fa50307f88698a5889a84cae7d6 Mon Sep 17 00:00:00 2001 From: David Hook Date: Wed, 9 Sep 2026 13:23:40 +1000 Subject: [PATCH 10/14] core: ElectronicCodeBook's eight-block methods are encrypt_8blocks / decrypt_8blocks (were *_blocks8), so they read like the pair methods; modes, the framework suite, benches and notes follow --- alpha_0.1.3_release_notes.md | 10 +++++----- .../src/electronic_code_book.rs | 16 ++++++++-------- crypto/core/src/traits.rs | 6 +++--- crypto/modes/benches/modes_benches.rs | 8 ++++---- crypto/modes/src/cbc.rs | 8 ++++---- crypto/modes/src/cfb.rs | 8 ++++---- crypto/modes/src/cfb8.rs | 4 ++-- crypto/modes/src/ctr.rs | 4 ++-- crypto/modes/src/ecb.rs | 6 +++--- crypto/modes/tests/acvp_cfb8_tests.rs | 4 ++-- crypto/modes/tests/acvp_cfb_tests.rs | 4 ++-- crypto/modes/tests/cbc_tests.rs | 4 ++-- crypto/modes/tests/cfb8_tests.rs | 10 +++++----- crypto/modes/tests/cfb_tests.rs | 10 +++++----- crypto/modes/tests/common/mod.rs | 16 ++++++++-------- crypto/modes/tests/ctr_tests.rs | 2 +- crypto/modes/tests/ecb_tests.rs | 4 ++-- 17 files changed, 62 insertions(+), 62 deletions(-) diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index ccdd6df8..1263d6cd 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -74,7 +74,7 @@ only OFB outstanding. Re-exported from the umbrella crate. `P1 XOR P1'` outright rather than merely whether the blocks were equal. * **Parallel decryption.** Sec 6.2 notes CBC decryption's inverse cipher calls can run in parallel, so `do_decrypt_blocks` walks the ciphertext in eights through - `ElectronicCodeBook::decrypt_blocks8`, then pairs through `decrypt_2blocks`, then a one-block + `ElectronicCodeBook::decrypt_8blocks`, then pairs through `decrypt_2blocks`, then a one-block remainder. A toy permutation that rotates its eight results proves the eight path is taken, and only for full eights. Measured against an otherwise identical permutation that does not override the pair methods, this is **1.83x** the @@ -123,7 +123,7 @@ CFB128 (`Cfb`), SP 800-38A Sec 6.3 with `s = b`: `Cfb<_, Decrypting, _, _>` never calls `decrypt_block` or `decrypt_2blocks`. This is pinned by a test permutation whose inverse methods panic, run over both the pair and single-block paths -- so the claim is enforced rather than merely documented. -* **Parallel decryption**, via `encrypt_blocks8` / `encrypt_2blocks` (eights, then pairs, then a single block, like CBC): Sec 6.3 notes CFB decryption's forward cipher +* **Parallel decryption**, via `encrypt_8blocks` / `encrypt_2blocks` (eights, then pairs, then a single block, like CBC): Sec 6.3 notes CFB decryption's forward cipher calls "can be performed in parallel if the input blocks are first constructed (in series) from the IV and the ciphertext", and with `s = b` those input blocks simply *are* the IV followed by the ciphertext. Re-measured after the stream-cipher rewrite: against an otherwise identical @@ -193,7 +193,7 @@ CFB8 (`Cfb8`), SP 800-38A Sec 6.3 with `s = 8`: CFB8. * **Decryption still batches.** Sec 6.3's parallel decryption applies: the successive register states depend only on the IV and the ciphertext, so they are built in series -- byte shuffling, - no cipher calls -- and the forward ciphers then run eight at a time through `encrypt_blocks8`, + no cipher calls -- and the forward ciphers then run eight at a time through `encrypt_8blocks`, then in pairs. Measured **1.94x** the throughput of the same decryption in 1-byte calls, which never batch (6.61 vs 3.40 MiB/s). Encryption cannot batch and does not. * **Decryption never calls the inverse cipher**, as in CFB128, pinned by the same test permutation @@ -247,7 +247,7 @@ CTR (`Ctr`), SP 800-38A Sec 6.5: * **Both directions are parallel**, the only mode here of which that is true. Sec 6.5: "In both CTR encryption and CTR decryption, the forward cipher functions can be performed in parallel." Counter blocks depend on nothing but the nonce and the index, so encryption batches through - `encrypt_blocks8` / `encrypt_2blocks` exactly as decryption does, and encryption and decryption are + `encrypt_8blocks` / `encrypt_2blocks` exactly as decryption does, and encryption and decryption are the same operation. Only the forward cipher function is ever used, as in the CFB modes. * The keystream block is the one buffer in this crate wrapped in `Secret`: a call may end part-way through a block and the remainder is kept for the next one, and unlike a chaining value that @@ -367,7 +367,7 @@ ECB (`Ecb`), SP 800-38A Sec 6.1: `core`: new `ElectronicCodeBook` trait (`crypto/core/src/traits.rs`), the raw keyed permutation -- `CIPH_K` / `CIPH^-1_K` of SP 800-38A Sec 5.1 -- that a mode is built on. `new`, `encrypt_block`, `decrypt_block`, plus provided `encrypt_2blocks` / `decrypt_2blocks` that -default to two single-block calls and `encrypt_blocks8` / `decrypt_blocks8` that default to four pair +default to two single-block calls and `encrypt_8blocks` / `decrypt_8blocks` that default to four pair calls, all of which bit-sliced implementations override (AES the pair form, SM4 both). The block methods are infallible; only `new` can fail, and only on the key. `bouncycastle-aes` implements it for all three key lengths (the data-encryption traits are still deliberately not implemented diff --git a/crypto/core-test-framework/src/electronic_code_book.rs b/crypto/core-test-framework/src/electronic_code_book.rs index 214d1e5d..64fa4ad5 100644 --- a/crypto/core-test-framework/src/electronic_code_book.rs +++ b/crypto/core-test-framework/src/electronic_code_book.rs @@ -34,7 +34,7 @@ impl TestFrameworkElectronicCodeBook { /// likewise for `decrypt_2blocks` -- this is what pins an override to the default's /// semantics, and it is the reason the pair methods are worth having in the trait at all; /// * the pair methods round-trip each other; - /// * `encrypt_blocks8` / `decrypt_blocks8` likewise agree with eight single-block calls in + /// * `encrypt_8blocks` / `decrypt_8blocks` likewise agree with eight single-block calls in /// order, and round-trip each other; /// * a key of the wrong [`KeyType`] is rejected; /// * the security-strength policy matches [`Algorithm::MAX_SECURITY_STRENGTH`]. @@ -123,21 +123,21 @@ impl TestFrameworkElectronicCodeBook { perm.encrypt_block(block); } let mut batched = *eight; - perm.encrypt_blocks8(&mut batched); - assert_eq!(batched, singly, "encrypt_blocks8 must match eight encrypt_block calls"); + perm.encrypt_8blocks(&mut batched); + assert_eq!(batched, singly, "encrypt_8blocks must match eight encrypt_block calls"); let mut singly = *eight; for block in singly.iter_mut() { perm.decrypt_block(block); } let mut batched = *eight; - perm.decrypt_blocks8(&mut batched); - assert_eq!(batched, singly, "decrypt_blocks8 must match eight decrypt_block calls"); + perm.decrypt_8blocks(&mut batched); + assert_eq!(batched, singly, "decrypt_8blocks must match eight decrypt_block calls"); let mut buf = *eight; - perm.encrypt_blocks8(&mut buf); - perm.decrypt_blocks8(&mut buf); - assert_eq!(buf, *eight, "decrypt_blocks8 must invert encrypt_blocks8"); + perm.encrypt_8blocks(&mut buf); + perm.decrypt_8blocks(&mut buf); + assert_eq!(buf, *eight, "decrypt_8blocks must invert encrypt_8blocks"); } // A pair of *identical* blocks must give a pair of identical outputs. This catches an diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 41d4d4ab..bdabf825 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -384,7 +384,7 @@ pub trait ElectronicCodeBook: /// /// Modes with parallel structure chunk their data into eights first, then pairs, then single /// blocks; see CBC decryption in `bouncycastle-modes`. - fn encrypt_blocks8(&self, blocks: &mut [[u8; BLOCK_LEN]; 8]) { + fn encrypt_8blocks(&self, blocks: &mut [[u8; BLOCK_LEN]; 8]) { // Eight is a multiple of two, so the remainder is empty. let (pairs, _) = blocks.as_mut_slice().as_chunks_mut::<2>(); for pair in pairs { @@ -393,8 +393,8 @@ pub trait ElectronicCodeBook: } /// The inverse cipher function on eight *independent* blocks, in place. - /// See [`ElectronicCodeBook::encrypt_blocks8`]. - fn decrypt_blocks8(&self, blocks: &mut [[u8; BLOCK_LEN]; 8]) { + /// See [`ElectronicCodeBook::encrypt_8blocks`]. + fn decrypt_8blocks(&self, blocks: &mut [[u8; BLOCK_LEN]; 8]) { let (pairs, _) = blocks.as_mut_slice().as_chunks_mut::<2>(); for pair in pairs { self.decrypt_2blocks(pair); diff --git a/crypto/modes/benches/modes_benches.rs b/crypto/modes/benches/modes_benches.rs index c6664b37..16070e00 100644 --- a/crypto/modes/benches/modes_benches.rs +++ b/crypto/modes/benches/modes_benches.rs @@ -4,8 +4,8 @@ //! CBC and CFB is serial by construction (SP 800-38A Sec 6.2 and Sec 6.3: each forward cipher input //! depends on the previous output), so it can only ever use the single-block path. *Decryption* in //! both is parallel, and this implementation hands blocks to the permutation's batch methods -- -//! eights first, then pairs, then the remainder singly: for CBC that is `decrypt_blocks8` / -//! `decrypt_2blocks`, for CFB it is `encrypt_blocks8` / `encrypt_2blocks`, since CFB uses the +//! eights first, then pairs, then the remainder singly: for CBC that is `decrypt_8blocks` / +//! `decrypt_2blocks`, for CFB it is `encrypt_8blocks` / `encrypt_2blocks`, since CFB uses the //! forward function in both directions. AES overrides only the pair form, so its eights are four //! pairs. With the bit-sliced AES, whose two-block path costs barely more than one block, //! decryption should therefore run at roughly twice the throughput of encryption. That gap is the @@ -368,7 +368,7 @@ fn bench_cfb_aes128(c: &mut Criterion) { }); } - // ---- decryption: parallel, and uses `encrypt_blocks8` / `encrypt_2blocks` -- the FORWARD + // ---- decryption: parallel, and uses `encrypt_8blocks` / `encrypt_2blocks` -- the FORWARD // batch methods ---- let (mut enc, iv) = Aes128Cfb::::do_encrypt_init(&k).unwrap(); let mut ciphertext = flat.clone(); @@ -485,7 +485,7 @@ fn bench_cfb_aes256(c: &mut Criterion) { /// CFB8: one forward cipher per byte, so ~1/16 of CFB's throughput on a 16-byte block. /// /// Encryption is strictly serial. Decryption builds its input blocks in series and then runs them -/// through `encrypt_blocks8` / `encrypt_2blocks` (SP 800-38A Sec 6.3's parallel decryption), so it +/// through `encrypt_8blocks` / `encrypt_2blocks` (SP 800-38A Sec 6.3's parallel decryption), so it /// should be substantially faster than encryption -- the same batch effect CBC and CFB show, at /// byte granularity. fn bench_cfb8_aes128(c: &mut Criterion) { diff --git a/crypto/modes/src/cbc.rs b/crypto/modes/src/cbc.rs index 9ad9e402..abf6fec9 100644 --- a/crypto/modes/src/cbc.rs +++ b/crypto/modes/src/cbc.rs @@ -27,7 +27,7 @@ //! the forward cipher operations cannot be performed in parallel". //! //! This implementation uses that: decryption walks the ciphertext eight blocks at a time through -//! [`ElectronicCodeBook::decrypt_blocks8`], then any remaining pair through +//! [`ElectronicCodeBook::decrypt_8blocks`], then any remaining pair through //! [`ElectronicCodeBook::decrypt_2blocks`], then the last block singly. A bit-sliced engine //! computes a pair (AES) or eight blocks (SM4) for barely more than the cost of one. Encryption //! cannot, and does not. @@ -123,7 +123,7 @@ where self.chain = cj1; } - /// Decrypts eight consecutive blocks with one [`ElectronicCodeBook::decrypt_blocks8`] call. + /// Decrypts eight consecutive blocks with one [`ElectronicCodeBook::decrypt_8blocks`] call. /// /// The same argument as [`Self::decrypt_pair`], eight wide: `Pj+k = CIPH^-1_K(Cj+k) XOR Cj+k-1` /// for `k = 0..8`, with `Cj-1` the incoming chaining value. No inverse cipher depends on @@ -133,7 +133,7 @@ where #[inline] fn decrypt_eight(&mut self, blocks: &mut [[u8; BLOCK_LEN]; 8]) { let cts = *blocks; - self.perm.decrypt_blocks8(blocks); + self.perm.decrypt_8blocks(blocks); let mut prev = self.chain; for (pj, cj) in blocks.iter_mut().zip(cts.iter()) { @@ -213,7 +213,7 @@ where /// The implementor hook (the flat `do_decrypt` is provided over it). /// - /// Walks the input in eights through `decrypt_blocks8`, then pairs through `decrypt_2blocks`, + /// Walks the input in eights through `decrypt_8blocks`, then pairs through `decrypt_2blocks`, /// then the at-most-one block left over: Sec 6.2's parallelism, in the units the permutation /// offers. `as_chunks_mut` splits into exactly those shapes with no runtime length check and no /// indexing arithmetic. Never fails: CBC has no per-IV data limit. diff --git a/crypto/modes/src/cfb.rs b/crypto/modes/src/cfb.rs index 40e9473b..29f51203 100644 --- a/crypto/modes/src/cfb.rs +++ b/crypto/modes/src/cfb.rs @@ -101,7 +101,7 @@ //! applied to each input block to produce the output blocks." //! //! So [`Cfb`](Cfb) never calls [`ElectronicCodeBook::decrypt_block`], -//! [`ElectronicCodeBook::decrypt_2blocks`] or [`ElectronicCodeBook::decrypt_blocks8`]. A +//! [`ElectronicCodeBook::decrypt_2blocks`] or [`ElectronicCodeBook::decrypt_8blocks`]. A //! permutation could implement only the forward direction and still work here; `cfb_tests.rs` pins //! that with a toy whose inverse panics. The mode XORs a keystream in both directions, and the two //! directions differ only in which of the two values -- the byte that came in, or the byte that @@ -117,7 +117,7 @@ //! //! Constructing them "in series" is trivial here: with `s = b` the input blocks *are* the IV //! followed by the ciphertext blocks, already in hand. Decryption therefore walks the -//! block-aligned part of the data in eights through [`ElectronicCodeBook::encrypt_blocks8`] and +//! block-aligned part of the data in eights through [`ElectronicCodeBook::encrypt_8blocks`] and //! pairs through [`ElectronicCodeBook::encrypt_2blocks`], which a bit-sliced engine computes for //! barely more than the cost of one block. Encryption cannot, and does not. Only the bytes that //! complete an open segment, and the bytes that open the final short one, go singly. @@ -285,7 +285,7 @@ where } } - /// Decrypts eight consecutive blocks with one [`ElectronicCodeBook::encrypt_blocks8`] call. + /// Decrypts eight consecutive blocks with one [`ElectronicCodeBook::encrypt_8blocks`] call. /// /// The same construction as [`Self::decrypt_pair`] widened to eight: the input blocks are the /// incoming input block followed by the first seven ciphertext blocks, all known before any @@ -296,7 +296,7 @@ where debug_assert_eq!(self.used, BLOCK_LEN, "the block path needs a segment boundary"); let mut o = [self.buf, blocks[0], blocks[1], blocks[2], blocks[3], blocks[4], blocks[5], blocks[6]]; - self.perm.encrypt_blocks8(&mut o); + self.perm.encrypt_8blocks(&mut o); self.buf = blocks[7]; for (block, o) in blocks.iter_mut().zip(o.iter()) { for (b, o) in block.iter_mut().zip(o.iter()) { diff --git a/crypto/modes/src/cfb8.rs b/crypto/modes/src/cfb8.rs index 545d2491..ae8c7571 100644 --- a/crypto/modes/src/cfb8.rs +++ b/crypto/modes/src/cfb8.rs @@ -76,7 +76,7 @@ //! Decryption knows every ciphertext byte before it starts, so it can build the shift register's //! successive states in series -- byte shuffling, no cipher calls -- and then run the forward //! ciphers together. This implementation does exactly that, in eights through -//! [`ElectronicCodeBook::encrypt_blocks8`] and then pairs through +//! [`ElectronicCodeBook::encrypt_8blocks`] and then pairs through //! [`ElectronicCodeBook::encrypt_2blocks`], which is where a bit-sliced engine earns back a large //! part of what the mode costs. Encryption cannot: `Ij` needs `C_{j-1}`, which is the output of the //! previous cipher call. @@ -259,7 +259,7 @@ where fn do_decrypt(&mut self, data: &mut [u8]) -> Result<(), SymmetricCipherError> { let (eights, rest) = data.as_chunks_mut::<8>(); for eight in eights.iter_mut() { - self.decrypt_batch(eight, P::encrypt_blocks8); + self.decrypt_batch(eight, P::encrypt_8blocks); } let (pairs, tail) = rest.as_chunks_mut::<2>(); for pair in pairs.iter_mut() { diff --git a/crypto/modes/src/ctr.rs b/crypto/modes/src/ctr.rs index 4cd3c12e..004e0f3a 100644 --- a/crypto/modes/src/ctr.rs +++ b/crypto/modes/src/ctr.rs @@ -92,7 +92,7 @@ //! Sec 6.5: "In both CTR encryption and CTR decryption, the forward cipher functions can be //! performed in parallel". Counter blocks depend on nothing but the nonce and the index, so unlike //! CBC and CFB there is no serial direction at all: **both** directions walk the block-aligned part -//! of the data in eights through [`ElectronicCodeBook::encrypt_blocks8`], then in pairs through +//! of the data in eights through [`ElectronicCodeBook::encrypt_8blocks`], then in pairs through //! [`ElectronicCodeBook::encrypt_2blocks`]. Only the bytes that finish a partially-used keystream //! block, and the short tail at the end, go one block at a time. //! @@ -369,7 +369,7 @@ where let (blocks, tail) = rest.as_chunks_mut::(); let (eights, rest_blocks) = blocks.as_chunks_mut::<8>(); for eight in eights.iter_mut() { - self.apply_batch(eight, P::encrypt_blocks8); + self.apply_batch(eight, P::encrypt_8blocks); } let (pairs, single) = rest_blocks.as_chunks_mut::<2>(); for pair in pairs.iter_mut() { diff --git a/crypto/modes/src/ecb.rs b/crypto/modes/src/ecb.rs index d986e4f3..26be2324 100644 --- a/crypto/modes/src/ecb.rs +++ b/crypto/modes/src/ecb.rs @@ -41,7 +41,7 @@ //! Sec 6.1: "In ECB encryption and ECB decryption, multiple forward cipher functions and inverse //! cipher functions can be computed in parallel." Unlike CBC and CFB, whose encryption is serial, //! both directions here batch through the permutation's eight-block and pair methods -//! ([`ElectronicCodeBook::encrypt_blocks8`] / [`ElectronicCodeBook::encrypt_2blocks`] and their +//! ([`ElectronicCodeBook::encrypt_8blocks`] / [`ElectronicCodeBook::encrypt_2blocks`] and their //! inverses), then finish the remaining block singly. use crate::{Decrypting, Encrypting}; @@ -136,7 +136,7 @@ where ) -> Result<(), SymmetricCipherError> { let (eights, rest) = blocks.as_chunks_mut::<8>(); for eight in eights.iter_mut() { - self.perm.encrypt_blocks8(eight); + self.perm.encrypt_8blocks(eight); } let (pairs, tail) = rest.as_chunks_mut::<2>(); for pair in pairs.iter_mut() { @@ -172,7 +172,7 @@ where ) -> Result<(), SymmetricCipherError> { let (eights, rest) = blocks.as_chunks_mut::<8>(); for eight in eights.iter_mut() { - self.perm.decrypt_blocks8(eight); + self.perm.decrypt_8blocks(eight); } let (pairs, tail) = rest.as_chunks_mut::<2>(); for pair in pairs.iter_mut() { diff --git a/crypto/modes/tests/acvp_cfb8_tests.rs b/crypto/modes/tests/acvp_cfb8_tests.rs index 8ecb55e1..944581bd 100644 --- a/crypto/modes/tests/acvp_cfb8_tests.rs +++ b/crypto/modes/tests/acvp_cfb8_tests.rs @@ -23,7 +23,7 @@ //! that reach the batch paths. Every case is run **four times**: as one call over the whole //! payload, byte by byte, in 8-byte calls, and in 3-byte calls that never line up with the //! 8-byte batch. Between them those put the multi-byte cases through -//! [`ElectronicCodeBook::encrypt_blocks8`] and [`ElectronicCodeBook::encrypt_2blocks`] -- the +//! [`ElectronicCodeBook::encrypt_8blocks`] and [`ElectronicCodeBook::encrypt_2blocks`] -- the //! *forward* function, even on the decrypt side -- and through the single-byte path, with the //! shift register carried across calls at every alignment. So all of that is exercised against real //! vectors and not only against the toys in `cfb8_tests.rs`. @@ -100,7 +100,7 @@ enum Grouping { Whole, /// One byte per call. Never batches. Bytes, - /// Eight bytes per call: every call is exactly one `encrypt_blocks8` batch. + /// Eight bytes per call: every call is exactly one `encrypt_8blocks` batch. Eights, /// Three bytes per call, so no call lines up with the 8-byte batch and the shift register has /// to carry across calls at every alignment. diff --git a/crypto/modes/tests/acvp_cfb_tests.rs b/crypto/modes/tests/acvp_cfb_tests.rs index 3f389964..5bec885f 100644 --- a/crypto/modes/tests/acvp_cfb_tests.rs +++ b/crypto/modes/tests/acvp_cfb_tests.rs @@ -25,7 +25,7 @@ //! block, in pairs with a one-block remainder for odd lengths, as one call over the whole payload, //! and in 5-byte calls that never line up with a block. The second and third passes are what put //! the multi-block cases through the pair and eight-block paths -- which for CFB are -//! [`ElectronicCodeBook::encrypt_2blocks`] and [`ElectronicCodeBook::encrypt_blocks8`], the +//! [`ElectronicCodeBook::encrypt_2blocks`] and [`ElectronicCodeBook::encrypt_8blocks`], the //! *forward* function, even on the decrypt side -- and the fourth is what puts them through the //! byte path with segments left open between calls. So all of that is exercised against real //! vectors and not only against the toys in `cfb_tests.rs`. Every ACVP CFB128 payload is a whole @@ -105,7 +105,7 @@ enum Grouping { /// Two blocks per call, with a one-block remainder for odd lengths. Uses the pair path. Pairs, /// The whole payload in one call: eights, then pairs, then the remaining block. The cases - /// spanning 8 to 10 blocks are the ones that reach `encrypt_blocks8`. + /// spanning 8 to 10 blocks are the ones that reach `encrypt_8blocks`. Whole, /// Five bytes per call, so every call but the first starts mid-segment and none is a whole /// block: the byte path, with the unused keystream carried between calls. diff --git a/crypto/modes/tests/cbc_tests.rs b/crypto/modes/tests/cbc_tests.rs index 361ebd4b..cce5dbcc 100644 --- a/crypto/modes/tests/cbc_tests.rs +++ b/crypto/modes/tests/cbc_tests.rs @@ -189,7 +189,7 @@ fn the_pair_path_is_really_used() { /// The eight-block path in `do_decrypt_blocks` must actually be taken, and only for full eights. /// /// [`SwappedEightToy`] returns its eight results rotated while its pair and single-block methods -/// are correct. So a CBC decryptor that uses `decrypt_blocks8` gives the wrong answer for eight +/// are correct. So a CBC decryptor that uses `decrypt_8blocks` gives the wrong answer for eight /// blocks handed over together, and the right answer for the same eight blocks handed over as /// two fours (pairs) or one at a time. Nine blocks are wrong too: eight, then one. #[test] @@ -212,7 +212,7 @@ fn the_eight_block_path_is_really_used() { assert_ne!( dec_blocks(&mut dec, &ct), plaintext, - "eight blocks must go through decrypt_blocks8" + "eight blocks must go through decrypt_8blocks" ); // Exactly eight together is wrong for the same reason. diff --git a/crypto/modes/tests/cfb8_tests.rs b/crypto/modes/tests/cfb8_tests.rs index c4560467..058287ed 100644 --- a/crypto/modes/tests/cfb8_tests.rs +++ b/crypto/modes/tests/cfb8_tests.rs @@ -249,7 +249,7 @@ fn the_ciphertext_of_a_prefix_is_a_prefix_of_the_ciphertext() { /// SP 800-38A Sec 6.3: "The *forward cipher* function is applied to each input block to produce the /// output blocks" -- in CFB *decryption* as well as encryption. /// -/// [`ForwardOnlyToy`] panics from `decrypt_block`, `decrypt_2blocks` and `decrypt_blocks8`, so this +/// [`ForwardOnlyToy`] panics from `decrypt_block`, `decrypt_2blocks` and `decrypt_8blocks`, so this /// test fails loudly if either direction of the mode ever reaches the inverse cipher. Every decrypt /// path is exercised -- eights, pairs and single bytes -- and the result is required to agree with /// the plain [`Toy`], otherwise the test could pass by not really encrypting anything. @@ -422,7 +422,7 @@ fn aes_chunking_matches_a_single_call() { /// is correct. CFB8 decryption batches through `encrypt_2blocks`, so with this permutation six /// bytes handed over together come out wrong while the same bytes one at a time come out right. /// -/// Six, not eight: the trait's default `encrypt_blocks8` is four `encrypt_2blocks` calls, so eight +/// Six, not eight: the trait's default `encrypt_8blocks` is four `encrypt_2blocks` calls, so eight /// bytes would also be wrong and would not distinguish the two paths. #[test] fn the_pair_path_is_really_used() { @@ -450,7 +450,7 @@ fn the_pair_path_is_really_used() { /// The eight-byte batch path in `do_decrypt` must actually be taken, and only for full eights. /// -/// [`SwappedEightToy`] returns its eight `encrypt_blocks8` results rotated while its pair and +/// [`SwappedEightToy`] returns its eight `encrypt_8blocks` results rotated while its pair and /// single-block methods are correct. So nine bytes handed over together decrypt wrongly (eight /// batched, then one), while six bytes (pairs) or one at a time decrypt correctly. #[test] @@ -468,9 +468,9 @@ fn the_eight_byte_path_is_really_used() { assert_eq!(enc(&mut e, &plaintext), ct, "CFB8 encryption must not use the eight path"); // ...but nine bytes together must now be wrong, because the first eight go through - // encrypt_blocks8. + // encrypt_8blocks. let mut d = SwappedEightCfb8::::do_decrypt_init(&key, &iv).unwrap(); - assert_ne!(dec(&mut d, &ct), plaintext, "nine bytes must go through encrypt_blocks8"); + assert_ne!(dec(&mut d, &ct), plaintext, "nine bytes must go through encrypt_8blocks"); // Six bytes use the pair path only, so they are correct even for this toy... let six = &ct[..6]; diff --git a/crypto/modes/tests/cfb_tests.rs b/crypto/modes/tests/cfb_tests.rs index ff68a563..7a0ab993 100644 --- a/crypto/modes/tests/cfb_tests.rs +++ b/crypto/modes/tests/cfb_tests.rs @@ -264,7 +264,7 @@ fn the_ciphertext_of_a_prefix_is_a_prefix_of_the_ciphertext() { /// SP 800-38A Sec 6.3: "The *forward cipher* function is applied to each input block to produce the /// output blocks" -- in CFB *decryption* as well as encryption. /// -/// [`ForwardOnlyToy`] panics from `decrypt_block`, `decrypt_2blocks` and `decrypt_blocks8`, so this +/// [`ForwardOnlyToy`] panics from `decrypt_block`, `decrypt_2blocks` and `decrypt_8blocks`, so this /// test fails loudly if either direction of the mode ever reaches the inverse cipher. Every /// decrypt path is exercised -- the eight-block, pair, single-block and byte paths -- and the result /// is required to agree with the plain [`Toy`], otherwise the test could pass by not really @@ -488,9 +488,9 @@ fn the_pair_path_is_really_used() { /// The eight-block path in `do_decrypt` must actually be taken, and only for full eights. /// -/// [`SwappedEightToy`] returns its eight `encrypt_blocks8` results rotated while its pair and +/// [`SwappedEightToy`] returns its eight `encrypt_8blocks` results rotated while its pair and /// single-block methods are correct. CFB decryption batches eights through the *forward* -/// `encrypt_blocks8`, so with this permutation nine blocks handed over together decrypt wrongly +/// `encrypt_8blocks`, so with this permutation nine blocks handed over together decrypt wrongly /// (eight rotated, then one), while the same blocks handed over as two fours (pairs) or one at a /// time decrypt correctly. Encryption is serial and never batches, so it is unaffected. #[test] @@ -509,9 +509,9 @@ fn the_eight_block_path_is_really_used() { assert_eq!(enc(&mut e, &plaintext), ct, "CFB encryption must not use the eight path"); // ...but nine blocks together must now be wrong, because the first eight go through - // encrypt_blocks8. + // encrypt_8blocks. let mut d = SwappedEightCfb::::do_decrypt_init(&key, &iv).unwrap(); - assert_ne!(dec(&mut d, &ct), plaintext, "nine blocks must go through encrypt_blocks8"); + assert_ne!(dec(&mut d, &ct), plaintext, "nine blocks must go through encrypt_8blocks"); // Two fours use the pair path only, so they are correct even for this toy... let mut d = SwappedEightCfb::::do_decrypt_init(&key, &iv).unwrap(); diff --git a/crypto/modes/tests/common/mod.rs b/crypto/modes/tests/common/mod.rs index da198150..93195856 100644 --- a/crypto/modes/tests/common/mod.rs +++ b/crypto/modes/tests/common/mod.rs @@ -123,7 +123,7 @@ impl ElectronicCodeBook for SwappedPairToy { /// A toy whose **inverse cipher function panics**. /// /// SP 800-38A Sec 6.3 applies the forward cipher function in both directions of CFB, so a correct -/// `Cfb` never touches `decrypt_block`, `decrypt_2blocks` or `decrypt_blocks8`. Running a full CFB round trip over this +/// `Cfb` never touches `decrypt_block`, `decrypt_2blocks` or `decrypt_8blocks`. Running a full CFB round trip over this /// permutation turns that claim into a test: if either decryption entry point is ever reached, the /// test panics with the message below rather than quietly producing a right answer for the wrong /// reason. @@ -162,19 +162,19 @@ impl ElectronicCodeBook for ForwardOnlyToy { panic!("CFB must never call the inverse cipher pair function (SP 800-38A Sec 6.3)"); } - fn encrypt_blocks8(&self, blocks: &mut [[u8; TOY_LEN]; 8]) { - self.inner.encrypt_blocks8(blocks); + fn encrypt_8blocks(&self, blocks: &mut [[u8; TOY_LEN]; 8]) { + self.inner.encrypt_8blocks(blocks); } - fn decrypt_blocks8(&self, _blocks: &mut [[u8; TOY_LEN]; 8]) { + fn decrypt_8blocks(&self, _blocks: &mut [[u8; TOY_LEN]; 8]) { panic!("CFB must never call the inverse cipher eight-block function (SP 800-38A Sec 6.3)"); } } -/// A [`Toy`] whose `encrypt_blocks8` / `decrypt_blocks8` return their eight results rotated by one +/// A [`Toy`] whose `encrypt_8blocks` / `decrypt_8blocks` return their eight results rotated by one /// slot, while every other method -- single block and pair -- is correct. /// -/// The eight-block analogue of [`SwappedPairToy`]: a CBC decryptor that uses `decrypt_blocks8` +/// The eight-block analogue of [`SwappedPairToy`]: a CBC decryptor that uses `decrypt_8blocks` /// must produce something other than the correct plaintext for eight or more blocks, while fewer /// than eight, which go through the pair and single paths, still round-trip. pub struct SwappedEightToy { @@ -199,14 +199,14 @@ impl ElectronicCodeBook for SwappedEightToy { self.inner.decrypt_block(block); } - fn encrypt_blocks8(&self, blocks: &mut [[u8; TOY_LEN]; 8]) { + fn encrypt_8blocks(&self, blocks: &mut [[u8; TOY_LEN]; 8]) { for block in blocks.iter_mut() { self.inner.encrypt_block(block); } blocks.rotate_left(1); } - fn decrypt_blocks8(&self, blocks: &mut [[u8; TOY_LEN]; 8]) { + fn decrypt_8blocks(&self, blocks: &mut [[u8; TOY_LEN]; 8]) { for block in blocks.iter_mut() { self.inner.decrypt_block(block); } diff --git a/crypto/modes/tests/ctr_tests.rs b/crypto/modes/tests/ctr_tests.rs index 4a85477f..e56b1381 100644 --- a/crypto/modes/tests/ctr_tests.rs +++ b/crypto/modes/tests/ctr_tests.rs @@ -602,7 +602,7 @@ fn the_eight_block_path_is_really_used_in_both_directions() { SwappedEightCtr::::do_encrypt_init_rng(&key, &mut pinned_rng(nonce)).unwrap(); let mut swapped = plaintext.clone(); e.do_encrypt(&mut swapped).unwrap(); - assert_ne!(swapped, ct, "nine blocks must go through encrypt_blocks8"); + assert_ne!(swapped, ct, "nine blocks must go through encrypt_8blocks"); // Four blocks at a time uses pairs only, so the rotated-eight toy is correct there. let (mut e, _) = diff --git a/crypto/modes/tests/ecb_tests.rs b/crypto/modes/tests/ecb_tests.rs index 40d964fc..ac7ceec3 100644 --- a/crypto/modes/tests/ecb_tests.rs +++ b/crypto/modes/tests/ecb_tests.rs @@ -250,7 +250,7 @@ fn the_eight_block_path_is_used_in_both_directions() { let (mut enc, _) = SwappedEightEcb::::do_encrypt_init(&key).unwrap(); let rotated = enc_blocks(&mut enc, &plaintext); - assert_ne!(rotated, ct, "nine blocks must go through encrypt_blocks8"); + assert_ne!(rotated, ct, "nine blocks must go through encrypt_8blocks"); assert_eq!(rotated[8], ct[8], "the ninth block goes through the single path and is right"); assert_eq!( &rotated[..8], @@ -264,7 +264,7 @@ fn the_eight_block_path_is_used_in_both_directions() { assert_eq!([a, b].as_flattened(), &ct[..8], "fours use the pair path only"); let mut dec = SwappedEightEcb::::do_decrypt_init(&key, &[]).unwrap(); - assert_ne!(dec_blocks(&mut dec, &ct), plaintext, "nine blocks must go through decrypt_blocks8"); + assert_ne!(dec_blocks(&mut dec, &ct), plaintext, "nine blocks must go through decrypt_8blocks"); let mut dec = SwappedEightEcb::::do_decrypt_init(&key, &[]).unwrap(); for (c, p) in ct.iter().zip(plaintext.iter()) { assert_eq!(&dec_flat(&mut dec, c), p, "the single-block path must not batch"); From ab7b94802d48d022ccf7e62ac3715967cead019a Mon Sep 17 00:00:00 2001 From: David Hook Date: Wed, 9 Sep 2026 13:42:40 +1000 Subject: [PATCH 11/14] core: ElectronicCodeBook batches four blocks, encrypt_4blocks / decrypt_4blocks (was eight): AES fills a pair and the u16/u32-plane engines fill four, so eight was two passes for every engine and left a four-lane engine half-empty on a 4-to-7-block tail; modes chunk fours, then pairs, then singles, the framework suite and the rotated-four toy pin the four path, benches and notes follow --- alpha_0.1.3_release_notes.md | 26 ++++---- cli/tests/aes_ecb_cli_tests.rs | 4 +- .../src/electronic_code_book.rs | 38 ++++++------ crypto/core/src/traits.rs | 26 ++++---- crypto/modes/benches/modes_benches.rs | 40 ++++++------- crypto/modes/src/cbc.rs | 28 ++++----- crypto/modes/src/cfb.rs | 31 +++++----- crypto/modes/src/cfb8.rs | 12 ++-- crypto/modes/src/ctr.rs | 8 +-- crypto/modes/src/ecb.rs | 20 +++---- crypto/modes/src/lib.rs | 2 +- crypto/modes/tests/acvp_cfb8_tests.rs | 12 ++-- crypto/modes/tests/acvp_cfb_tests.rs | 8 +-- crypto/modes/tests/acvp_ctr_tests.rs | 2 +- crypto/modes/tests/acvp_ecb_tests.rs | 12 ++-- crypto/modes/tests/cbc_tests.rs | 60 +++++++++---------- crypto/modes/tests/cfb8_tests.rs | 60 +++++++++---------- crypto/modes/tests/cfb_tests.rs | 56 ++++++++--------- crypto/modes/tests/common/mod.rs | 32 +++++----- crypto/modes/tests/ctr_tests.rs | 28 ++++----- crypto/modes/tests/ctr_vector_tests.rs | 2 +- crypto/modes/tests/ecb_tests.rs | 52 ++++++++-------- crypto/modes/tests/sp800_38a_cfb8_tests.rs | 6 +- 23 files changed, 279 insertions(+), 286 deletions(-) diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index 1263d6cd..d81a5ddb 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -73,10 +73,10 @@ only OFB outstanding. Re-exported from the umbrella crate. This matters more for CFB than for CBC: CFB XORs a keystream, so a repeated key-and-IV pair leaks `P1 XOR P1'` outright rather than merely whether the blocks were equal. * **Parallel decryption.** Sec 6.2 notes CBC decryption's inverse cipher calls can run in - parallel, so `do_decrypt_blocks` walks the ciphertext in eights through - `ElectronicCodeBook::decrypt_8blocks`, then pairs through `decrypt_2blocks`, then a one-block - remainder. A toy permutation that rotates its eight results proves the eight path is taken, and - only for full eights. Measured against an + parallel, so `do_decrypt_blocks` walks the ciphertext in fours through + `ElectronicCodeBook::decrypt_4blocks`, then pairs through `decrypt_2blocks`, then a one-block + remainder. A toy permutation that rotates its four results proves the four path is taken, and + only for full fours. Measured against an otherwise identical permutation that does not override the pair methods, this is **1.83x** the decryption throughput (67.9 vs 37.1 MiB/s, AES-128, 16 KiB, N=8). CBC encryption is serial by construction and does not use it. @@ -123,7 +123,7 @@ CFB128 (`Cfb`), SP 800-38A Sec 6.3 with `s = b`: `Cfb<_, Decrypting, _, _>` never calls `decrypt_block` or `decrypt_2blocks`. This is pinned by a test permutation whose inverse methods panic, run over both the pair and single-block paths -- so the claim is enforced rather than merely documented. -* **Parallel decryption**, via `encrypt_8blocks` / `encrypt_2blocks` (eights, then pairs, then a single block, like CBC): Sec 6.3 notes CFB decryption's forward cipher +* **Parallel decryption**, via `encrypt_4blocks` / `encrypt_2blocks` (fours, then pairs, then a single block, like CBC): Sec 6.3 notes CFB decryption's forward cipher calls "can be performed in parallel if the input blocks are first constructed (in series) from the IV and the ciphertext", and with `s = b` those input blocks simply *are* the IV followed by the ciphertext. Re-measured after the stream-cipher rewrite: against an otherwise identical @@ -137,7 +137,7 @@ CFB128 (`Cfb`), SP 800-38A Sec 6.3 with `s = b`: whole number of blocks end mid-segment and the next call finishes that segment byte by byte. At 125-byte calls (7 blocks and 13 bytes) encryption measured 51.1 MiB/s against 51.4 for block-aligned calls, and decryption 90.6 against 106.8 -- the decrypt side pays because a partial - segment at each end of a call breaks the eight-block batch. + segment at each end of a call breaks the four-block batch. * Verified against all six SP 800-38A **Appendix F.3.13-F.3.18** vectors (CFB128-AES128/192/256, Encrypt and Decrypt) in the same four groupings as CBC. F.3 additionally tabulates the *output blocks* -- the keystream -- so those are checked against the raw permutation too @@ -193,11 +193,11 @@ CFB8 (`Cfb8`), SP 800-38A Sec 6.3 with `s = 8`: CFB8. * **Decryption still batches.** Sec 6.3's parallel decryption applies: the successive register states depend only on the IV and the ciphertext, so they are built in series -- byte shuffling, - no cipher calls -- and the forward ciphers then run eight at a time through `encrypt_8blocks`, + no cipher calls -- and the forward ciphers then run four at a time through `encrypt_4blocks`, then in pairs. Measured **1.94x** the throughput of the same decryption in 1-byte calls, which never batch (6.61 vs 3.40 MiB/s). Encryption cannot batch and does not. * **Decryption never calls the inverse cipher**, as in CFB128, pinned by the same test permutation - whose inverse methods panic, run over the eight-block, pair and single-byte paths. + whose inverse methods panic, run over the four-block, pair and single-byte paths. * Verified against all six SP 800-38A **Appendix F.3.7-F.3.12** vectors (CFB8-AES128/192/256, Encrypt and Decrypt), each in seven groupings from one byte per call up to the whole message. F.3.7's tabulated **input and output blocks** -- all 18 of each -- are checked three ways: that @@ -247,7 +247,7 @@ CTR (`Ctr`), SP 800-38A Sec 6.5: * **Both directions are parallel**, the only mode here of which that is true. Sec 6.5: "In both CTR encryption and CTR decryption, the forward cipher functions can be performed in parallel." Counter blocks depend on nothing but the nonce and the index, so encryption batches through - `encrypt_8blocks` / `encrypt_2blocks` exactly as decryption does, and encryption and decryption are + `encrypt_4blocks` / `encrypt_2blocks` exactly as decryption does, and encryption and decryption are the same operation. Only the forward cipher function is ever used, as in the CFB modes. * The keystream block is the one buffer in this crate wrapped in `Secret`: a call may end part-way through a block and the remainder is kept for the next one, and unlike a chaining value that @@ -351,7 +351,7 @@ ECB (`Ecb`), SP 800-38A Sec 6.1: (176 / 208 / 240 B for AES-128/192/256). * **Both directions batch.** Sec 6.1 allows forward and inverse cipher calls "to be computed in parallel", so encryption as well as decryption walks the blocks through `ElectronicCodeBook::{en,de}crypt_blocks8`, then the pair methods, then - a single block. The swapped-pair and rotated-eight test permutations prove both paths are taken in both directions. + a single block. The swapped-pair and rotated-four test permutations prove both paths are taken in both directions. * `aes128-ecb` / `aes192-ecb` / `aes256-ecb` CLI subcommands over the shared block-mode plumbing, which is now generic over `INIT_DATA_LEN`: nothing is prepended on `encrypt` or consumed on `decrypt`, so output is exactly as long as input. The per-command help carries the warning. @@ -359,7 +359,7 @@ ECB (`Ecb`), SP 800-38A Sec 6.1: groupings each -- and, since there is no IV, `encrypt` is checked against the published ciphertext too, through the streaming API and the one-shot. Each tabulated ciphertext block is also checked to be `CIPH_K` of its plaintext block through the raw permutation. The **NIST ACVP `ACVP-AES-ECB`** set (2138 AFT cases) already used by `aes` - is run again through the mode API, both directions, in three groupings including one that reaches the eight-block + is run again through the mode API, both directions, in three groupings including one that reaches the four-block path. Structural tests pin the Sec 6.1 equations against a reference over the toy permutation, determinism and the codebook property, Appendix D error propagation (a corrupted block randomises itself and nothing else, checked over all 128 bit positions with real AES), the empty init data, and composition with `bouncycastle-padding`. @@ -367,7 +367,7 @@ ECB (`Ecb`), SP 800-38A Sec 6.1: `core`: new `ElectronicCodeBook` trait (`crypto/core/src/traits.rs`), the raw keyed permutation -- `CIPH_K` / `CIPH^-1_K` of SP 800-38A Sec 5.1 -- that a mode is built on. `new`, `encrypt_block`, `decrypt_block`, plus provided `encrypt_2blocks` / `decrypt_2blocks` that -default to two single-block calls and `encrypt_8blocks` / `decrypt_8blocks` that default to four pair +default to two single-block calls and `encrypt_4blocks` / `decrypt_4blocks` that default to two pair calls, all of which bit-sliced implementations override (AES the pair form, SM4 both). The block methods are infallible; only `new` can fail, and only on the key. `bouncycastle-aes` implements it for all three key lengths (the data-encryption traits are still deliberately not implemented @@ -607,7 +607,7 @@ Block cipher traits (PR #96): `do_{en,de}crypt_blocks(&mut [[u8; BLOCK_LEN]])`, which is what guarantees an implementation never sees a partial block; an implementor writes only `do_{en,de}crypt_init[_rng]` and that hook. The hook takes a *slice* of blocks rather than a `[[u8; BLOCK_LEN]; N]` array (it did at first): every whole number of blocks is valid, so - there is no length invariant for a const parameter to carry, and batching -- singly, in pairs, in eights -- is the + there is no length invariant for a const parameter to carry, and batching -- singly, in pairs, in fours -- is the mode's decision. `do_{en,de}crypt` therefore hands the whole buffer to the hook in one call, and CBC decryption chunks it into pairs for `decrypt_2blocks` itself. The data methods keep a `Result` only for modes with a per-initialization data limit (counter-based modes); CBC never fails them. diff --git a/cli/tests/aes_ecb_cli_tests.rs b/cli/tests/aes_ecb_cli_tests.rs index ddbc66e0..d218cf03 100644 --- a/cli/tests/aes_ecb_cli_tests.rs +++ b/cli/tests/aes_ecb_cli_tests.rs @@ -234,8 +234,8 @@ fn encrypt_then_decrypt_round_trips_with_no_iv() { } } -/// Round trips at sizes that straddle the 1 KiB streaming chunk, the eight-block batch and the -/// block boundary: 128 is one eight; 144 is an eight plus one block; 1040 is a chunk plus a block. +/// Round trips at sizes that straddle the 1 KiB streaming chunk, the four-block batch and the +/// block boundary: 128 is two fours; 144 is two fours plus one block; 1040 is a chunk plus a block. #[test] fn round_trips_across_chunk_and_batch_boundaries() { for size in [16usize, 32, 128, 144, 1024, 1040, 4096, 4112, 65536] { diff --git a/crypto/core-test-framework/src/electronic_code_book.rs b/crypto/core-test-framework/src/electronic_code_book.rs index 64fa4ad5..ca8e855e 100644 --- a/crypto/core-test-framework/src/electronic_code_book.rs +++ b/crypto/core-test-framework/src/electronic_code_book.rs @@ -34,7 +34,7 @@ impl TestFrameworkElectronicCodeBook { /// likewise for `decrypt_2blocks` -- this is what pins an override to the default's /// semantics, and it is the reason the pair methods are worth having in the trait at all; /// * the pair methods round-trip each other; - /// * `encrypt_8blocks` / `decrypt_8blocks` likewise agree with eight single-block calls in + /// * `encrypt_4blocks` / `decrypt_4blocks` likewise agree with four single-block calls in /// order, and round-trip each other; /// * a key of the wrong [`KeyType`] is rejected; /// * the security-strength policy matches [`Algorithm::MAX_SECURITY_STRENGTH`]. @@ -110,34 +110,34 @@ impl TestFrameworkElectronicCodeBook { assert_eq!(buf, [*a, *b], "decrypt_2blocks must invert encrypt_2blocks"); } - // The eight-block methods must be indistinguishable from eight single-block calls, in every + // The four-block methods must be indistinguishable from four single-block calls, in every // slot, whether they are the trait default (four pair calls) or an override. - let eights = blocks.as_chunks::<8>().0; + let fours = blocks.as_chunks::<4>().0; assert!( - !eights.is_empty(), - "DUMMY_SEED should hold at least eight blocks; test setup problem" + !fours.is_empty(), + "DUMMY_SEED should hold at least four blocks; test setup problem" ); - for eight in eights.iter() { - let mut singly = *eight; + for four in fours.iter() { + let mut singly = *four; for block in singly.iter_mut() { perm.encrypt_block(block); } - let mut batched = *eight; - perm.encrypt_8blocks(&mut batched); - assert_eq!(batched, singly, "encrypt_8blocks must match eight encrypt_block calls"); + let mut batched = *four; + perm.encrypt_4blocks(&mut batched); + assert_eq!(batched, singly, "encrypt_4blocks must match four encrypt_block calls"); - let mut singly = *eight; + let mut singly = *four; for block in singly.iter_mut() { perm.decrypt_block(block); } - let mut batched = *eight; - perm.decrypt_8blocks(&mut batched); - assert_eq!(batched, singly, "decrypt_8blocks must match eight decrypt_block calls"); - - let mut buf = *eight; - perm.encrypt_8blocks(&mut buf); - perm.decrypt_8blocks(&mut buf); - assert_eq!(buf, *eight, "decrypt_8blocks must invert encrypt_8blocks"); + let mut batched = *four; + perm.decrypt_4blocks(&mut batched); + assert_eq!(batched, singly, "decrypt_4blocks must match four decrypt_block calls"); + + let mut buf = *four; + perm.encrypt_4blocks(&mut buf); + perm.decrypt_4blocks(&mut buf); + assert_eq!(buf, *four, "decrypt_4blocks must invert encrypt_4blocks"); } // A pair of *identical* blocks must give a pair of identical outputs. This catches an diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index bdabf825..09ae6a30 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -263,7 +263,7 @@ pub trait BlockCipherEncryptor< /// block shape is what guarantees it never sees a partial block. It takes a slice rather than /// a `[[u8; BLOCK_LEN]; N]` array because every whole number of blocks is valid, so there is /// no length invariant for a const parameter to carry, and because how to batch the blocks -- - /// singly, in pairs, in eights -- is the mode's decision, not the caller's: a mode whose + /// singly, in pairs, in fours -- is the mode's decision, not the caller's: a mode whose /// permutation processes several blocks at once (CBC decryption, CTR) chunks the slice itself. /// Callers should normally use the flat [`BlockCipherEncryptor::do_encrypt`] instead. fn do_encrypt_blocks( @@ -371,30 +371,32 @@ pub trait ElectronicCodeBook: self.decrypt_block(b); } - /// The forward cipher function on eight *independent* blocks, in place. + /// The forward cipher function on four *independent* blocks, in place. /// - /// Provided as four [`ElectronicCodeBook::encrypt_2blocks`] calls, so an implementation that + /// Provided as two [`ElectronicCodeBook::encrypt_2blocks`] calls, so an implementation that /// overrides only the pair form gets its benefit here too. An engine whose natural unit is /// larger than a pair overrides this directly: a bit-sliced engine whose S-box circuit - /// substitutes four blocks per pass runs eight blocks as two full passes rather than four - /// half-empty pair calls. + /// substitutes four blocks per pass runs the four as one full pass rather than two half-empty + /// pair calls. Four is the unit because it is the widest any engine in this library fills: + /// AES fills a pair, and the `u16`- and `u32`-plane engines (SM4, Camellia, ARIA) fill four. /// - /// Overrides must be indistinguishable from the default, including the order of the eight + /// Overrides must be indistinguishable from the default, including the order of the four /// results. `TestFrameworkElectronicCodeBook` pins that. /// - /// Modes with parallel structure chunk their data into eights first, then pairs, then single + /// Modes with parallel structure chunk their data into fours first, then pairs, then single /// blocks; see CBC decryption in `bouncycastle-modes`. - fn encrypt_8blocks(&self, blocks: &mut [[u8; BLOCK_LEN]; 8]) { - // Eight is a multiple of two, so the remainder is empty. + fn encrypt_4blocks(&self, blocks: &mut [[u8; BLOCK_LEN]; 4]) { + // Four is a multiple of two, so the remainder is empty. let (pairs, _) = blocks.as_mut_slice().as_chunks_mut::<2>(); for pair in pairs { self.encrypt_2blocks(pair); } } - /// The inverse cipher function on eight *independent* blocks, in place. - /// See [`ElectronicCodeBook::encrypt_8blocks`]. - fn decrypt_8blocks(&self, blocks: &mut [[u8; BLOCK_LEN]; 8]) { + /// The inverse cipher function on four *independent* blocks, in place. + /// See [`ElectronicCodeBook::encrypt_4blocks`]. + fn decrypt_4blocks(&self, blocks: &mut [[u8; BLOCK_LEN]; 4]) { + // Four is a multiple of two, so the remainder is empty. let (pairs, _) = blocks.as_mut_slice().as_chunks_mut::<2>(); for pair in pairs { self.decrypt_2blocks(pair); diff --git a/crypto/modes/benches/modes_benches.rs b/crypto/modes/benches/modes_benches.rs index 16070e00..82cb977d 100644 --- a/crypto/modes/benches/modes_benches.rs +++ b/crypto/modes/benches/modes_benches.rs @@ -4,9 +4,9 @@ //! CBC and CFB is serial by construction (SP 800-38A Sec 6.2 and Sec 6.3: each forward cipher input //! depends on the previous output), so it can only ever use the single-block path. *Decryption* in //! both is parallel, and this implementation hands blocks to the permutation's batch methods -- -//! eights first, then pairs, then the remainder singly: for CBC that is `decrypt_8blocks` / -//! `decrypt_2blocks`, for CFB it is `encrypt_8blocks` / `encrypt_2blocks`, since CFB uses the -//! forward function in both directions. AES overrides only the pair form, so its eights are four +//! fours first, then pairs, then the remainder singly: for CBC that is `decrypt_4blocks` / +//! `decrypt_2blocks`, for CFB it is `encrypt_4blocks` / `encrypt_2blocks`, since CFB uses the +//! forward function in both directions. AES overrides only the pair form, so its fours are two //! pairs. With the bit-sliced AES, whose two-block path costs barely more than one block, //! decryption should therefore run at roughly twice the throughput of encryption. That gap is the //! entire justification for the batch methods on `ElectronicCodeBook`, so if it disappears, @@ -26,7 +26,7 @@ //! throughput of CFB over the same 16 KiB. That ratio, against `modes::cfb::AES_128`, is the number //! to watch; it is inherent to `s = 8` (Sec 6.3 discards `b - s` bits of every output block), not a //! property of this implementation. Decryption should still beat encryption, because CFB8 -//! decryption builds its input blocks in series and then batches the ciphers eight at a time while +//! decryption builds its input blocks in series and then batches the ciphers four at a time while //! encryption cannot. //! //! The cipher works in place, so each measurement runs on a fresh copy of the data made in @@ -168,7 +168,7 @@ fn bench_aes128(c: &mut Criterion) { ) }); - // N=2 is one pair and N=8 one eight (four pairs, for AES), so every block goes through + // N=2 is one pair and N=8 two fours (four pairs, for AES), so every block goes through // decrypt_2blocks. group.bench_function("16KiB decrypt -- N=2 (all pairs)", |b| { b.iter_batched( @@ -186,7 +186,7 @@ fn bench_aes128(c: &mut Criterion) { ) }); - group.bench_function("16KiB decrypt -- N=8 (all pairs)", |b| { + group.bench_function("16KiB decrypt -- N=8 (all fours)", |b| { b.iter_batched( || ciphertext.clone(), |mut scratch| { @@ -285,7 +285,7 @@ fn bench_aes256(c: &mut Criterion) { enc.do_encrypt_blocks(chunk).unwrap(); } - group.bench_function("16KiB decrypt -- N=8 (all pairs)", |b| { + group.bench_function("16KiB decrypt -- N=8 (all fours)", |b| { b.iter_batched( || ciphertext.clone(), |mut scratch| { @@ -368,7 +368,7 @@ fn bench_cfb_aes128(c: &mut Criterion) { }); } - // ---- decryption: parallel, and uses `encrypt_8blocks` / `encrypt_2blocks` -- the FORWARD + // ---- decryption: parallel, and uses `encrypt_4blocks` / `encrypt_2blocks` -- the FORWARD // batch methods ---- let (mut enc, iv) = Aes128Cfb::::do_encrypt_init(&k).unwrap(); let mut ciphertext = flat.clone(); @@ -378,10 +378,10 @@ fn bench_cfb_aes128(c: &mut Criterion) { // N=1 never forms a pair, so this is the single-block path: the ratio against encrypt // should be about 1. ("16KiB decrypt -- N=1 (no pairing)", BLOCK_LEN), - // N=2 and N=8 are all pairs (N=8 one eight), so every block goes through a batch method. + // N=2 and N=8 are all batches (N=8 two fours), so every block goes through a batch method. ("16KiB decrypt -- N=2 (all pairs)", 2 * BLOCK_LEN), - ("16KiB decrypt -- N=8 (all pairs)", 8 * BLOCK_LEN), - // N=9 is one eight plus a one-block remainder, so it exercises the tail path too. + ("16KiB decrypt -- N=8 (all fours)", 8 * BLOCK_LEN), + // N=9 is two fours plus a one-block remainder, so it exercises the tail path too. ("16KiB decrypt -- N=9 (pairs + remainder)", 9 * BLOCK_LEN), // As for encryption: 7 blocks plus 13 bytes per call. Compare with N=8. ("16KiB decrypt -- 125-byte calls (byte path at both ends)", 125), @@ -463,7 +463,7 @@ fn bench_cfb_aes256(c: &mut Criterion) { let mut ciphertext = flat.clone(); enc.do_encrypt(&mut ciphertext).unwrap(); - group.bench_function("16KiB decrypt -- N=8 (all pairs)", |b| { + group.bench_function("16KiB decrypt -- N=8 (all fours)", |b| { b.iter_batched( || ciphertext.clone(), |mut scratch| { @@ -485,7 +485,7 @@ fn bench_cfb_aes256(c: &mut Criterion) { /// CFB8: one forward cipher per byte, so ~1/16 of CFB's throughput on a 16-byte block. /// /// Encryption is strictly serial. Decryption builds its input blocks in series and then runs them -/// through `encrypt_8blocks` / `encrypt_2blocks` (SP 800-38A Sec 6.3's parallel decryption), so it +/// through `encrypt_4blocks` / `encrypt_2blocks` (SP 800-38A Sec 6.3's parallel decryption), so it /// should be substantially faster than encryption -- the same batch effect CBC and CFB show, at /// byte granularity. fn bench_cfb8_aes128(c: &mut Criterion) { @@ -514,10 +514,10 @@ fn bench_cfb8_aes128(c: &mut Criterion) { enc.do_encrypt(&mut ciphertext).unwrap(); for (name, call_len) in [ - // One call: eights, then pairs, then the tail. This is the batched path. + // One call: fours, then pairs, then the tail. This is the batched path. ("16KiB decrypt -- whole message in one call (batched)", DATA_LEN), - // 8-byte calls: still exactly one eight-block batch per call. - ("16KiB decrypt -- 8-byte calls (one batch each)", 8), + // 8-byte calls: exactly two four-block batches per call. + ("16KiB decrypt -- 8-byte calls (two batches each)", 8), // 1-byte calls: never batches, so this is the cost of the serial path on the decrypt side // and the controlled comparison for what batching buys. ("16KiB decrypt -- 1-byte calls (no batching)", 1), @@ -556,7 +556,7 @@ fn bench_ctr_aes128(c: &mut Criterion) { // N=1 never forms a pair: the single-block path, and the baseline for the batch effect. ("16KiB encrypt -- N=1 (no batching)", BLOCK_LEN), ("16KiB encrypt -- N=2 (all pairs)", 2 * BLOCK_LEN), - ("16KiB encrypt -- N=8 (one eight per call)", 8 * BLOCK_LEN), + ("16KiB encrypt -- N=8 (two fours per call)", 8 * BLOCK_LEN), // Calls that are not a whole number of blocks, so each end goes byte by byte. ("16KiB encrypt -- 125-byte calls (byte path at both ends)", 125), ] { @@ -580,7 +580,7 @@ fn bench_ctr_aes128(c: &mut Criterion) { for (name, call_len) in [ ("16KiB decrypt -- N=1 (no batching)", BLOCK_LEN), - ("16KiB decrypt -- N=8 (one eight per call)", 8 * BLOCK_LEN), + ("16KiB decrypt -- N=8 (two fours per call)", 8 * BLOCK_LEN), ] { group.bench_function(name, |b| { b.iter_batched( @@ -650,7 +650,7 @@ fn bench_ecb_aes128(c: &mut Criterion) { ) }); - group.bench_function("16KiB encrypt -- N=8 (eights)", |b| { + group.bench_function("16KiB encrypt -- N=8 (fours)", |b| { b.iter_batched( || blocks.clone(), |mut scratch| { @@ -666,7 +666,7 @@ fn bench_ecb_aes128(c: &mut Criterion) { ) }); - group.bench_function("16KiB decrypt -- N=8 (eights)", |b| { + group.bench_function("16KiB decrypt -- N=8 (fours)", |b| { b.iter_batched( || blocks.clone(), |mut scratch| { diff --git a/crypto/modes/src/cbc.rs b/crypto/modes/src/cbc.rs index abf6fec9..14a07164 100644 --- a/crypto/modes/src/cbc.rs +++ b/crypto/modes/src/cbc.rs @@ -26,10 +26,10 @@ //! operation (except the first) depends on the result of the previous forward cipher operation, so //! the forward cipher operations cannot be performed in parallel". //! -//! This implementation uses that: decryption walks the ciphertext eight blocks at a time through -//! [`ElectronicCodeBook::decrypt_8blocks`], then any remaining pair through +//! This implementation uses that: decryption walks the ciphertext four blocks at a time through +//! [`ElectronicCodeBook::decrypt_4blocks`], then any remaining pair through //! [`ElectronicCodeBook::decrypt_2blocks`], then the last block singly. A bit-sliced engine -//! computes a pair (AES) or eight blocks (SM4) for barely more than the cost of one. Encryption +//! computes a pair (AES) or four blocks (SM4) for barely more than the cost of one. Encryption //! cannot, and does not. use crate::iv::random_iv; @@ -123,17 +123,17 @@ where self.chain = cj1; } - /// Decrypts eight consecutive blocks with one [`ElectronicCodeBook::decrypt_8blocks`] call. + /// Decrypts four consecutive blocks with one [`ElectronicCodeBook::decrypt_4blocks`] call. /// - /// The same argument as [`Self::decrypt_pair`], eight wide: `Pj+k = CIPH^-1_K(Cj+k) XOR Cj+k-1` - /// for `k = 0..8`, with `Cj-1` the incoming chaining value. No inverse cipher depends on - /// another's output, so all eight run together; the ciphertexts are copied out first because + /// The same argument as [`Self::decrypt_pair`], four wide: `Pj+k = CIPH^-1_K(Cj+k) XOR Cj+k-1` + /// for `k = 0..4`, with `Cj-1` the incoming chaining value. No inverse cipher depends on + /// another's output, so all four run together; the ciphertexts are copied out first because /// the permutation overwrites them and each is the next block's XOR operand, and the chaining - /// value advances to `Cj+7`. + /// value advances to `Cj+3`. #[inline] - fn decrypt_eight(&mut self, blocks: &mut [[u8; BLOCK_LEN]; 8]) { + fn decrypt_four(&mut self, blocks: &mut [[u8; BLOCK_LEN]; 4]) { let cts = *blocks; - self.perm.decrypt_8blocks(blocks); + self.perm.decrypt_4blocks(blocks); let mut prev = self.chain; for (pj, cj) in blocks.iter_mut().zip(cts.iter()) { @@ -213,7 +213,7 @@ where /// The implementor hook (the flat `do_decrypt` is provided over it). /// - /// Walks the input in eights through `decrypt_8blocks`, then pairs through `decrypt_2blocks`, + /// Walks the input in fours through `decrypt_4blocks`, then pairs through `decrypt_2blocks`, /// then the at-most-one block left over: Sec 6.2's parallelism, in the units the permutation /// offers. `as_chunks_mut` splits into exactly those shapes with no runtime length check and no /// indexing arithmetic. Never fails: CBC has no per-IV data limit. @@ -221,9 +221,9 @@ where &mut self, blocks: &mut [[u8; BLOCK_LEN]], ) -> Result<(), SymmetricCipherError> { - let (eights, rest) = blocks.as_chunks_mut::<8>(); - for eight in eights.iter_mut() { - self.decrypt_eight(eight); + let (fours, rest) = blocks.as_chunks_mut::<4>(); + for four in fours.iter_mut() { + self.decrypt_four(four); } let (pairs, tail) = rest.as_chunks_mut::<2>(); for pair in pairs.iter_mut() { diff --git a/crypto/modes/src/cfb.rs b/crypto/modes/src/cfb.rs index 29f51203..6be3f721 100644 --- a/crypto/modes/src/cfb.rs +++ b/crypto/modes/src/cfb.rs @@ -101,7 +101,7 @@ //! applied to each input block to produce the output blocks." //! //! So [`Cfb`](Cfb) never calls [`ElectronicCodeBook::decrypt_block`], -//! [`ElectronicCodeBook::decrypt_2blocks`] or [`ElectronicCodeBook::decrypt_8blocks`]. A +//! [`ElectronicCodeBook::decrypt_2blocks`] or [`ElectronicCodeBook::decrypt_4blocks`]. A //! permutation could implement only the forward direction and still work here; `cfb_tests.rs` pins //! that with a toy whose inverse panics. The mode XORs a keystream in both directions, and the two //! directions differ only in which of the two values -- the byte that came in, or the byte that @@ -117,7 +117,7 @@ //! //! Constructing them "in series" is trivial here: with `s = b` the input blocks *are* the IV //! followed by the ciphertext blocks, already in hand. Decryption therefore walks the -//! block-aligned part of the data in eights through [`ElectronicCodeBook::encrypt_8blocks`] and +//! block-aligned part of the data in fours through [`ElectronicCodeBook::encrypt_4blocks`] and //! pairs through [`ElectronicCodeBook::encrypt_2blocks`], which a bit-sliced engine computes for //! barely more than the cost of one block. Encryption cannot, and does not. Only the bytes that //! complete an open segment, and the bytes that open the final short one, go singly. @@ -285,19 +285,18 @@ where } } - /// Decrypts eight consecutive blocks with one [`ElectronicCodeBook::encrypt_8blocks`] call. + /// Decrypts four consecutive blocks with one [`ElectronicCodeBook::encrypt_4blocks`] call. /// - /// The same construction as [`Self::decrypt_pair`] widened to eight: the input blocks are the - /// incoming input block followed by the first seven ciphertext blocks, all known before any - /// cipher call, so the eight forward ciphers are independent (Sec 6.3's parallel decryption). - /// `I_{j+8} = Cj+7` is read before the XOR turns it into `Pj+7`. + /// The same construction as [`Self::decrypt_pair`] widened to four: the input blocks are the + /// incoming input block followed by the first three ciphertext blocks, all known before any + /// cipher call, so the four forward ciphers are independent (Sec 6.3's parallel decryption). + /// `I_{j+4} = Cj+3` is read before the XOR turns it into `Pj+3`. #[inline] - fn decrypt_eight(&mut self, blocks: &mut [[u8; BLOCK_LEN]; 8]) { + fn decrypt_four(&mut self, blocks: &mut [[u8; BLOCK_LEN]; 4]) { debug_assert_eq!(self.used, BLOCK_LEN, "the block path needs a segment boundary"); - let mut o = - [self.buf, blocks[0], blocks[1], blocks[2], blocks[3], blocks[4], blocks[5], blocks[6]]; - self.perm.encrypt_8blocks(&mut o); - self.buf = blocks[7]; + let mut o = [self.buf, blocks[0], blocks[1], blocks[2]]; + self.perm.encrypt_4blocks(&mut o); + self.buf = blocks[3]; for (block, o) in blocks.iter_mut().zip(o.iter()) { for (b, o) in block.iter_mut().zip(o.iter()) { *b ^= *o; @@ -392,7 +391,7 @@ where /// Decrypts `data`, of any length, in place. /// - /// Walks the block-aligned middle in eights through the permutation's *forward* eight-block + /// Walks the block-aligned middle in fours through the permutation's *forward* four-block /// path, then in pairs through its forward pair path, then the remaining block singly. /// `as_chunks_mut` splits into exactly those shapes with no runtime length check and no /// indexing arithmetic. The bytes that complete an open segment, and the final short segment, @@ -400,9 +399,9 @@ where fn do_decrypt(&mut self, data: &mut [u8]) -> Result<(), SymmetricCipherError> { let (head, blocks, tail) = self.split(data); self.decrypt_bytes(head); - let (eights, rest) = blocks.as_chunks_mut::<8>(); - for eight in eights.iter_mut() { - self.decrypt_eight(eight); + let (fours, rest) = blocks.as_chunks_mut::<4>(); + for four in fours.iter_mut() { + self.decrypt_four(four); } let (pairs, single) = rest.as_chunks_mut::<2>(); for pair in pairs.iter_mut() { diff --git a/crypto/modes/src/cfb8.rs b/crypto/modes/src/cfb8.rs index ae8c7571..1497533d 100644 --- a/crypto/modes/src/cfb8.rs +++ b/crypto/modes/src/cfb8.rs @@ -75,8 +75,8 @@ //! //! Decryption knows every ciphertext byte before it starts, so it can build the shift register's //! successive states in series -- byte shuffling, no cipher calls -- and then run the forward -//! ciphers together. This implementation does exactly that, in eights through -//! [`ElectronicCodeBook::encrypt_8blocks`] and then pairs through +//! ciphers together. This implementation does exactly that, in fours through +//! [`ElectronicCodeBook::encrypt_4blocks`] and then pairs through //! [`ElectronicCodeBook::encrypt_2blocks`], which is where a bit-sliced engine earns back a large //! part of what the mode costs. Encryption cannot: `Ij` needs `C_{j-1}`, which is the output of the //! previous cipher call. @@ -253,13 +253,13 @@ where /// with the *ciphertext* byte -- the one that came in, not the plaintext going out -- shifted /// into the register. /// - /// Walks the data in eights through the permutation's *forward* eight-block path, then in pairs + /// Walks the data in fours through the permutation's *forward* four-block path, then in pairs /// through its forward pair path, then the remaining bytes singly (Sec 6.3's parallel /// decryption; see the module docs). Never fails: CFB has no per-IV data limit. fn do_decrypt(&mut self, data: &mut [u8]) -> Result<(), SymmetricCipherError> { - let (eights, rest) = data.as_chunks_mut::<8>(); - for eight in eights.iter_mut() { - self.decrypt_batch(eight, P::encrypt_8blocks); + let (fours, rest) = data.as_chunks_mut::<4>(); + for four in fours.iter_mut() { + self.decrypt_batch(four, P::encrypt_4blocks); } let (pairs, tail) = rest.as_chunks_mut::<2>(); for pair in pairs.iter_mut() { diff --git a/crypto/modes/src/ctr.rs b/crypto/modes/src/ctr.rs index 004e0f3a..ca6e7704 100644 --- a/crypto/modes/src/ctr.rs +++ b/crypto/modes/src/ctr.rs @@ -92,7 +92,7 @@ //! Sec 6.5: "In both CTR encryption and CTR decryption, the forward cipher functions can be //! performed in parallel". Counter blocks depend on nothing but the nonce and the index, so unlike //! CBC and CFB there is no serial direction at all: **both** directions walk the block-aligned part -//! of the data in eights through [`ElectronicCodeBook::encrypt_8blocks`], then in pairs through +//! of the data in fours through [`ElectronicCodeBook::encrypt_4blocks`], then in pairs through //! [`ElectronicCodeBook::encrypt_2blocks`]. Only the bytes that finish a partially-used keystream //! block, and the short tail at the end, go one block at a time. //! @@ -367,9 +367,9 @@ where self.apply_bytes(head); let (blocks, tail) = rest.as_chunks_mut::(); - let (eights, rest_blocks) = blocks.as_chunks_mut::<8>(); - for eight in eights.iter_mut() { - self.apply_batch(eight, P::encrypt_8blocks); + let (fours, rest_blocks) = blocks.as_chunks_mut::<4>(); + for four in fours.iter_mut() { + self.apply_batch(four, P::encrypt_4blocks); } let (pairs, single) = rest_blocks.as_chunks_mut::<2>(); for pair in pairs.iter_mut() { diff --git a/crypto/modes/src/ecb.rs b/crypto/modes/src/ecb.rs index 26be2324..fb1e0d8e 100644 --- a/crypto/modes/src/ecb.rs +++ b/crypto/modes/src/ecb.rs @@ -40,8 +40,8 @@ //! //! Sec 6.1: "In ECB encryption and ECB decryption, multiple forward cipher functions and inverse //! cipher functions can be computed in parallel." Unlike CBC and CFB, whose encryption is serial, -//! both directions here batch through the permutation's eight-block and pair methods -//! ([`ElectronicCodeBook::encrypt_8blocks`] / [`ElectronicCodeBook::encrypt_2blocks`] and their +//! both directions here batch through the permutation's four-block and pair methods +//! ([`ElectronicCodeBook::encrypt_4blocks`] / [`ElectronicCodeBook::encrypt_2blocks`] and their //! inverses), then finish the remaining block singly. use crate::{Decrypting, Encrypting}; @@ -127,16 +127,16 @@ where /// block, in place. /// /// Sec 6.1 allows the forward cipher functions to "be computed in parallel", so the blocks go - /// to the permutation in eights, then pairs, then the remaining block singly. `as_chunks_mut` + /// to the permutation in fours, then pairs, then the remaining block singly. `as_chunks_mut` /// splits into exactly those shapes with no runtime length check. Never fails: ECB has no /// per-initialization data limit. fn do_encrypt_blocks( &mut self, blocks: &mut [[u8; BLOCK_LEN]], ) -> Result<(), SymmetricCipherError> { - let (eights, rest) = blocks.as_chunks_mut::<8>(); - for eight in eights.iter_mut() { - self.perm.encrypt_8blocks(eight); + let (fours, rest) = blocks.as_chunks_mut::<4>(); + for four in fours.iter_mut() { + self.perm.encrypt_4blocks(four); } let (pairs, tail) = rest.as_chunks_mut::<2>(); for pair in pairs.iter_mut() { @@ -164,15 +164,15 @@ where } /// The implementor hook (the flat `do_decrypt` is provided over it): `Pj = CIPH^-1_K(Cj)` for - /// every block, in place -- eights, then pairs, then the remaining block, as on the encrypt + /// every block, in place -- fours, then pairs, then the remaining block, as on the encrypt /// side. Never fails. fn do_decrypt_blocks( &mut self, blocks: &mut [[u8; BLOCK_LEN]], ) -> Result<(), SymmetricCipherError> { - let (eights, rest) = blocks.as_chunks_mut::<8>(); - for eight in eights.iter_mut() { - self.perm.decrypt_8blocks(eight); + let (fours, rest) = blocks.as_chunks_mut::<4>(); + for four in fours.iter_mut() { + self.perm.decrypt_4blocks(four); } let (pairs, tail) = rest.as_chunks_mut::<2>(); for pair in pairs.iter_mut() { diff --git a/crypto/modes/src/lib.rs b/crypto/modes/src/lib.rs index 2644bea3..f0284028 100644 --- a/crypto/modes/src/lib.rs +++ b/crypto/modes/src/lib.rs @@ -363,7 +363,7 @@ //! it is live key material for the bytes not yet consumed. //! //! The data methods work in place. The batch paths in a decryptor are the transient cost: a -//! `[[u8; BLOCK_LEN]; 8]` of stack for the eight-block path -- 128 B on AES -- and a +//! `[[u8; BLOCK_LEN]; 4]` of stack for the four-block path -- 64 B on AES -- and a //! `[[u8; BLOCK_LEN]; 2]` for the pair path. CFB8's batch paths hold input blocks it builds itself; //! CBC's and CFB's hold a copy of the ciphertext they need for the chaining value. //! [`Encrypting`] and [`Decrypting`] are zero-sized and held in a `PhantomData`, so encoding the diff --git a/crypto/modes/tests/acvp_cfb8_tests.rs b/crypto/modes/tests/acvp_cfb8_tests.rs index 944581bd..9748b91c 100644 --- a/crypto/modes/tests/acvp_cfb8_tests.rs +++ b/crypto/modes/tests/acvp_cfb8_tests.rs @@ -23,7 +23,7 @@ //! that reach the batch paths. Every case is run **four times**: as one call over the whole //! payload, byte by byte, in 8-byte calls, and in 3-byte calls that never line up with the //! 8-byte batch. Between them those put the multi-byte cases through -//! [`ElectronicCodeBook::encrypt_8blocks`] and [`ElectronicCodeBook::encrypt_2blocks`] -- the +//! [`ElectronicCodeBook::encrypt_4blocks`] and [`ElectronicCodeBook::encrypt_2blocks`] -- the //! *forward* function, even on the decrypt side -- and through the single-byte path, with the //! shift register carried across calls at every alignment. So all of that is exercised against real //! vectors and not only against the toys in `cfb8_tests.rs`. @@ -96,12 +96,12 @@ fn cipher_key(bytes: &[u8]) -> KeyMaterial { /// How to walk the bytes of one case. #[derive(Clone, Copy, PartialEq, Eq, Debug)] enum Grouping { - /// The whole payload in one call: eights, then pairs, then the remaining bytes singly. + /// The whole payload in one call: fours, then pairs, then the remaining bytes singly. Whole, /// One byte per call. Never batches. Bytes, - /// Eight bytes per call: every call is exactly one `encrypt_8blocks` batch. - Eights, + /// Four bytes per call: every call is exactly one `encrypt_4blocks` batch. + Fours, /// Three bytes per call, so no call lines up with the 8-byte batch and the shift register has /// to carry across calls at every alignment. Threes, @@ -112,7 +112,7 @@ impl Grouping { match self { Grouping::Whole => payload_len.max(1), Grouping::Bytes => 1, - Grouping::Eights => 8, + Grouping::Fours => 4, Grouping::Threes => 3, } } @@ -256,7 +256,7 @@ fn acvp_aes_cfb8_known_answer_tests() { multi_block += 1; } - for grouping in [Grouping::Whole, Grouping::Bytes, Grouping::Eights, Grouping::Threes] { + for grouping in [Grouping::Whole, Grouping::Bytes, Grouping::Fours, Grouping::Threes] { let got = run_case_for_key_len(&key_bytes, iv, &input, encrypt, grouping); assert_eq!( got, diff --git a/crypto/modes/tests/acvp_cfb_tests.rs b/crypto/modes/tests/acvp_cfb_tests.rs index 5bec885f..458722b5 100644 --- a/crypto/modes/tests/acvp_cfb_tests.rs +++ b/crypto/modes/tests/acvp_cfb_tests.rs @@ -24,8 +24,8 @@ //! including 54 whose payload spans 2 to 10 blocks. Every case is run **four times**: block by //! block, in pairs with a one-block remainder for odd lengths, as one call over the whole payload, //! and in 5-byte calls that never line up with a block. The second and third passes are what put -//! the multi-block cases through the pair and eight-block paths -- which for CFB are -//! [`ElectronicCodeBook::encrypt_2blocks`] and [`ElectronicCodeBook::encrypt_8blocks`], the +//! the multi-block cases through the pair and four-block paths -- which for CFB are +//! [`ElectronicCodeBook::encrypt_2blocks`] and [`ElectronicCodeBook::encrypt_4blocks`], the //! *forward* function, even on the decrypt side -- and the fourth is what puts them through the //! byte path with segments left open between calls. So all of that is exercised against real //! vectors and not only against the toys in `cfb_tests.rs`. Every ACVP CFB128 payload is a whole @@ -104,8 +104,8 @@ enum Grouping { Single, /// Two blocks per call, with a one-block remainder for odd lengths. Uses the pair path. Pairs, - /// The whole payload in one call: eights, then pairs, then the remaining block. The cases - /// spanning 8 to 10 blocks are the ones that reach `encrypt_8blocks`. + /// The whole payload in one call: fours, then pairs, then the remaining block. The cases + /// of four or more blocks are the ones that reach `encrypt_4blocks`. Whole, /// Five bytes per call, so every call but the first starts mid-segment and none is a whole /// block: the byte path, with the unused keystream carried between calls. diff --git a/crypto/modes/tests/acvp_ctr_tests.rs b/crypto/modes/tests/acvp_ctr_tests.rs index 2e51dcb6..d717b647 100644 --- a/crypto/modes/tests/acvp_ctr_tests.rs +++ b/crypto/modes/tests/acvp_ctr_tests.rs @@ -101,7 +101,7 @@ fn cipher_key(bytes: &[u8]) -> KeyMaterial { /// How to walk the bytes of one case. #[derive(Clone, Copy, PartialEq, Eq, Debug)] enum Grouping { - /// The whole payload in one call: eights, then pairs, then the remaining bytes singly. + /// The whole payload in one call: fours, then pairs, then the remaining bytes singly. Whole, /// One whole block per call. Blocks, diff --git a/crypto/modes/tests/acvp_ecb_tests.rs b/crypto/modes/tests/acvp_ecb_tests.rs index e529b0f7..f79abfb6 100644 --- a/crypto/modes/tests/acvp_ecb_tests.rs +++ b/crypto/modes/tests/acvp_ecb_tests.rs @@ -10,7 +10,7 @@ //! methods; this file is what pins that the mode adds nothing and loses nothing on the way: every //! case is run through the `BlockCipherEncryptor` / `BlockCipherDecryptor` API in three groupings //! -- block by block, in pairs with a remainder, and the whole payload in one hook call (which for -//! the 8-to-10-block cases reaches the eight-block path) -- in both directions. +//! the cases of four or more blocks reaches the four-block path) -- in both directions. //! //! Unlike the CBC and CFB response files, the ECB one records `key`, `pt` and `ct` for every case, //! so it is read alone and each case is checked in both directions regardless of its group's @@ -77,7 +77,7 @@ enum Grouping { Single, /// Two blocks per call, with a one-block remainder for odd lengths. Pairs, - /// The whole payload in one hook call: eights, then pairs, then the remainder. + /// The whole payload in one hook call: fours, then pairs, then the remainder. Whole, } @@ -160,7 +160,7 @@ fn acvp_aes_ecb_through_the_mode_api() { let mut checked = 0usize; let mut multi_block = 0usize; - let mut eight_or_more = 0usize; + let mut four_or_more = 0usize; let mut skipped_mct = 0usize; let mut per_key_len: BTreeMap = BTreeMap::new(); @@ -183,7 +183,7 @@ fn acvp_aes_ecb_through_the_mode_api() { let ct = to_blocks(&get("ct")); assert_eq!(pt.len(), ct.len(), "tcId {tc_id}: pt and ct differ in length"); multi_block += usize::from(pt.len() > 1); - eight_or_more += usize::from(pt.len() >= 8); + four_or_more += usize::from(pt.len() >= 4); for grouping in [Grouping::Single, Grouping::Pairs, Grouping::Whole] { assert_eq!( @@ -211,11 +211,11 @@ fn acvp_aes_ecb_through_the_mode_api() { } println!( "ACVP AES-ECB via Ecb: {checked} AFT cases checked in three groupings each \ - ({multi_block} multi-block, {eight_or_more} of eight or more blocks); {skipped_mct} MCT cases skipped" + ({multi_block} multi-block, {four_or_more} of four or more blocks); {skipped_mct} MCT cases skipped" ); // Guard against a silently-empty or partial run. assert!(checked > 2000, "expected the full ACVP AFT set, only checked {checked}"); - assert!(eight_or_more > 0, "expected cases that reach the eight-block path"); + assert!(four_or_more > 0, "expected cases that reach the four-block path"); assert_eq!(per_key_len.len(), 3, "expected all three key lengths"); } diff --git a/crypto/modes/tests/cbc_tests.rs b/crypto/modes/tests/cbc_tests.rs index cce5dbcc..90d4c3bd 100644 --- a/crypto/modes/tests/cbc_tests.rs +++ b/crypto/modes/tests/cbc_tests.rs @@ -12,11 +12,11 @@ use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; use bouncycastle_core_test_framework::electronic_code_book::TestFrameworkElectronicCodeBook; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkBlockCipher; use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; -use common::{SwappedEightToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use common::{SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; type ToyCbc

= Cbc; type SwappedCbc = Cbc; -type SwappedEightCbc = Cbc; +type SwappedFourCbc = Cbc; /// The implementor hook `do_encrypt_blocks`, by value, for tests whose data is block-shaped. fn enc_blocks( @@ -92,7 +92,7 @@ fn call_grouping_does_not_change_the_result() { let iv: [u8; TOY_LEN] = core::array::from_fn(|i| 0xF0 ^ (i as u8)); let pinned_rng = || bouncycastle_core_test_framework::FixedSeedRNG::::new(iv); - // Reference: all eight blocks in one call. + // Reference: all eight blocks in one call (two fours). let (mut enc, got_iv) = ToyCbc::::do_encrypt_init_rng(&key, &mut pinned_rng()).unwrap(); assert_eq!(got_iv, iv, "the pinned RNG should reproduce the IV"); @@ -186,51 +186,47 @@ fn the_pair_path_is_really_used() { assert_eq!([p0, p1], plaintext, "the single-block path must not pair"); } -/// The eight-block path in `do_decrypt_blocks` must actually be taken, and only for full eights. +/// The four-block path in `do_decrypt_blocks` must actually be taken, and only for full fours. /// -/// [`SwappedEightToy`] returns its eight results rotated while its pair and single-block methods -/// are correct. So a CBC decryptor that uses `decrypt_8blocks` gives the wrong answer for eight -/// blocks handed over together, and the right answer for the same eight blocks handed over as -/// two fours (pairs) or one at a time. Nine blocks are wrong too: eight, then one. +/// [`SwappedFourToy`] returns its four results rotated while its pair and single-block methods +/// are correct. So a CBC decryptor that uses `decrypt_4blocks` gives the wrong answer for four +/// blocks handed over together, and the right answer for the same four blocks handed over as +/// two pairs or one at a time. Five blocks are wrong too: four, then one. #[test] -fn the_eight_block_path_is_really_used() { +fn the_four_block_path_is_really_used() { let key = toy_key(); - let plaintext: [[u8; TOY_LEN]; 9] = core::array::from_fn(|i| [0x10 * i as u8 + 1; TOY_LEN]); + let plaintext: [[u8; TOY_LEN]; 5] = core::array::from_fn(|i| [0x10 * i as u8 + 1; TOY_LEN]); - // The correct toy round-trips nine blocks. + // The correct toy round-trips five blocks. let (mut enc, iv) = ToyCbc::::do_encrypt_init(&key).unwrap(); let ct = enc_blocks(&mut enc, &plaintext); let mut dec = ToyCbc::::do_decrypt_init(&key, &iv).unwrap(); assert_eq!(dec_blocks(&mut dec, &ct), plaintext); - // The rotated-eight toy encrypts identically (encryption is serial and never batches)... - let (mut enc, iv) = SwappedEightCbc::::do_encrypt_init(&key).unwrap(); + // The rotated-four toy encrypts identically (encryption is serial and never batches)... + let (mut enc, iv) = SwappedFourCbc::::do_encrypt_init(&key).unwrap(); let ct = enc_blocks(&mut enc, &plaintext); - // ...but decrypting nine together must be wrong, because the first eight take the eight path. - let mut dec = SwappedEightCbc::::do_decrypt_init(&key, &iv).unwrap(); - assert_ne!( - dec_blocks(&mut dec, &ct), - plaintext, - "eight blocks must go through decrypt_8blocks" - ); + // ...but decrypting five together must be wrong, because the first four take the four path. + let mut dec = SwappedFourCbc::::do_decrypt_init(&key, &iv).unwrap(); + assert_ne!(dec_blocks(&mut dec, &ct), plaintext, "four blocks must go through decrypt_4blocks"); - // Exactly eight together is wrong for the same reason. - let eight: [[u8; TOY_LEN]; 8] = ct[..8].try_into().unwrap(); - let mut dec = SwappedEightCbc::::do_decrypt_init(&key, &iv).unwrap(); - assert_ne!(&dec_blocks(&mut dec, &eight)[..], &plaintext[..8]); + // Exactly four together is wrong for the same reason. + let four: [[u8; TOY_LEN]; 4] = ct[..4].try_into().unwrap(); + let mut dec = SwappedFourCbc::::do_decrypt_init(&key, &iv).unwrap(); + assert_ne!(&dec_blocks(&mut dec, &four)[..], &plaintext[..4]); - // Two fours go through the pair path and are correct; so is the ninth block on its own. - let mut dec = SwappedEightCbc::::do_decrypt_init(&key, &iv).unwrap(); - let first: [[u8; TOY_LEN]; 4] = ct[..4].try_into().unwrap(); - let second: [[u8; TOY_LEN]; 4] = ct[4..8].try_into().unwrap(); + // Two pairs go through the pair path and are correct; so is the fifth block on its own. + let mut dec = SwappedFourCbc::::do_decrypt_init(&key, &iv).unwrap(); + let first: [[u8; TOY_LEN]; 2] = ct[..2].try_into().unwrap(); + let second: [[u8; TOY_LEN]; 2] = ct[2..4].try_into().unwrap(); assert_eq!( &dec_blocks(&mut dec, &first)[..], - &plaintext[..4], - "fewer than eight must not batch" + &plaintext[..2], + "fewer than four must not batch" ); - assert_eq!(&dec_blocks(&mut dec, &second)[..], &plaintext[4..8]); - assert_eq!(dec_flat(&mut dec, &ct[8]), plaintext[8]); + assert_eq!(&dec_blocks(&mut dec, &second)[..], &plaintext[2..4]); + assert_eq!(dec_flat(&mut dec, &ct[4]), plaintext[4]); } /// The flat streaming method must agree with the block-shaped implementor hook. diff --git a/crypto/modes/tests/cfb8_tests.rs b/crypto/modes/tests/cfb8_tests.rs index 058287ed..24eced43 100644 --- a/crypto/modes/tests/cfb8_tests.rs +++ b/crypto/modes/tests/cfb8_tests.rs @@ -19,12 +19,12 @@ use bouncycastle_core::traits::{ElectronicCodeBook, StreamCipherDecryptor, Strea use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkStreamCipher; use bouncycastle_modes::{Cbc, Cfb, Cfb8, Decrypting, Encrypting}; -use common::{ForwardOnlyToy, SwappedEightToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; type ToyCfb8 = Cfb8; type SwappedCfb8 = Cfb8; type ForwardOnlyCfb8 = Cfb8; -type SwappedEightCfb8 = Cfb8; +type SwappedFourCfb8 = Cfb8; /// `do_encrypt`, by value. fn enc(e: &mut impl StreamCipherEncryptor, plaintext: &[u8]) -> Vec { @@ -249,9 +249,9 @@ fn the_ciphertext_of_a_prefix_is_a_prefix_of_the_ciphertext() { /// SP 800-38A Sec 6.3: "The *forward cipher* function is applied to each input block to produce the /// output blocks" -- in CFB *decryption* as well as encryption. /// -/// [`ForwardOnlyToy`] panics from `decrypt_block`, `decrypt_2blocks` and `decrypt_8blocks`, so this +/// [`ForwardOnlyToy`] panics from `decrypt_block`, `decrypt_2blocks` and `decrypt_4blocks`, so this /// test fails loudly if either direction of the mode ever reaches the inverse cipher. Every decrypt -/// path is exercised -- eights, pairs and single bytes -- and the result is required to agree with +/// path is exercised -- fours, pairs and single bytes -- and the result is required to agree with /// the plain [`Toy`], otherwise the test could pass by not really encrypting anything. #[test] fn neither_direction_uses_the_inverse_cipher() { @@ -263,7 +263,7 @@ fn neither_direction_uses_the_inverse_cipher() { ForwardOnlyCfb8::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)).unwrap(); let ct = enc(&mut e, &plaintext); - // One call: two eights, then a pair, then a single byte. + // One call: four fours, then a pair, then a single byte. let mut d = ForwardOnlyCfb8::::do_decrypt_init(&key, &iv).unwrap(); assert_eq!(dec(&mut d, &ct), plaintext, "all paths, forward cipher only"); @@ -303,8 +303,8 @@ fn the_decryptor_shifts_in_ciphertext_not_plaintext() { /// at byte granularity. Every chunking in [`CHUNKINGS`] is checked against the one-call reference in /// both directions, and every encrypt chunking against every decrypt chunking. /// -/// For CFB8 the decrypt side is where this bites: chunk sizes that are not multiples of 8 leave the -/// eight-byte batch loop with a different remainder each call, so the register has to carry across +/// For CFB8 the decrypt side is where this bites: chunk sizes that are not multiples of 4 leave the +/// four-byte batch loop with a different remainder each call, so the register has to carry across /// calls correctly for every alignment. #[test] fn call_chunking_does_not_change_the_result() { @@ -350,7 +350,7 @@ fn call_chunking_does_not_change_the_result() { /// (`sp800_38a_cfb8_tests.rs`, `acvp_cfb8_tests.rs`) chunks against *published* ciphertext; this is /// the direct single-call-versus-chunked comparison. /// -/// The message is 171 bytes, which is 21 eight-byte batches and a 3-byte tail, so the chunkings +/// The message is 171 bytes, which is 42 four-byte batches and a 3-byte tail, so the chunkings /// leave the batch loop with a different remainder each time. #[test] fn aes_chunking_matches_a_single_call() { @@ -422,13 +422,13 @@ fn aes_chunking_matches_a_single_call() { /// is correct. CFB8 decryption batches through `encrypt_2blocks`, so with this permutation six /// bytes handed over together come out wrong while the same bytes one at a time come out right. /// -/// Six, not eight: the trait's default `encrypt_8blocks` is four `encrypt_2blocks` calls, so eight +/// Two, not four: the trait's default `encrypt_4blocks` is two `encrypt_2blocks` calls, so four /// bytes would also be wrong and would not distinguish the two paths. #[test] fn the_pair_path_is_really_used() { let key = toy_key(); let iv = pinned_iv(); - let plaintext = message(6); + let plaintext = message(2); // The correct toy round-trips. let ct = enc(&mut pinned_encryptor(iv), &plaintext); @@ -439,46 +439,46 @@ fn the_pair_path_is_really_used() { SwappedCfb8::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)).unwrap(); assert_eq!(enc(&mut e, &plaintext), ct, "CFB8 encryption must not use the pair path"); - // ...but decrypting six bytes together must now be wrong, because the pair path is used. + // ...but decrypting two bytes together must now be wrong, because the pair path is used. let mut d = SwappedCfb8::::do_decrypt_init(&key, &iv).unwrap(); - assert_ne!(dec(&mut d, &ct), plaintext, "three pairs must go through encrypt_2blocks"); + assert_ne!(dec(&mut d, &ct), plaintext, "a pair must go through encrypt_2blocks"); // One byte at a time avoids the pair path, so it is correct even for this toy. let mut d = SwappedCfb8::::do_decrypt_init(&key, &iv).unwrap(); assert_eq!(dec_chunked(&mut d, &ct, 1), plaintext, "the single-byte path must not pair"); } -/// The eight-byte batch path in `do_decrypt` must actually be taken, and only for full eights. +/// The four-byte batch path in `do_decrypt` must actually be taken, and only for full fours. /// -/// [`SwappedEightToy`] returns its eight `encrypt_8blocks` results rotated while its pair and -/// single-block methods are correct. So nine bytes handed over together decrypt wrongly (eight -/// batched, then one), while six bytes (pairs) or one at a time decrypt correctly. +/// [`SwappedFourToy`] returns its four `encrypt_4blocks` results rotated while its pair and +/// single-block methods are correct. So five bytes handed over together decrypt wrongly (four +/// batched, then one), while two bytes (a pair) or one at a time decrypt correctly. #[test] -fn the_eight_byte_path_is_really_used() { +fn the_four_byte_path_is_really_used() { let key = toy_key(); let iv = pinned_iv(); - let plaintext = message(9); + let plaintext = message(5); let ct = enc(&mut pinned_encryptor(iv), &plaintext); assert_eq!(dec(&mut pinned_decryptor(iv), &ct), plaintext); - // The rotated-eight toy encrypts identically: CFB8 encryption is serial and never batches. + // The rotated-four toy encrypts identically: CFB8 encryption is serial and never batches. let (mut e, _) = - SwappedEightCfb8::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)).unwrap(); - assert_eq!(enc(&mut e, &plaintext), ct, "CFB8 encryption must not use the eight path"); + SwappedFourCfb8::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)).unwrap(); + assert_eq!(enc(&mut e, &plaintext), ct, "CFB8 encryption must not use the four path"); - // ...but nine bytes together must now be wrong, because the first eight go through - // encrypt_8blocks. - let mut d = SwappedEightCfb8::::do_decrypt_init(&key, &iv).unwrap(); - assert_ne!(dec(&mut d, &ct), plaintext, "nine bytes must go through encrypt_8blocks"); + // ...but five bytes together must now be wrong, because the first four go through + // encrypt_4blocks. + let mut d = SwappedFourCfb8::::do_decrypt_init(&key, &iv).unwrap(); + assert_ne!(dec(&mut d, &ct), plaintext, "five bytes must go through encrypt_4blocks"); - // Six bytes use the pair path only, so they are correct even for this toy... - let six = &ct[..6]; - let mut d = SwappedEightCfb8::::do_decrypt_init(&key, &iv).unwrap(); - assert_eq!(dec(&mut d, six), plaintext[..6], "pairs must not use the eight path"); + // Two bytes use the pair path only, so they are correct even for this toy... + let two = &ct[..2]; + let mut d = SwappedFourCfb8::::do_decrypt_init(&key, &iv).unwrap(); + assert_eq!(dec(&mut d, two), plaintext[..2], "pairs must not use the four path"); // ...and so is one byte at a time. - let mut d = SwappedEightCfb8::::do_decrypt_init(&key, &iv).unwrap(); + let mut d = SwappedFourCfb8::::do_decrypt_init(&key, &iv).unwrap(); assert_eq!(dec_chunked(&mut d, &ct, 1), plaintext, "the single-byte path must not batch"); } diff --git a/crypto/modes/tests/cfb_tests.rs b/crypto/modes/tests/cfb_tests.rs index 7a0ab993..812f592b 100644 --- a/crypto/modes/tests/cfb_tests.rs +++ b/crypto/modes/tests/cfb_tests.rs @@ -1,7 +1,7 @@ //! Structural tests for CFB, driven by a toy permutation. //! //! These check the properties of the *mode* -- the keystream construction, chaining, call -//! sequencing at arbitrary byte boundaries, the short final segment, the pair/eight-block split on +//! sequencing at arbitrary byte boundaries, the short final segment, the pair/four-block split on //! the decrypt side, direction typing, SP 800-38A Appendix D error propagation, and the "forward //! cipher function only" rule of Sec 6.3 -- independently of any real cipher. The known-answer //! tests against SP 800-38A Appendix F.3.13-F.3.18 are in `sp800_38a_cfb_tests.rs`, and the ACVP @@ -21,12 +21,12 @@ use bouncycastle_core::traits::{ use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkStreamCipher; use bouncycastle_modes::{Cbc, Cfb, Decrypting, Encrypting}; -use common::{ForwardOnlyToy, SwappedEightToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; type ToyCfb = Cfb; type SwappedCfb = Cfb; type ForwardOnlyCfb = Cfb; -type SwappedEightCfb = Cfb; +type SwappedFourCfb = Cfb; /// `do_encrypt`, by value. fn enc(e: &mut impl StreamCipherEncryptor, plaintext: &[u8]) -> Vec { @@ -264,9 +264,9 @@ fn the_ciphertext_of_a_prefix_is_a_prefix_of_the_ciphertext() { /// SP 800-38A Sec 6.3: "The *forward cipher* function is applied to each input block to produce the /// output blocks" -- in CFB *decryption* as well as encryption. /// -/// [`ForwardOnlyToy`] panics from `decrypt_block`, `decrypt_2blocks` and `decrypt_8blocks`, so this +/// [`ForwardOnlyToy`] panics from `decrypt_block`, `decrypt_2blocks` and `decrypt_4blocks`, so this /// test fails loudly if either direction of the mode ever reaches the inverse cipher. Every -/// decrypt path is exercised -- the eight-block, pair, single-block and byte paths -- and the result +/// decrypt path is exercised -- the four-block, pair, single-block and byte paths -- and the result /// is required to agree with the plain [`Toy`], otherwise the test could pass by not really /// encrypting anything. #[test] @@ -279,7 +279,7 @@ fn neither_direction_uses_the_inverse_cipher() { ForwardOnlyCfb::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)).unwrap(); let ct = enc(&mut e, &plaintext); - // One call: eight blocks, then a pair, then a single, then the short segment. + // One call: two fours, then a pair, then a single, then the short segment. let mut d = ForwardOnlyCfb::::do_decrypt_init(&key, &iv).unwrap(); assert_eq!(dec(&mut d, &ct), plaintext, "all paths, forward cipher only"); @@ -381,7 +381,7 @@ fn call_chunking_does_not_change_the_result() { /// the direct single-call-versus-chunked comparison. /// /// The message is 171 bytes: not a whole number of blocks, so every chunking ends on a short final -/// segment, and long enough to run the decryptor's eight-block batch ten times over. +/// segment, and long enough to run the decryptor's four-block batch several times over. #[test] fn aes_chunking_matches_a_single_call() { fn check(name: &str) @@ -486,43 +486,43 @@ fn the_pair_path_is_really_used() { assert_eq!(got, plaintext, "a pair not at a segment boundary is not a pair"); } -/// The eight-block path in `do_decrypt` must actually be taken, and only for full eights. +/// The four-block path in `do_decrypt` must actually be taken, and only for full fours. /// -/// [`SwappedEightToy`] returns its eight `encrypt_8blocks` results rotated while its pair and -/// single-block methods are correct. CFB decryption batches eights through the *forward* -/// `encrypt_8blocks`, so with this permutation nine blocks handed over together decrypt wrongly -/// (eight rotated, then one), while the same blocks handed over as two fours (pairs) or one at a -/// time decrypt correctly. Encryption is serial and never batches, so it is unaffected. +/// [`SwappedFourToy`] returns its four `encrypt_4blocks` results rotated while its pair and +/// single-block methods are correct. CFB decryption batches fours through the *forward* +/// `encrypt_4blocks`, so with this permutation five blocks handed over together decrypt wrongly +/// (four rotated, then one), while the same blocks handed over as two pairs or one at a time +/// decrypt correctly. Encryption is serial and never batches, so it is unaffected. #[test] -fn the_eight_block_path_is_really_used() { +fn the_four_block_path_is_really_used() { let key = toy_key(); let iv = pinned_iv(); - let plaintext = message(9 * TOY_LEN); + let plaintext = message(5 * TOY_LEN); - // The correct toy round-trips nine blocks. + // The correct toy round-trips five blocks. let ct = enc(&mut pinned_encryptor(iv), &plaintext); assert_eq!(dec(&mut pinned_decryptor(iv), &ct), plaintext); - // The rotated-eight toy encrypts identically: CFB encryption is serial and never batches. + // The rotated-four toy encrypts identically: CFB encryption is serial and never batches. let (mut e, _) = - SwappedEightCfb::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)).unwrap(); - assert_eq!(enc(&mut e, &plaintext), ct, "CFB encryption must not use the eight path"); + SwappedFourCfb::::do_encrypt_init_rng(&key, &mut pinned_rng(iv)).unwrap(); + assert_eq!(enc(&mut e, &plaintext), ct, "CFB encryption must not use the four path"); - // ...but nine blocks together must now be wrong, because the first eight go through - // encrypt_8blocks. - let mut d = SwappedEightCfb::::do_decrypt_init(&key, &iv).unwrap(); - assert_ne!(dec(&mut d, &ct), plaintext, "nine blocks must go through encrypt_8blocks"); + // ...but five blocks together must now be wrong, because the first four go through + // encrypt_4blocks. + let mut d = SwappedFourCfb::::do_decrypt_init(&key, &iv).unwrap(); + assert_ne!(dec(&mut d, &ct), plaintext, "five blocks must go through encrypt_4blocks"); - // Two fours use the pair path only, so they are correct even for this toy... - let mut d = SwappedEightCfb::::do_decrypt_init(&key, &iv).unwrap(); + // Pairs use the pair path only, so they are correct even for this toy... + let mut d = SwappedFourCfb::::do_decrypt_init(&key, &iv).unwrap(); assert_eq!( - dec_chunked(&mut d, &ct, 4 * TOY_LEN), + dec_chunked(&mut d, &ct, 2 * TOY_LEN), plaintext, - "fours must not use the eight path" + "pairs must not use the four path" ); // ...and so is one block at a time. - let mut d = SwappedEightCfb::::do_decrypt_init(&key, &iv).unwrap(); + let mut d = SwappedFourCfb::::do_decrypt_init(&key, &iv).unwrap(); assert_eq!( dec_chunked(&mut d, &ct, TOY_LEN), plaintext, diff --git a/crypto/modes/tests/common/mod.rs b/crypto/modes/tests/common/mod.rs index 93195856..3307fa17 100644 --- a/crypto/modes/tests/common/mod.rs +++ b/crypto/modes/tests/common/mod.rs @@ -123,14 +123,14 @@ impl ElectronicCodeBook for SwappedPairToy { /// A toy whose **inverse cipher function panics**. /// /// SP 800-38A Sec 6.3 applies the forward cipher function in both directions of CFB, so a correct -/// `Cfb` never touches `decrypt_block`, `decrypt_2blocks` or `decrypt_8blocks`. Running a full CFB round trip over this +/// `Cfb` never touches `decrypt_block`, `decrypt_2blocks` or `decrypt_4blocks`. Running a full CFB round trip over this /// permutation turns that claim into a test: if either decryption entry point is ever reached, the /// test panics with the message below rather than quietly producing a right answer for the wrong /// reason. /// /// This is deliberately not a valid [`ElectronicCodeBook`] -- it cannot pass /// `TestFrameworkElectronicCodeBook`, which exercises both directions -- so it is only ever used with -/// `Cfb`. Its forward methods delegate to [`Toy`], including the pair and eight-block methods, so a CFB round trip +/// `Cfb`. Its forward methods delegate to [`Toy`], including the pair and four-block methods, so a CFB round trip /// over it must agree with one over `Toy`. pub struct ForwardOnlyToy { inner: Toy, @@ -162,31 +162,31 @@ impl ElectronicCodeBook for ForwardOnlyToy { panic!("CFB must never call the inverse cipher pair function (SP 800-38A Sec 6.3)"); } - fn encrypt_8blocks(&self, blocks: &mut [[u8; TOY_LEN]; 8]) { - self.inner.encrypt_8blocks(blocks); + fn encrypt_4blocks(&self, blocks: &mut [[u8; TOY_LEN]; 4]) { + self.inner.encrypt_4blocks(blocks); } - fn decrypt_8blocks(&self, _blocks: &mut [[u8; TOY_LEN]; 8]) { - panic!("CFB must never call the inverse cipher eight-block function (SP 800-38A Sec 6.3)"); + fn decrypt_4blocks(&self, _blocks: &mut [[u8; TOY_LEN]; 4]) { + panic!("CFB must never call the inverse cipher four-block function (SP 800-38A Sec 6.3)"); } } -/// A [`Toy`] whose `encrypt_8blocks` / `decrypt_8blocks` return their eight results rotated by one +/// A [`Toy`] whose `encrypt_4blocks` / `decrypt_4blocks` return their four results rotated by one /// slot, while every other method -- single block and pair -- is correct. /// -/// The eight-block analogue of [`SwappedPairToy`]: a CBC decryptor that uses `decrypt_8blocks` -/// must produce something other than the correct plaintext for eight or more blocks, while fewer -/// than eight, which go through the pair and single paths, still round-trip. -pub struct SwappedEightToy { +/// The four-block analogue of [`SwappedPairToy`]: a CBC decryptor that uses `decrypt_4blocks` +/// must produce something other than the correct plaintext for four or more blocks, while fewer +/// than four, which go through the pair and single paths, still round-trip. +pub struct SwappedFourToy { inner: Toy, } -impl Algorithm for SwappedEightToy { - const ALG_NAME: &'static str = "SwappedEightToy"; +impl Algorithm for SwappedFourToy { + const ALG_NAME: &'static str = "SwappedFourToy"; const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit; } -impl ElectronicCodeBook for SwappedEightToy { +impl ElectronicCodeBook for SwappedFourToy { fn new(key: &KeyMaterial) -> Result { Ok(Self { inner: Toy::new(key)? }) } @@ -199,14 +199,14 @@ impl ElectronicCodeBook for SwappedEightToy { self.inner.decrypt_block(block); } - fn encrypt_8blocks(&self, blocks: &mut [[u8; TOY_LEN]; 8]) { + fn encrypt_4blocks(&self, blocks: &mut [[u8; TOY_LEN]; 4]) { for block in blocks.iter_mut() { self.inner.encrypt_block(block); } blocks.rotate_left(1); } - fn decrypt_8blocks(&self, blocks: &mut [[u8; TOY_LEN]; 8]) { + fn decrypt_4blocks(&self, blocks: &mut [[u8; TOY_LEN]; 4]) { for block in blocks.iter_mut() { self.inner.decrypt_block(block); } diff --git a/crypto/modes/tests/ctr_tests.rs b/crypto/modes/tests/ctr_tests.rs index e56b1381..40c5318f 100644 --- a/crypto/modes/tests/ctr_tests.rs +++ b/crypto/modes/tests/ctr_tests.rs @@ -30,14 +30,14 @@ use bouncycastle_core::traits::{ElectronicCodeBook, StreamCipherDecryptor, Strea use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkStreamCipher; use bouncycastle_modes::{Ctr, Decrypting, Encrypting}; -use common::{ForwardOnlyToy, SwappedEightToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use common::{ForwardOnlyToy, SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; /// The default shape under test: a 12-byte nonce, so a 4-byte counter. const NONCE_LEN: usize = 12; type ToyCtr = Ctr; type SwappedCtr = Ctr; type ForwardOnlyCtr = Ctr; -type SwappedEightCtr = Ctr; +type SwappedFourCtr = Ctr; /// A 15-byte nonce leaves a **1-byte** counter, so the whole counter space is 256 blocks -- 4 KiB /// of keystream. That makes the exhaustion behaviour reachable in a test. @@ -589,34 +589,34 @@ fn the_pair_path_is_really_used_in_both_directions() { assert_ne!(back, plaintext, "CTR decryption must use the pair path"); } -/// The eight-block path must be taken, in both directions, and only for full eights. +/// The four-block path must be taken, in both directions, and only for full fours. #[test] -fn the_eight_block_path_is_really_used_in_both_directions() { +fn the_four_block_path_is_really_used_in_both_directions() { let key = toy_key(); let nonce = pinned_nonce(); - let plaintext = message(9 * TOY_LEN); + let plaintext = message(5 * TOY_LEN); let ct = enc(&mut pinned_encryptor(nonce), &plaintext); let (mut e, _) = - SwappedEightCtr::::do_encrypt_init_rng(&key, &mut pinned_rng(nonce)).unwrap(); + SwappedFourCtr::::do_encrypt_init_rng(&key, &mut pinned_rng(nonce)).unwrap(); let mut swapped = plaintext.clone(); e.do_encrypt(&mut swapped).unwrap(); - assert_ne!(swapped, ct, "nine blocks must go through encrypt_8blocks"); + assert_ne!(swapped, ct, "five blocks must go through encrypt_4blocks"); - // Four blocks at a time uses pairs only, so the rotated-eight toy is correct there. + // Two blocks at a time uses pairs only, so the rotated-four toy is correct there. let (mut e, _) = - SwappedEightCtr::::do_encrypt_init_rng(&key, &mut pinned_rng(nonce)).unwrap(); - let mut fours = plaintext.clone(); - for piece in fours.chunks_mut(4 * TOY_LEN) { + SwappedFourCtr::::do_encrypt_init_rng(&key, &mut pinned_rng(nonce)).unwrap(); + let mut pairs = plaintext.clone(); + for piece in pairs.chunks_mut(2 * TOY_LEN) { e.do_encrypt(piece).unwrap(); } - assert_eq!(fours, ct, "fours must not use the eight path"); + assert_eq!(pairs, ct, "pairs must not use the four path"); - let mut d = SwappedEightCtr::::do_decrypt_init(&key, &nonce).unwrap(); + let mut d = SwappedFourCtr::::do_decrypt_init(&key, &nonce).unwrap(); let mut back = ct.clone(); d.do_decrypt(&mut back).unwrap(); - assert_ne!(back, plaintext, "decryption must batch eights too"); + assert_ne!(back, plaintext, "decryption must batch fours too"); } // ---- nonce handling ------------------------------------------------------------------------ diff --git a/crypto/modes/tests/ctr_vector_tests.rs b/crypto/modes/tests/ctr_vector_tests.rs index ef85dd34..116dcc6b 100644 --- a/crypto/modes/tests/ctr_vector_tests.rs +++ b/crypto/modes/tests/ctr_vector_tests.rs @@ -89,7 +89,7 @@ fn key_material(hex_str: &str) -> KeyMaterial { .expect("a valid symmetric cipher key") } -/// Chunk sizes that cut across the block and the eight-block batch, so the vectors are reproduced +/// Chunk sizes that cut across the block and the four-block batch, so the vectors are reproduced /// through every path rather than only the batched one. const CHUNKINGS: [usize; 6] = [1, 5, 16, 17, 33, 69]; diff --git a/crypto/modes/tests/ecb_tests.rs b/crypto/modes/tests/ecb_tests.rs index ac7ceec3..28db8aaa 100644 --- a/crypto/modes/tests/ecb_tests.rs +++ b/crypto/modes/tests/ecb_tests.rs @@ -1,7 +1,7 @@ //! Structural tests for ECB, driven by a toy permutation. //! //! These check the properties of the *mode* -- that it is the permutation applied block by block -//! with nothing chained, that both directions batch through the pair and eight-block paths, call +//! with nothing chained, that both directions batch through the pair and four-block paths, call //! sequencing, direction typing, the empty init data, SP 800-38A Appendix D error propagation, and //! the codebook property that makes ECB unsuitable for data -- independently of any real cipher. The //! known-answer tests against SP 800-38A Appendix F.1 are in `sp800_38a_ecb_tests.rs`, and the ACVP @@ -22,11 +22,11 @@ use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkBlockCipher; use bouncycastle_modes::{Cbc, Decrypting, Ecb, Encrypting}; use bouncycastle_padding::{PKCS7, PaddedDecryptor, PaddedEncryptor}; -use common::{SwappedEightToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; +use common::{SwappedFourToy, SwappedPairToy, TOY_LEN, Toy, toy_key}; type ToyEcb = Ecb; type SwappedEcb = Ecb; -type SwappedEightEcb = Ecb; +type SwappedFourEcb = Ecb; /// The implementor hook `do_encrypt_blocks`, by value, for tests whose data is block-shaped. fn enc_blocks( @@ -205,7 +205,7 @@ fn the_rng_constructor_draws_nothing() { assert_eq!(block, enc_flat(&mut encryptor(), &[0x42u8; TOY_LEN])); } -// ---- batching: pairs and eights, in both directions --------------------------------------- +// ---- batching: pairs and fours, in both directions ---------------------------------------- /// Sec 6.1: "multiple forward cipher functions and inverse cipher functions can be computed in /// parallel" -- so, unlike CBC and CFB, *both* directions batch. [`SwappedPairToy`] swaps its two @@ -237,35 +237,31 @@ fn the_pair_path_is_used_in_both_directions() { assert_eq!([dec_flat(&mut dec, &ct[0]), dec_flat(&mut dec, &ct[1])], plaintext); } -/// The eight-block path must be taken, and only for full eights, in both directions. -/// [`SwappedEightToy`] rotates its eight results while its pair and single-block methods are -/// correct, so nine blocks handed over together are wrong (eight rotated, then one right) and the -/// same blocks as two fours or singly are right. +/// The four-block path must be taken, and only for full fours, in both directions. +/// [`SwappedFourToy`] rotates its four results while its pair and single-block methods are +/// correct, so five blocks handed over together are wrong (four rotated, then one right) and the +/// same blocks as two pairs or singly are right. #[test] -fn the_eight_block_path_is_used_in_both_directions() { +fn the_four_block_path_is_used_in_both_directions() { let key = toy_key(); - let plaintext: [[u8; TOY_LEN]; 9] = core::array::from_fn(|i| [0x10 * i as u8 + 1; TOY_LEN]); + let plaintext: [[u8; TOY_LEN]; 5] = core::array::from_fn(|i| [0x10 * i as u8 + 1; TOY_LEN]); let ct = enc_blocks(&mut encryptor(), &plaintext); assert_eq!(dec_blocks(&mut decryptor(), &ct), plaintext); - let (mut enc, _) = SwappedEightEcb::::do_encrypt_init(&key).unwrap(); + let (mut enc, _) = SwappedFourEcb::::do_encrypt_init(&key).unwrap(); let rotated = enc_blocks(&mut enc, &plaintext); - assert_ne!(rotated, ct, "nine blocks must go through encrypt_8blocks"); - assert_eq!(rotated[8], ct[8], "the ninth block goes through the single path and is right"); - assert_eq!( - &rotated[..8], - &[ct[1], ct[2], ct[3], ct[4], ct[5], ct[6], ct[7], ct[0]], - "eight rotated" - ); - - let (mut enc, _) = SwappedEightEcb::::do_encrypt_init(&key).unwrap(); - let a = enc_blocks(&mut enc, &[plaintext[0], plaintext[1], plaintext[2], plaintext[3]]); - let b = enc_blocks(&mut enc, &[plaintext[4], plaintext[5], plaintext[6], plaintext[7]]); - assert_eq!([a, b].as_flattened(), &ct[..8], "fours use the pair path only"); - - let mut dec = SwappedEightEcb::::do_decrypt_init(&key, &[]).unwrap(); - assert_ne!(dec_blocks(&mut dec, &ct), plaintext, "nine blocks must go through decrypt_8blocks"); - let mut dec = SwappedEightEcb::::do_decrypt_init(&key, &[]).unwrap(); + assert_ne!(rotated, ct, "five blocks must go through encrypt_4blocks"); + assert_eq!(rotated[4], ct[4], "the fifth block goes through the single path and is right"); + assert_eq!(&rotated[..4], &[ct[1], ct[2], ct[3], ct[0]], "four rotated"); + + let (mut enc, _) = SwappedFourEcb::::do_encrypt_init(&key).unwrap(); + let a = enc_blocks(&mut enc, &[plaintext[0], plaintext[1]]); + let b = enc_blocks(&mut enc, &[plaintext[2], plaintext[3]]); + assert_eq!([a, b].as_flattened(), &ct[..4], "pairs use the pair path only"); + + let mut dec = SwappedFourEcb::::do_decrypt_init(&key, &[]).unwrap(); + assert_ne!(dec_blocks(&mut dec, &ct), plaintext, "five blocks must go through decrypt_4blocks"); + let mut dec = SwappedFourEcb::::do_decrypt_init(&key, &[]).unwrap(); for (c, p) in ct.iter().zip(plaintext.iter()) { assert_eq!(&dec_flat(&mut dec, c), p, "the single-block path must not batch"); } @@ -287,7 +283,7 @@ fn call_grouping_does_not_change_the_result() { got[3..11].copy_from_slice(&enc_blocks(&mut enc, &rest)); assert_eq!(got, reference); - for grouping in [1usize, 2, 8, 11] { + for grouping in [1usize, 2, 4, 5, 8, 11] { let mut dec = decryptor(); let mut out = Vec::new(); for chunk in reference.chunks(grouping) { diff --git a/crypto/modes/tests/sp800_38a_cfb8_tests.rs b/crypto/modes/tests/sp800_38a_cfb8_tests.rs index 59b10187..de6ed2c3 100644 --- a/crypto/modes/tests/sp800_38a_cfb8_tests.rs +++ b/crypto/modes/tests/sp800_38a_cfb8_tests.rs @@ -121,9 +121,9 @@ fn key_material(hex_str: &str) -> KeyMaterial { .expect("a valid symmetric cipher key") } -/// Chunk sizes that cut across the eight-byte batch and the 16-byte block: 1 is the single-byte -/// path only, 8 is exactly the batch, and the rest leave a different remainder each call. -const CHUNKINGS: [usize; 6] = [1, 3, 8, 9, 17, 18]; +/// Chunk sizes that cut across the four-byte batch and the 16-byte block: 1 is the single-byte +/// path only, 4 is exactly the batch, 8 is two, and the rest leave a different remainder each call. +const CHUNKINGS: [usize; 7] = [1, 3, 4, 8, 9, 17, 18]; /// Runs one Appendix F.3 CFB8 encrypt subsection. /// From c534f3b5e9ed41e92dd921cb47618a40a9f6ec29 Mon Sep 17 00:00:00 2001 From: David Hook Date: Wed, 9 Sep 2026 13:50:37 +1000 Subject: [PATCH 12/14] release notes: the block-cipher trait section names the four-block methods by their current name --- alpha_0.1.3_release_notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index d81a5ddb..27640723 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -350,7 +350,7 @@ ECB (`Ecb`), SP 800-38A Sec 6.1: always gets encrypted to the same ciphertext block"). One block smaller than `Cbc` / `Cfb`, since nothing chains (176 / 208 / 240 B for AES-128/192/256). * **Both directions batch.** Sec 6.1 allows forward and inverse cipher calls "to be computed in parallel", so encryption - as well as decryption walks the blocks through `ElectronicCodeBook::{en,de}crypt_blocks8`, then the pair methods, then + as well as decryption walks the blocks through `ElectronicCodeBook::{en,de}crypt_4blocks`, then the pair methods, then a single block. The swapped-pair and rotated-four test permutations prove both paths are taken in both directions. * `aes128-ecb` / `aes192-ecb` / `aes256-ecb` CLI subcommands over the shared block-mode plumbing, which is now generic over `INIT_DATA_LEN`: nothing is prepended on `encrypt` or consumed on `decrypt`, so output is exactly as long as From 16552e965a177d139d720cb9246c151c67804bac Mon Sep 17 00:00:00 2001 From: David Hook Date: Wed, 9 Sep 2026 14:55:10 +1000 Subject: [PATCH 13/14] core: SymmetricCipherEncryptor / SymmetricCipherDecryptor are SimpleCipherEncryptor / SimpleCipherDecryptor; the framework suite becomes TestFrameworkSimpleCipher and the modes API test file is renamed to match; aes, padding, modes and the notes follow --- alpha_0.1.3_release_notes.md | 20 +++--- crypto/aes/src/cbc.rs | 16 ++--- crypto/aes/src/ecb.rs | 12 ++-- crypto/aes/src/lib.rs | 2 +- crypto/aes/tests/cbc_alias_tests.rs | 6 +- crypto/aes/tests/ecb_alias_tests.rs | 6 +- .../src/symmetric_ciphers.rs | 13 ++-- crypto/core-test-framework/summary.md | 4 +- crypto/core/src/traits.rs | 26 +++---- crypto/modes/src/lib.rs | 8 +-- crypto/modes/tests/ecb_tests.rs | 4 +- ...pi_tests.rs => simple_cipher_api_tests.rs} | 69 ++++++++----------- crypto/padding/src/padded.rs | 12 ++-- crypto/padding/tests/padded_tests.rs | 12 ++-- 14 files changed, 100 insertions(+), 110 deletions(-) rename crypto/modes/tests/{symmetric_cipher_api_tests.rs => simple_cipher_api_tests.rs} (83%) diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index 27640723..93dc18bb 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -373,8 +373,8 @@ are infallible; only `new` can fail, and only on the key. `bouncycastle-aes` imp it for all three key lengths (the data-encryption traits are still deliberately not implemented there). -`core`: new `SymmetricCipherEncryptor` and -`SymmetricCipherDecryptor` traits, the arbitrary-length data API a +`core`: new `SimpleCipherEncryptor` and +`SimpleCipherDecryptor` traits, the arbitrary-length data API a caller uses, as opposed to the block-aligned `BlockCipher*` traits a mode implements. Their shape is taken from `PaddedEncryptor` / `PaddedDecryptor`, which now implement them: streaming `do_{en,de}crypt_init[_rng]`, exact `update_out_len`, `do_update_out`, and a consuming `do_final` that @@ -388,11 +388,11 @@ methods, so an implementor writes six methods. The older one-shot-only `SymmetricCipher` trait is **deleted**, and its four methods -- `encrypt`, `encrypt_out`, `decrypt`, `decrypt_out` -- move onto `AEADCipher`, which was its only remaining user. Every other kind of cipher now reaches an arbitrary-length one-shot some other way: a block -mode through `SymmetricCipherEncryptor` / `SymmetricCipherDecryptor` and the padding adapters, a +mode through `SimpleCipherEncryptor` / `SimpleCipherDecryptor` and the padding adapters, a stream mode through those same traits directly. `AEADCipher` therefore drops the supertrait and declares the four itself, against `NONCE_LEN`, with the documentation saying what they mean for an AEAD: no additional authenticated data, and a ciphertext layout that is the implementation's -business because the tag has to go somewhere. `TestFrameworkSymmetricCipher::test`, which was that +business because the tag has to go somewhere. `TestFrameworkSimpleCipher::test`, which was that trait's suite, moves to `TestFrameworkAEADCipher::test_plain_one_shots` and is called from `TestFrameworkAEADCipher::test`, so an AEAD implementor keeps the coverage without asking for it. @@ -404,16 +404,16 @@ made that worse, so both now carry the same key-length guard the block and strea had. Every strength loop in the file is guarded. Stream ciphers also reach the arbitrary-length API: `StreamCipherEncryptor` and -`StreamCipherDecryptor` get blanket impls of `SymmetricCipherEncryptor` / `SymmetricCipherDecryptor` +`StreamCipherDecryptor` get blanket impls of `SimpleCipherEncryptor` / `SimpleCipherDecryptor` with `FINAL_LEN = 0`, written in terms of the in-place `do_encrypt` / `do_decrypt`. An implementor still writes only the in-place methods, but a caller can use `encrypt_out`, `do_update_out` and the `std` one-shots, and can hold a stream mode through the same trait as a padded block mode -- which is what makes "any of the five modes behind one trait" true rather than aspirational. For a stream cipher the length predictions are exact rather than upper bounds, and `do_final` has nothing to produce. The one cost is that both traits then spell `do_encrypt_init` identically, so code with -both in scope must qualify the call; `crypto/modes/tests/symmetric_cipher_api_tests.rs` is written +both in scope must qualify the call; `crypto/modes/tests/simple_cipher_api_tests.rs` is written that way deliberately, to show it is workable. That file also runs all three stream modes through -`TestFrameworkSymmetricCipher::test_encryptor_decryptor`, the same conformance suite the padded +`TestFrameworkSimpleCipher::test_encryptor_decryptor`, the same conformance suite the padded adapters run, and checks the separate-output API against the in-place one byte for byte. Mutation-tested with `--test-workspace`, which is what these blanket impls need: run against core's @@ -433,7 +433,7 @@ one-shots over a single implementor hook per direction. `Cfb` and `Cfb8` are its Testing: -* `core-test-framework` gains `TestFrameworkSymmetricCipher::test_encryptor_decryptor`, which pins the +* `core-test-framework` gains `TestFrameworkSimpleCipher::test_encryptor_decryptor`, which pins the paired contract: one-shot round trips at every length up to a few final chunks, the `std` one-shots against the `_out` ones, streaming in eight chunkings with `update_out_len` exact on every call, `do_final_out` against `do_final`, a driven RNG reproducing its init data and determining the @@ -447,7 +447,7 @@ Testing: five strengths, which a key shorter than 32 bytes cannot carry, so the framework panicked for any 16- or 24-byte key. It now skips the strengths the key length cannot hold. The bug was invisible until now because nothing in the workspace implemented the block cipher traits. The - identical loop in `TestFrameworkSymmetricCipher` and `TestFrameworkAEADCipher` is still unfixed; + identical loop in `TestFrameworkSimpleCipher` and `TestFrameworkAEADCipher` is still unfixed; both still have no implementors, so it stays latent. * `TestFrameworkStreamCipher::test` was a `todo!()` and is now implemented for the `StreamCipherEncryptor` / `StreamCipherDecryptor` pair, carrying the same key-length guard as the @@ -476,7 +476,7 @@ Testing: block as data; `ALWAYS_PADS` is false. Through `PaddedEncryptor` / `PaddedDecryptor` this *enforces* alignment with the arbitrary-length API shape: an aligned message passes through with its length unchanged and no final block, an unaligned one fails at `do_final` / `encrypt_out`, and an empty ciphertext decrypts to the empty - message. The test framework's `TestFrameworkSymmetricCipher` gained `required_alignment`, which makes it assert + message. The test framework's `TestFrameworkSimpleCipher` gained `required_alignment`, which makes it assert that every unaligned length is refused. * Tests are derived from the RFC 5652 padding rule; the adapters are driven with a toy XOR-CBC cipher implementing the new block cipher traits, covering every data length, ten chunkings in both directions, tampering, malformed diff --git a/crypto/aes/src/cbc.rs b/crypto/aes/src/cbc.rs index 2c80bfca..d31b103e 100644 --- a/crypto/aes/src/cbc.rs +++ b/crypto/aes/src/cbc.rs @@ -27,7 +27,7 @@ //! //! # These are the arbitrary-length API //! -//! A padded alias implements [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`], not the +//! A padded alias implements [`SimpleCipherEncryptor`] / [`SimpleCipherDecryptor`], not the //! block traits: `encrypt_out` / `decrypt_out` and the streaming `do_update_out` / `do_final`, all //! taking a `&[u8]` of any length. The block-aligned API, with its compile-time length checks and //! its in-place data methods, is `bouncycastle_modes::Cbc` itself, which these wrap: @@ -52,7 +52,7 @@ use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; // Imports needed for docs #[allow(unused_imports)] -use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; #[allow(unused_imports)] use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; // end of imports needed for docs @@ -66,7 +66,7 @@ use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; /// ``` /// use bouncycastle_aes::AES_CBC_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +/// use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; /// use bouncycastle_padding::PKCS7; /// @@ -93,7 +93,7 @@ use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; /// ``` /// use bouncycastle_aes::AES_CBC_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::SymmetricCipherEncryptor; +/// use bouncycastle_core::traits::SimpleCipherEncryptor; /// use bouncycastle_modes::Encrypting; /// use bouncycastle_padding::NoPadding; /// @@ -117,7 +117,7 @@ use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; /// ```compile_fail /// use bouncycastle_aes::AES_CBC_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::SymmetricCipherEncryptor; +/// use bouncycastle_core::traits::SimpleCipherEncryptor; /// use bouncycastle_modes::Encrypting; /// use bouncycastle_padding::{NoPadding, PKCS7}; /// @@ -134,7 +134,7 @@ use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; /// ``` /// use bouncycastle_aes::AES_CBC_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::SymmetricCipherEncryptor; +/// use bouncycastle_core::traits::SimpleCipherEncryptor; /// use bouncycastle_modes::Encrypting; /// use bouncycastle_padding::NoPadding; /// @@ -157,7 +157,7 @@ pub type AES_CBC_128 = = = = (name: &str) where - Enc: SymmetricCipherEncryptor, - Dec: SymmetricCipherDecryptor, + Enc: SimpleCipherEncryptor, + Dec: SimpleCipherDecryptor, { for len in [0usize, 1, 15, 16, 17, 63, 64] { let plaintext: Vec = (0..len).map(|i| (i * 11 + 3) as u8).collect(); diff --git a/crypto/aes/tests/ecb_alias_tests.rs b/crypto/aes/tests/ecb_alias_tests.rs index 6ad0cf4a..ebe6c9c3 100644 --- a/crypto/aes/tests/ecb_alias_tests.rs +++ b/crypto/aes/tests/ecb_alias_tests.rs @@ -8,7 +8,7 @@ use bouncycastle_aes::{AES_128, AES_ECB_128, AES_ECB_192, AES_ECB_256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; use bouncycastle_modes::{Decrypting, Ecb, Encrypting}; use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; @@ -53,8 +53,8 @@ fn there_is_no_iv() { fn every_key_length_round_trips() { fn check(name: &str) where - Enc: SymmetricCipherEncryptor, - Dec: SymmetricCipherDecryptor, + Enc: SimpleCipherEncryptor, + Dec: SimpleCipherDecryptor, { for len in [0usize, 1, 15, 16, 17, 64] { let plaintext: Vec = (0..len).map(|i| (i * 11 + 3) as u8).collect(); diff --git a/crypto/core-test-framework/src/symmetric_ciphers.rs b/crypto/core-test-framework/src/symmetric_ciphers.rs index 98bb5e75..b3878ac7 100644 --- a/crypto/core-test-framework/src/symmetric_ciphers.rs +++ b/crypto/core-test-framework/src/symmetric_ciphers.rs @@ -7,12 +7,11 @@ use bouncycastle_core::key_material::{ }; use bouncycastle_core::traits::{ AEADCipher, BlockCipherDecryptor, BlockCipherEncryptor, SecurityStrength, - StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, + SimpleCipherDecryptor, SimpleCipherEncryptor, StreamCipherDecryptor, StreamCipherEncryptor, }; /// Instance of the test framework. -pub struct TestFrameworkSymmetricCipher { +pub struct TestFrameworkSimpleCipher { /// For [`test_encryptor_decryptor`](Self::test_encryptor_decryptor): the plaintext length /// granularity the pair accepts. 1 (the default) means every length round-trips. A larger value /// -- the block length, for a `PaddedEncryptor` over `NoPadding` -- means only multiples of it @@ -21,13 +20,13 @@ pub struct TestFrameworkSymmetricCipher { pub required_alignment: usize, } -impl TestFrameworkSymmetricCipher { +impl TestFrameworkSimpleCipher { /// pub fn new() -> Self { Self { required_alignment: 1 } } - /// Exercises the [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`] contract for a + /// Exercises the [`SimpleCipherEncryptor`] / [`SimpleCipherDecryptor`] contract for a /// paired implementor. /// /// Checks, in order: @@ -50,8 +49,8 @@ impl TestFrameworkSymmetricCipher { const KEY_LEN: usize, const INIT_DATA_LEN: usize, const FINAL_LEN: usize, - E: SymmetricCipherEncryptor, - D: SymmetricCipherDecryptor, + E: SimpleCipherEncryptor, + D: SimpleCipherDecryptor, >( &self, ) { diff --git a/crypto/core-test-framework/summary.md b/crypto/core-test-framework/summary.md index df164c32..c0b23ef1 100644 --- a/crypto/core-test-framework/summary.md +++ b/crypto/core-test-framework/summary.md @@ -124,7 +124,7 @@ The identical loop appears in two other suites in | Suite | Loop at | Implementors in tree | Status | |---|---|---|---| -| `TestFrameworkSymmetricCipher::test` | line 87 | 0 | **gone**: the `SymmetricCipher` trait was deleted and its suite moved to `TestFrameworkAEADCipher::test_plain_one_shots`, guarded on the way | +| `TestFrameworkSimpleCipher::test` | line 87 | 0 | **gone**: the `SymmetricCipher` trait was deleted and its suite moved to `TestFrameworkAEADCipher::test_plain_one_shots`, guarded on the way | | `TestFrameworkBlockCipher` | line 240 | 1 (`crypto/modes`) | **fixed** | | `TestFrameworkAEADCipher` | line 386 | 0 | **fixed** | | `TestFrameworkStreamCipher` | in `test` | 2 (`crypto/modes`: `Cfb`, `Cfb8`) | **fixed** (written later, with the guard) | @@ -180,7 +180,7 @@ new suites are exercised by: ## 6. Open items -1. ~~**Fix the same loop in `TestFrameworkSymmetricCipher` and `TestFrameworkAEADCipher`** (§3).~~ Done. +1. ~~**Fix the same loop in `TestFrameworkSimpleCipher` and `TestFrameworkAEADCipher`** (§3).~~ Done. Three lines each, and the next implementor of either trait will otherwise hit the panic. 2. **Decide whether the `Default` impl added to `TestFrameworkElectronicCodeBook` should be added to the other suites** for consistency — they all have `new()` and no `Default`, which clippy diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 09ae6a30..8285227c 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -23,7 +23,7 @@ pub trait AEADCipher, const SK_LEN: usize, const SIG /// The decryption half of a stream cipher's streaming API; see [`StreamCipherEncryptor`], whose /// notes on in-place operation, arbitrary lengths, the `Result` and the free -/// [`SymmetricCipherDecryptor`] impl all apply here too. +/// [`SimpleCipherDecryptor`] impl all apply here too. pub trait StreamCipherDecryptor: Algorithm + Sized { @@ -1194,7 +1194,7 @@ pub trait StreamCipherDecryptor: Sized { } /// The decryption half of a symmetric cipher's arbitrary-length API. See -/// [`SymmetricCipherEncryptor`] for the shape of the API and the meaning of `FINAL_LEN`; this is +/// [`SimpleCipherEncryptor`] for the shape of the API and the meaning of `FINAL_LEN`; this is /// its mirror image, and the two are implemented by paired types. /// /// Decryption is not the exact mirror of encryption in one respect: the last `FINAL_LEN` bytes a @@ -1347,14 +1347,14 @@ pub trait SuspendableKeyed: Sized { /// [`do_decrypt_init`](Self::do_decrypt_init), [`update_out_len`](Self::update_out_len), /// [`do_update_out`](Self::do_update_out), [`do_final`](Self::do_final) and /// [`decrypt_out_max_len`](Self::decrypt_out_max_len). -pub trait SymmetricCipherDecryptor< +pub trait SimpleCipherDecryptor< const KEY_LEN: usize, const INIT_DATA_LEN: usize, const FINAL_LEN: usize, >: Algorithm + Sized { /// Begins a streaming decryption from the init data returned by - /// [`SymmetricCipherEncryptor::do_encrypt_init`]. + /// [`SimpleCipherEncryptor::do_encrypt_init`]. /// /// # Errors /// Rejects a key whose [`KeyType`] is not [`KeyType::SymmetricCipherKey`], and one whose @@ -1477,14 +1477,14 @@ pub trait SymmetricCipherDecryptor< /// are provided over the streaming methods. An implementor writes only the two `_init` /// constructors, [`update_out_len`](Self::update_out_len), [`do_update_out`](Self::do_update_out), /// [`do_final`](Self::do_final) and [`encrypt_out_len`](Self::encrypt_out_len). -pub trait SymmetricCipherEncryptor< +pub trait SimpleCipherEncryptor< const KEY_LEN: usize, const INIT_DATA_LEN: usize, const FINAL_LEN: usize, >: Algorithm + Sized { /// Begins a streaming encryption, returning the encryptor and the generated init data (IV or - /// nonce), which the recipient needs for [`SymmetricCipherDecryptor::do_decrypt_init`]. Sources + /// nonce), which the recipient needs for [`SimpleCipherDecryptor::do_decrypt_init`]. Sources /// randomness from the library's default OS-backed RNG. /// /// # Errors @@ -1603,11 +1603,11 @@ pub trait SymmetricCipherEncryptor< } } -/// Every stream cipher is also a [`SymmetricCipherEncryptor`] with `FINAL_LEN = 0`. +/// Every stream cipher is also a [`SimpleCipherEncryptor`] with `FINAL_LEN = 0`. /// /// The two traits describe the same operation at different granularities. [`StreamCipherEncryptor`] /// is the in-place view -- one buffer, transformed where it lies -- and -/// [`SymmetricCipherEncryptor`] is the separate-output view that the padding adapters and the AEAD +/// [`SimpleCipherEncryptor`] is the separate-output view that the padding adapters and the AEAD /// ciphers share. A stream cipher can offer the second in terms of the first, because it changes /// neither the length of its data nor anything at the end of the message: `update_out_len` is the /// identity, `encrypt_out_len` is the identity, and `do_final` has nothing to produce, which is @@ -1623,7 +1623,7 @@ pub trait SymmetricCipherEncryptor< /// ` as StreamCipherEncryptor<..>>::do_encrypt_init(&key)` -- though either resolves to the /// same function. impl - SymmetricCipherEncryptor for T + SimpleCipherEncryptor for T where T: StreamCipherEncryptor, { @@ -1685,10 +1685,10 @@ where } } -/// Every stream cipher is also a [`SymmetricCipherDecryptor`] with `FINAL_LEN = 0`. The mirror of +/// Every stream cipher is also a [`SimpleCipherDecryptor`] with `FINAL_LEN = 0`. The mirror of /// the [`StreamCipherEncryptor`] blanket impl above; see it for why this exists. impl - SymmetricCipherDecryptor for T + SimpleCipherDecryptor for T where T: StreamCipherDecryptor, { diff --git a/crypto/modes/src/lib.rs b/crypto/modes/src/lib.rs index f0284028..0bc4f077 100644 --- a/crypto/modes/src/lib.rs +++ b/crypto/modes/src/lib.rs @@ -20,7 +20,7 @@ //! //! **All five reach the same arbitrary-length API**, so code can be written against one trait and //! handed any mode. A block mode gets there by being wrapped in `bouncycastle-padding`'s adapters, -//! which are [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`] with the padded block as +//! which are [`SimpleCipherEncryptor`] / [`SimpleCipherDecryptor`] with the padded block as //! their final output; a stream mode implements those traits directly, with `FINAL_LEN = 0` because //! it has no final output at all. The `bouncycastle-aes` aliases show the difference in //! one line each: `AES_CBC_128` names a padding scheme, `AES_CTR_128` @@ -295,7 +295,7 @@ //! ``` //! use bouncycastle_aes::AES_128; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::traits::{SymmetricCipherDecryptor, SymmetricCipherEncryptor}; +//! use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; //! use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; //! use bouncycastle_padding::{PKCS7, PaddedDecryptor, PaddedEncryptor}; //! @@ -532,8 +532,8 @@ pub use ecb::Ecb; // Imports needed for docs #[allow(unused_imports)] use bouncycastle_core::traits::{ - BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook, StreamCipherDecryptor, - StreamCipherEncryptor, SymmetricCipherDecryptor, SymmetricCipherEncryptor, + BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook, SimpleCipherDecryptor, + SimpleCipherEncryptor, StreamCipherDecryptor, StreamCipherEncryptor, }; // end of imports needed for docs diff --git a/crypto/modes/tests/ecb_tests.rs b/crypto/modes/tests/ecb_tests.rs index 28db8aaa..b5d387ca 100644 --- a/crypto/modes/tests/ecb_tests.rs +++ b/crypto/modes/tests/ecb_tests.rs @@ -15,8 +15,8 @@ mod common; use bouncycastle_aes::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ - BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, + BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook, SimpleCipherDecryptor, + SimpleCipherEncryptor, }; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkBlockCipher; diff --git a/crypto/modes/tests/symmetric_cipher_api_tests.rs b/crypto/modes/tests/simple_cipher_api_tests.rs similarity index 83% rename from crypto/modes/tests/symmetric_cipher_api_tests.rs rename to crypto/modes/tests/simple_cipher_api_tests.rs index fdef4cb1..eae97149 100644 --- a/crypto/modes/tests/symmetric_cipher_api_tests.rs +++ b/crypto/modes/tests/simple_cipher_api_tests.rs @@ -1,6 +1,6 @@ -//! The stream modes through the [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`] API. +//! The stream modes through the [`SimpleCipherEncryptor`] / [`SimpleCipherDecryptor`] API. //! -//! `Cfb`, `Cfb8` and `Ctr` implement the stream traits directly and get the symmetric-cipher traits +//! `Cfb`, `Cfb8` and `Ctr` implement the stream traits directly and get the simple-cipher traits //! from the blanket impls in `bouncycastle-core`, with `FINAL_LEN = 0`. That is what lets a caller //! hold any of the five modes through one trait: a padded `Cbc` or `Ecb` with the padded block as //! its final output, and a stream mode with nothing. @@ -17,7 +17,7 @@ //! //! # Both traits in scope at once //! -//! This file imports the stream traits *and* the symmetric ones, so `do_encrypt_init` is ambiguous +//! This file imports the stream traits *and* the simple-cipher ones, so `do_encrypt_init` is ambiguous //! here and every call has to name the trait it means. That is the one ergonomic cost of a mode //! implementing both, so it is worth having a file that demonstrates it is workable; the two //! resolve to the same function. @@ -27,10 +27,9 @@ mod common; use bouncycastle_aes::AES_128; use bouncycastle_core::key_material::{KeyMaterial, KeyType}; use bouncycastle_core::traits::{ - StreamCipherDecryptor, StreamCipherEncryptor, SymmetricCipherDecryptor, - SymmetricCipherEncryptor, + SimpleCipherDecryptor, SimpleCipherEncryptor, StreamCipherDecryptor, StreamCipherEncryptor, }; -use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkSymmetricCipher; +use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkSimpleCipher; use bouncycastle_modes::{Cfb, Cfb8, Ctr, Decrypting, Encrypting}; use common::{TOY_LEN, Toy, toy_key}; @@ -49,7 +48,7 @@ type ToyCtr = Ctr; /// policy. #[test] fn the_stream_modes_conform_to_the_symmetric_cipher_suite() { - let framework = TestFrameworkSymmetricCipher::new(); + let framework = TestFrameworkSimpleCipher::new(); framework .test_encryptor_decryptor::, ToyCfb>(); framework @@ -68,9 +67,9 @@ fn the_two_apis_agree_byte_for_byte() { key: &KeyMaterial, ) where E: StreamCipherEncryptor - + SymmetricCipherEncryptor, + + SimpleCipherEncryptor, D: StreamCipherDecryptor - + SymmetricCipherDecryptor, + + SimpleCipherDecryptor, { for len in [0usize, 1, 15, 16, 17, 63, 64, 171] { let plaintext: Vec = (0..len).map(|i| (i * 7 + 1) as u8).collect(); @@ -83,7 +82,7 @@ fn the_two_apis_agree_byte_for_byte() { // The separate-output API, under the same init data, reached through the blanket impl. let mut dec_as_sym = - >::do_decrypt_init( + >::do_decrypt_init( key, &init, ) .unwrap(); @@ -112,10 +111,8 @@ fn the_input_buffer_is_not_modified() { let original = plaintext.clone(); let (mut enc, _init) = - as SymmetricCipherEncryptor>::do_encrypt_init( - &key, - ) - .unwrap(); + as SimpleCipherEncryptor>::do_encrypt_init(&key) + .unwrap(); let mut ciphertext = vec![0u8; plaintext.len()]; enc.do_update_out(&plaintext, &mut ciphertext).unwrap(); @@ -129,20 +126,18 @@ fn the_length_predictions_are_exact() { let key = toy_key(); for len in [0usize, 1, 15, 16, 17, 1000] { assert_eq!( - as SymmetricCipherEncryptor>::encrypt_out_len(len), + as SimpleCipherEncryptor>::encrypt_out_len(len), len, "encrypt_out_len is the identity" ); assert_eq!( - as SymmetricCipherDecryptor>::decrypt_out_max_len( - len - ), + as SimpleCipherDecryptor>::decrypt_out_max_len(len), len, "decrypt_out_max_len is exact, not an upper bound" ); let (enc, _) = - as SymmetricCipherEncryptor>::do_encrypt_init(&key) + as SimpleCipherEncryptor>::do_encrypt_init(&key) .unwrap(); assert_eq!(enc.update_out_len(len), len, "update_out_len is the identity"); } @@ -158,10 +153,8 @@ fn a_short_output_buffer_is_refused_without_consuming_anything() { let plaintext: Vec = (0..32u8).collect(); let (mut enc, init) = - as SymmetricCipherEncryptor>::do_encrypt_init( - &key, - ) - .unwrap(); + as SimpleCipherEncryptor>::do_encrypt_init(&key) + .unwrap(); let mut too_small = vec![0u8; plaintext.len() - 1]; match enc.do_update_out(&plaintext, &mut too_small) { @@ -208,7 +201,7 @@ fn a_short_output_buffer_is_refused_when_decrypting_too() { enc.do_encrypt(&mut ciphertext).unwrap(); let mut dec = - as SymmetricCipherDecryptor>::do_decrypt_init( + as SimpleCipherDecryptor>::do_decrypt_init( &key, &init, ) .unwrap(); @@ -232,7 +225,7 @@ fn a_short_output_buffer_is_refused_when_decrypting_too() { // short", not "not exactly equal". let mut oversized = vec![0xAAu8; ciphertext.len() + 8]; let mut dec = - as SymmetricCipherDecryptor>::do_decrypt_init( + as SimpleCipherDecryptor>::do_decrypt_init( &key, &init, ) .unwrap(); @@ -251,13 +244,12 @@ fn the_one_shots_round_trip_with_real_aes() { let message = b"a message of no particular length at all"; // CFB128 - let (iv, ct) = - as SymmetricCipherEncryptor<16, 16, 0>>::encrypt( - &key, message, - ) - .unwrap(); + let (iv, ct) = as SimpleCipherEncryptor<16, 16, 0>>::encrypt( + &key, message, + ) + .unwrap(); assert_eq!(ct.len(), message.len(), "a stream cipher does not change the length"); - let back = as SymmetricCipherDecryptor<16, 16, 0>>::decrypt( + let back = as SimpleCipherDecryptor<16, 16, 0>>::decrypt( &key, &iv, &ct, ) .unwrap(); @@ -265,11 +257,11 @@ fn the_one_shots_round_trip_with_real_aes() { // CFB8 let (iv, ct) = - as SymmetricCipherEncryptor<16, 16, 0>>::encrypt( + as SimpleCipherEncryptor<16, 16, 0>>::encrypt( &key, message, ) .unwrap(); - let back = as SymmetricCipherDecryptor<16, 16, 0>>::decrypt( + let back = as SimpleCipherDecryptor<16, 16, 0>>::decrypt( &key, &iv, &ct, ) .unwrap(); @@ -277,15 +269,14 @@ fn the_one_shots_round_trip_with_real_aes() { // CTR let (nonce, ct) = - as SymmetricCipherEncryptor<16, 12, 0>>::encrypt( + as SimpleCipherEncryptor<16, 12, 0>>::encrypt( &key, message, ) .unwrap(); assert_eq!(nonce.len(), 12, "CTR's init data is its 12-byte nonce"); - let back = - as SymmetricCipherDecryptor<16, 12, 0>>::decrypt( - &key, &nonce, &ct, - ) - .unwrap(); + let back = as SimpleCipherDecryptor<16, 12, 0>>::decrypt( + &key, &nonce, &ct, + ) + .unwrap(); assert_eq!(back, message); } diff --git a/crypto/padding/src/padded.rs b/crypto/padding/src/padded.rs index ee22d21e..d7760919 100644 --- a/crypto/padding/src/padded.rs +++ b/crypto/padding/src/padded.rs @@ -1,7 +1,7 @@ //! [`PaddedEncryptor`] / [`PaddedDecryptor`]: adapt a block-aligned [`BlockCipherEncryptor`] / //! [`BlockCipherDecryptor`] to arbitrary-length data using a [`Padding`] scheme. //! -//! The public API is the [`SymmetricCipherEncryptor`] / [`SymmetricCipherDecryptor`] traits, whose +//! The public API is the [`SimpleCipherEncryptor`] / [`SimpleCipherDecryptor`] traits, whose //! shape was drawn from these two types; the one-shot methods are the traits' provided ones. //! `FINAL_LEN` is `BLOCK_LEN`: the final output is the padded block -- or, under a scheme with //! [`Padding::ALWAYS_PADS`] `false` (`NoPadding`) and an aligned message, nothing at all, in which @@ -11,7 +11,7 @@ use bouncycastle_core::errors::SymmetricCipherError; use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{ Algorithm, BlockCipherDecryptor, BlockCipherEncryptor, Padding, RNG, SecurityStrength, - SymmetricCipherDecryptor, SymmetricCipherEncryptor, + SimpleCipherDecryptor, SimpleCipherEncryptor, }; use bouncycastle_utils::secret::Secret; use core::array::from_mut; @@ -22,8 +22,8 @@ const GROUP: usize = 8; /// Encrypts arbitrary-length data with a block cipher `E`, padding the final block with `P`. /// -/// Stream with [`SymmetricCipherEncryptor::do_update_out`] then [`SymmetricCipherEncryptor::do_final`], -/// or use the one-shot [`SymmetricCipherEncryptor::encrypt_out`]. Output is +/// Stream with [`SimpleCipherEncryptor::do_update_out`] then [`SimpleCipherEncryptor::do_final`], +/// or use the one-shot [`SimpleCipherEncryptor::encrypt_out`]. Output is /// `plaintext_len / BLOCK_LEN + 1` blocks for a scheme that always pads (PKCS7), and exactly the /// input length for one that never does (`NoPadding`, which rejects an unaligned input at /// `do_final`). The buffered partial plaintext block is held in a [`Secret`]. @@ -68,7 +68,7 @@ where } impl - SymmetricCipherEncryptor + SimpleCipherEncryptor for PaddedEncryptor where E: BlockCipherEncryptor, @@ -212,7 +212,7 @@ where } impl - SymmetricCipherDecryptor + SimpleCipherDecryptor for PaddedDecryptor where D: BlockCipherDecryptor, diff --git a/crypto/padding/tests/padded_tests.rs b/crypto/padding/tests/padded_tests.rs index 42f7cb60..4f27a454 100644 --- a/crypto/padding/tests/padded_tests.rs +++ b/crypto/padding/tests/padded_tests.rs @@ -9,11 +9,11 @@ use bouncycastle_core::errors::{KeyMaterialError, PaddingError, SymmetricCipherE use bouncycastle_core::key_material::{KeyMaterial, KeyMaterialTrait, KeyType}; use bouncycastle_core::traits::{ Algorithm, BlockCipherDecryptor, BlockCipherEncryptor, RNG, SecurityStrength, - SymmetricCipherDecryptor, SymmetricCipherEncryptor, + SimpleCipherDecryptor, SimpleCipherEncryptor, }; use bouncycastle_core_test_framework::FixedSeedRNG; use bouncycastle_core_test_framework::symmetric_ciphers::{ - TestFrameworkBlockCipher, TestFrameworkSymmetricCipher, + TestFrameworkBlockCipher, TestFrameworkSimpleCipher, }; use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; use bouncycastle_rng::hash_drbg80090a::{HashDRBG80090A, HashDRBG80090AParams_SHA256}; @@ -105,11 +105,11 @@ fn toy_cipher_passes_core_test_framework() { TestFrameworkBlockCipher::new().test::(); } -/// The padded adapters are the first implementors of `SymmetricCipherEncryptor` / -/// `SymmetricCipherDecryptor`, so this is also what exercises those traits' provided one-shots. +/// The padded adapters are the first implementors of `SimpleCipherEncryptor` / +/// `SimpleCipherDecryptor`, so this is also what exercises those traits' provided one-shots. #[test] fn padded_adapters_pass_the_symmetric_cipher_framework() { - TestFrameworkSymmetricCipher::new().test_encryptor_decryptor::(); + TestFrameworkSimpleCipher::new().test_encryptor_decryptor::(); } #[test] @@ -308,7 +308,7 @@ fn wrong_key_type_is_rejected_by_adapters() { /// `PaddingError`, at `encrypt_out` and at a streaming `do_final`. #[test] fn no_padding_adapters_pass_the_symmetric_cipher_framework() { - let mut framework = TestFrameworkSymmetricCipher::new(); + let mut framework = TestFrameworkSimpleCipher::new(); framework.required_alignment = B; framework.test_encryptor_decryptor::(); } From 4c7eec753157e0a5bcd928ffc2647954d1353f97 Mon Sep 17 00:00:00 2001 From: David Hook Date: Wed, 9 Sep 2026 15:34:17 +1000 Subject: [PATCH 14/14] gitignore: ignore editor swap and backup files, and drop the .fred.swp Vim swap file that d98f703 swept in --- .fred.swp | Bin 12288 -> 0 bytes .gitignore | 5 +++++ 2 files changed, 5 insertions(+) delete mode 100644 .fred.swp diff --git a/.fred.swp b/.fred.swp deleted file mode 100644 index b402b0be5bd1d16c53c61e17f4181945f6c20339..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI&y-ve05C`zII|9KATw$e23lk&aL#hNDW%tK5u}I>`=OVGd6Y@?tfsPc^t! zDG<9+_K~(e{@MQMm$;u_hh0Me0uX=z1Rwwb2tWV=5P$##AkYh_bl`q}m}NRW{rUgq z|9{9q1OW&@00Izz00bZa0SG_<0uX?}KLzNiVzS;)46bQhTaq%ti%{)!9^{-9%Mi7T zQai&#BBo-yuKR>kYbjl^r-mCJ-biz6s+<;)Lh5*B83v_eGc`U0md>{})i4DWoo`jm XsX|4%dAMHQ-sO!YB`-oNAM)%ASFlo? diff --git a/.gitignore b/.gitignore index c1ef8598..408e5af3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,11 @@ mutants.out*/ .idea/ .vscode/ +# editor swap / backup files +*.swp +*.swo +*~ + # Claude Code: ignore personal/local state, but share team tooling # (skills, slash commands, subagents, and project settings.json). .claude/*