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/* 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/QUALITY_AND_STYLE.md b/QUALITY_AND_STYLE.md index 382c3582..5d84453a 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/cli/src/aes_cbc_cmd.rs b/cli/src/aes_cbc_cmd.rs index d8f4a72c..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_lowmemory::{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 29a9e474..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_lowmemory::{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 dde4491a..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_lowmemory::{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 611b64c0..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_lowmemory::{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 d4dc6f4a..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_lowmemory::{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 337d815a..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/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/aes-lowmemory/benches/aes_benches.rs b/crypto/aes-lowmemory/benches/aes_benches.rs deleted file mode 100644 index 82d81003..00000000 --- a/crypto/aes-lowmemory/benches/aes_benches.rs +++ /dev/null @@ -1,183 +0,0 @@ -//! Criterion benchmarks for the bit-sliced AES engine. -//! -//! The comparison that matters here is `encrypt_block` against `encrypt_blocks2` 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). - -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256, BLOCK_LEN}; -use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -use bouncycastle_core::traits::RNG; -use bouncycastle_rng as rng; -use criterion::{Criterion, Throughput, criterion_group, criterion_main}; -use std::hint::black_box; - -/// 16 KiB of data, i.e. 1024 AES blocks. -const NUM_BLOCKS: usize = 1024; -const DATA_LEN: usize = NUM_BLOCKS * BLOCK_LEN; - -fn random_blocks() -> Vec<[u8; BLOCK_LEN]> { - let mut blocks = vec![[0u8; BLOCK_LEN]; NUM_BLOCKS]; - let mut generator = rng::DefaultRNG::default(); - for block in blocks.iter_mut() { - generator.next_bytes_out(block).unwrap(); - } - blocks -} - -fn key() -> KeyMaterial { - let mut bytes = [0u8; N]; - rng::DefaultRNG::default().next_bytes_out(&mut bytes).unwrap(); - KeyMaterial::::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey).unwrap() -} - -fn bench_key_expansion(c: &mut Criterion) { - let mut group = c.benchmark_group("aes_lowmemory::key expansion"); - - let key128 = key::<16>(); - group.bench_function("Aes128::new()", |b| { - b.iter(|| black_box(Aes128::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())) - }); - - let key256 = key::<32>(); - group.bench_function("Aes256::new()", |b| { - b.iter(|| black_box(Aes256::new(black_box(&key256)).unwrap())) - }); - - group.finish(); -} - -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"); - 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_blocks2() 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)); - } - black_box(&buf); - }) - }); - - group.bench_function("16KiB -- .decrypt_block() x1024", |b| { - b.iter(|| { - let mut buf = blocks.clone(); - for block in buf.iter_mut() { - aes.decrypt_block(black_box(block)); - } - black_box(&buf); - }) - }); - - group.bench_function("16KiB -- .decrypt_blocks2() 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)); - } - black_box(&buf); - }) - }); - - group.finish(); -} - -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"); - 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_blocks2() 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)); - } - black_box(&buf); - }) - }); - - group.finish(); -} - -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"); - 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_blocks2() 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)); - } - black_box(&buf); - }) - }); - - group.bench_function("16KiB -- .decrypt_blocks2() 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)); - } - black_box(&buf); - }) - }); - - group.finish(); -} - -criterion_group!(benches, bench_key_expansion, bench_aes128, bench_aes192, bench_aes256); -criterion_main!(benches); diff --git a/crypto/aes-lowmemory/src/cbc.rs b/crypto/aes-lowmemory/src/cbc.rs deleted file mode 100644 index d68f6e2a..00000000 --- a/crypto/aes-lowmemory/src/cbc.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! Type aliases for AES in CBC mode (NIST SP 800-38A Sec 6.2). -//! -//! `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. - -use crate::{Aes128, Aes192, Aes256, BLOCK_LEN}; -use bouncycastle_modes::Cbc; - -/// 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. -/// -/// The IV is generated by encryption and returned; it is never supplied. Encryption and decryption -/// work in place. -/// -/// ``` -/// use bouncycastle_aes_lowmemory::AES_CBC_128; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; -/// use bouncycastle_modes::{Decrypting, Encrypting}; -/// -/// 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]); -/// ``` -/// -/// A length that is not a whole number of blocks is a **compile** error, not a runtime one: -/// -/// ```compile_fail -/// use bouncycastle_aes_lowmemory::AES_CBC_128; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::BlockCipherEncryptor; -/// use bouncycastle_modes::Encrypting; -/// -/// 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]); -/// ``` -#[allow(non_camel_case_types)] -pub type AES_CBC_128 = Cbc; - -/// AES-192 in CBC mode. 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_modes::{Decrypting, Encrypting}; -/// -/// 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]); -/// ``` -#[allow(non_camel_case_types)] -pub type AES_CBC_192 = Cbc; - -/// AES-256 in CBC mode. 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_modes::{Decrypting, Encrypting}; -/// -/// 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]); -/// ``` -#[allow(non_camel_case_types)] -pub type AES_CBC_256 = Cbc; diff --git a/crypto/aes-lowmemory/src/ecb.rs b/crypto/aes-lowmemory/src/ecb.rs deleted file mode 100644 index d9902f8f..00000000 --- a/crypto/aes-lowmemory/src/ecb.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! Type aliases for AES in ECB mode (NIST SP 800-38A Sec 6.1). -//! -//! `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. -//! -//! **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". - -use crate::{Aes128, Aes192, Aes256, BLOCK_LEN}; -use bouncycastle_modes::Ecb; - -/// 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. -/// -/// 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. -/// -/// ``` -/// use bouncycastle_aes_lowmemory::AES_ECB_128; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; -/// use bouncycastle_modes::{Decrypting, Encrypting}; -/// -/// 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]); -/// ``` -/// -/// A length that is not a whole number of blocks is a **compile** error, not a runtime one: -/// -/// ```compile_fail -/// use bouncycastle_aes_lowmemory::AES_ECB_128; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::BlockCipherEncryptor; -/// use bouncycastle_modes::Encrypting; -/// -/// 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]); -/// ``` -#[allow(non_camel_case_types)] -pub type AES_ECB_128 = Ecb; - -/// AES-192 in ECB mode. See [`AES_ECB_128`]. -/// -/// ``` -/// use bouncycastle_aes_lowmemory::AES_ECB_192; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; -/// use bouncycastle_modes::{Decrypting, Encrypting}; -/// -/// 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]); -/// ``` -#[allow(non_camel_case_types)] -pub type AES_ECB_192 = Ecb; - -/// AES-256 in ECB mode. See [`AES_ECB_128`]. -/// -/// ``` -/// use bouncycastle_aes_lowmemory::AES_ECB_256; -/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -/// use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; -/// use bouncycastle_modes::{Decrypting, Encrypting}; -/// -/// 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]); -/// ``` -#[allow(non_camel_case_types)] -pub type AES_ECB_256 = Ecb; diff --git a/crypto/aes-lowmemory/Cargo.toml b/crypto/aes/Cargo.toml similarity index 68% rename from crypto/aes-lowmemory/Cargo.toml rename to crypto/aes/Cargo.toml index f6cbff4d..2e2f8d68 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 @@ -8,13 +8,15 @@ 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 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 new file mode 100644 index 00000000..cf8e4af4 --- /dev/null +++ b/crypto/aes/benches/aes_benches.rs @@ -0,0 +1,135 @@ +//! 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::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. +const NUM_BLOCKS: usize = 1024; +const DATA_LEN: usize = NUM_BLOCKS * BLOCK_LEN; + +fn random_blocks() -> Vec<[u8; BLOCK_LEN]> { + let mut blocks = vec![[0u8; BLOCK_LEN]; NUM_BLOCKS]; + let mut generator = rng::DefaultRNG::default(); + for block in blocks.iter_mut() { + generator.next_bytes_out(block).unwrap(); + } + blocks +} + +fn key() -> KeyMaterial { + let mut bytes = [0u8; N]; + rng::DefaultRNG::default().next_bytes_out(&mut bytes).unwrap(); + KeyMaterial::::from_bytes_as_type(&bytes, KeyType::SymmetricCipherKey).unwrap() +} + +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())) + }); + + group.finish(); +} + +/// 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(|| { + for block in blocks.iter_mut() { + aes.encrypt_block(black_box(block)); + } + black_box(&blocks); + }) + }); + + group.bench_function("16KiB -- .encrypt_2blocks() x512", |b| { + b.iter(|| { + 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(&blocks); + }) + }); + + group.bench_function("16KiB -- .decrypt_block() x1024", |b| { + b.iter(|| { + for block in blocks.iter_mut() { + aes.decrypt_block(black_box(block)); + } + black_box(&blocks); + }) + }); + + group.bench_function("16KiB -- .decrypt_2blocks() x512", |b| { + b.iter(|| { + 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(&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 mut group = c.benchmark_group("aes::AES_192"); + bench_data_paths(&mut group, &aes); + group.finish(); +} + +fn bench_aes256(c: &mut Criterion) { + let aes = AES_256::new(&key::<32>()).unwrap(); + let mut group = c.benchmark_group("aes::AES_256"); + bench_data_paths(&mut group, &aes); + group.finish(); +} + +criterion_group!(benches, bench_key_expansion, bench_aes128, bench_aes192, bench_aes256); +criterion_main!(benches); diff --git a/crypto/aes-lowmemory/src/aes.rs b/crypto/aes/src/aes.rs similarity index 68% rename from crypto/aes-lowmemory/src/aes.rs rename to crypto/aes/src/aes.rs index 08198459..d6dbbc42 100644 --- a/crypto/aes-lowmemory/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,26 +14,29 @@ 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. /// /// 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_2blocks`]), 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 @@ -68,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)); @@ -94,14 +97,14 @@ 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. /// /// 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)); @@ -122,23 +125,23 @@ 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]) { + /// 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]); } - /// 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_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]); } @@ -147,24 +150,24 @@ 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_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. /// 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); + 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"); } - /// 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); + 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"); @@ -177,112 +180,112 @@ 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 /// * [`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()) }) + 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. - pub fn new(key: &KeyMaterial<24>) -> Result { +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. - pub fn new(key: &KeyMaterial<32>) -> Result { +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; } // 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. -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) + 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) } } -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) + 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) } } -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) + 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) } } -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-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/src/cbc.rs b/crypto/aes/src/cbc.rs new file mode 100644 index 00000000..d31b103e --- /dev/null +++ b/crypto/aes/src/cbc.rs @@ -0,0 +1,207 @@ +//! 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, 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 [`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: +//! +//! ```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::{AES_128, AES_192, AES_256, BLOCK_LEN}; +use bouncycastle_modes::{Cbc, Decrypting, Encrypting}; + +// Imports needed for docs +#[allow(unused_imports)] +use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; +#[allow(unused_imports)] +use bouncycastle_padding::{NoPadding, PKCS7, PaddedDecryptor, PaddedEncryptor}; +// end of imports needed for docs + +/// AES-128 in CBC mode with a padding scheme. +/// +/// `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::AES_CBC_128; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; +/// 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"); +/// +/// // 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::AES_CBC_128; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::traits::SimpleCipherEncryptor; +/// 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()); +/// ``` +/// +/// 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::AES_CBC_128; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::traits::SimpleCipherEncryptor; +/// use bouncycastle_modes::Encrypting; +/// use bouncycastle_padding::{NoPadding, PKCS7}; +/// +/// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +/// +/// // 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::AES_CBC_128; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::traits::SimpleCipherEncryptor; +/// 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, + Pad, + 16, + BLOCK_LEN, +>>::Mode; + +/// AES-192 in CBC mode with a padding scheme. See [`AES_CBC_128`]. +/// +/// ``` +/// use bouncycastle_aes::AES_CBC_192; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; +/// use bouncycastle_modes::{Decrypting, Encrypting}; +/// use bouncycastle_padding::PKCS7; +/// +/// let key = KeyMaterial::<24>::from_bytes_as_type(&[0x42; 24], KeyType::SymmetricCipherKey).unwrap(); +/// 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, + Pad, + 24, + BLOCK_LEN, +>>::Mode; + +/// AES-256 in CBC mode with a padding scheme. See [`AES_CBC_128`]. +/// +/// ``` +/// use bouncycastle_aes::AES_CBC_256; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; +/// use bouncycastle_modes::{Decrypting, Encrypting}; +/// use bouncycastle_padding::PKCS7; +/// +/// let key = KeyMaterial::<32>::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey).unwrap(); +/// 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, + Pad, + 32, + BLOCK_LEN, +>>::Mode; diff --git a/crypto/aes-lowmemory/src/cfb.rs b/crypto/aes/src/cfb.rs similarity index 90% rename from crypto/aes-lowmemory/src/cfb.rs rename to crypto/aes/src/cfb.rs index ac55210a..55539508 100644 --- a/crypto/aes-lowmemory/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 @@ -20,7 +20,7 @@ use bouncycastle_modes::Cfb; /// Encryption and decryption work in place. /// /// ``` -/// use bouncycastle_aes_lowmemory::AES_CFB_128; +/// use bouncycastle_aes::AES_CFB_128; /// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; /// use bouncycastle_core::traits::{StreamCipherDecryptor, StreamCipherEncryptor}; /// use bouncycastle_modes::{Decrypting, Encrypting}; @@ -49,12 +49,12 @@ 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`]. /// /// ``` -/// 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}; @@ -66,12 +66,12 @@ 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`]. /// /// ``` -/// 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}; @@ -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-lowmemory/src/cfb8.rs b/crypto/aes/src/cfb8.rs similarity index 91% rename from crypto/aes-lowmemory/src/cfb8.rs rename to crypto/aes/src/cfb8.rs index 505e7c35..1d26481e 100644 --- a/crypto/aes-lowmemory/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 @@ -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}; @@ -56,12 +56,12 @@ 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`]. /// /// ``` -/// 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}; @@ -73,12 +73,12 @@ 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`]. /// /// ``` -/// 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}; @@ -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-lowmemory/src/ctr.rs b/crypto/aes/src/ctr.rs similarity index 90% rename from crypto/aes-lowmemory/src/ctr.rs rename to crypto/aes/src/ctr.rs index 5c6dc40a..6c6e64c8 100644 --- a/crypto/aes-lowmemory/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. @@ -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}; @@ -55,12 +55,12 @@ 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`]. /// /// ``` -/// 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}; @@ -72,12 +72,12 @@ 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`]. /// /// ``` -/// 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}; @@ -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 new file mode 100644 index 00000000..dbc5096b --- /dev/null +++ b/crypto/aes/src/ecb.rs @@ -0,0 +1,162 @@ +//! 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, 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. 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 [`SimpleCipherEncryptor`] / [`SimpleCipherDecryptor`], 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::{AES_128, AES_192, AES_256, BLOCK_LEN}; +use bouncycastle_modes::{Decrypting, Ecb, Encrypting}; + +// Imports needed for docs +#[allow(unused_imports)] +use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; +#[allow(unused_imports)] +use bouncycastle_padding::{NoPadding, PKCS7}; +// end of imports needed for docs + +/// AES-128 in ECB mode with a padding scheme. +/// +/// `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::AES_ECB_128; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; +/// 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"); +/// +/// // 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"); +/// ``` +/// +/// The codebook property survives padding, which is the whole objection to ECB: two identical +/// plaintext blocks still give two identical ciphertext blocks. +/// +/// ``` +/// use bouncycastle_aes::AES_ECB_128; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::traits::SimpleCipherEncryptor; +/// use bouncycastle_modes::Encrypting; +/// use bouncycastle_padding::NoPadding; +/// +/// let key = KeyMaterial::<16>::from_bytes_as_type(&[0x42; 16], KeyType::SymmetricCipherKey).unwrap(); +/// +/// // 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, + Pad, + 16, + 0, +>>::Mode; + +/// AES-192 in ECB mode with a padding scheme. See [`AES_ECB_128`], and its warning. +/// +/// ``` +/// use bouncycastle_aes::AES_ECB_192; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; +/// use bouncycastle_modes::{Decrypting, Encrypting}; +/// use bouncycastle_padding::PKCS7; +/// +/// let key = KeyMaterial::<24>::from_bytes_as_type(&[0x42; 24], KeyType::SymmetricCipherKey).unwrap(); +/// 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, + Pad, + 24, + 0, +>>::Mode; + +/// AES-256 in ECB mode with a padding scheme. See [`AES_ECB_128`], and its warning. +/// +/// ``` +/// use bouncycastle_aes::AES_ECB_256; +/// use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +/// use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; +/// use bouncycastle_modes::{Decrypting, Encrypting}; +/// use bouncycastle_padding::PKCS7; +/// +/// let key = KeyMaterial::<32>::from_bytes_as_type(&[0x42; 32], KeyType::SymmetricCipherKey).unwrap(); +/// 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, + Pad, + 32, + 0, +>>::Mode; diff --git a/crypto/aes-lowmemory/src/lib.rs b/crypto/aes/src/lib.rs similarity index 73% rename from crypto/aes-lowmemory/src/lib.rs rename to crypto/aes/src/lib.rs index eed751d6..6654cc73 100644 --- a/crypto/aes-lowmemory/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,8 +12,9 @@ //! ## Encrypting and decrypting a single block //! //! ``` -//! use bouncycastle_aes_lowmemory::Aes128; +//! use bouncycastle_aes::AES_128; //! 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, @@ -21,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, @@ -39,61 +40,70 @@ //! ## 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_2blocks`](bouncycastle_core::traits::ElectronicCodeBook::encrypt_2blocks) is roughly twice the throughput of two +//! [`ElectronicCodeBook::encrypt_block`](bouncycastle_core::traits::ElectronicCodeBook::encrypt_block) calls: //! //! ``` -//! use bouncycastle_aes_lowmemory::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); -//! aes.decrypt_blocks2(&mut blocks); +//! aes.encrypt_2blocks(&mut blocks); +//! aes.decrypt_2blocks(&mut blocks); //! assert_eq!(blocks, [[0u8; 16], [1u8; 16]]); //! ``` //! //! ## 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_aes::AES_CBC_256; //! use bouncycastle_core::key_material::{KeyMaterial, KeyType}; -//! use bouncycastle_core::traits::{BlockCipherDecryptor, BlockCipherEncryptor}; +//! use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; //! 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); //! ``` //! -//! There is no one-shot static on the permutation, because `Aes128::new(&key)?.encrypt_block(..)` +//! 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 `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. //! @@ -129,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 [`AES_128`] value therefore encrypts and decrypts //! from one stored schedule. //! //! # Memory Usage @@ -140,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 @@ -157,15 +167,16 @@ //! //! ## 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. //! //! 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 //! @@ -202,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)] @@ -213,15 +224,14 @@ mod cfb; mod cfb8; mod ctr; mod ecb; +mod padded_mode; mod round; mod sbox; mod schedule; -pub use aes::{Aes, Aes128, Aes192, Aes256, BLOCK_LEN}; -pub use bitslice::Block; +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}; 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 schedule::{Aes128Params, Aes192Params, Aes256Params, AesParams}; diff --git a/crypto/aes/src/padded_mode.rs b/crypto/aes/src/padded_mode.rs new file mode 100644 index 00000000..e9ac6ba2 --- /dev/null +++ b/crypto/aes/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/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 88% rename from crypto/aes-lowmemory/src/schedule.rs rename to crypto/aes/src/schedule.rs index 9ae50e38..9559c786 100644 --- a/crypto/aes-lowmemory/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; @@ -132,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] } @@ -145,7 +150,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 +167,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 +208,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 +290,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 +303,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 +318,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 +327,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 +337,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 +348,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 +393,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 +403,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 +426,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 +458,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-lowmemory/tests/acvp_tests.rs b/crypto/aes/tests/bc-test-data.rs similarity index 91% rename from crypto/aes-lowmemory/tests/acvp_tests.rs rename to crypto/aes/tests/bc-test-data.rs index aa7018f8..c94df200 100644 --- a/crypto/aes-lowmemory/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) | //! @@ -44,11 +44,11 @@ //! 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::{AES_128, AES_192, AES_256, 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; @@ -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); @@ -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,23 +158,23 @@ 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) } + if e { aes.encrypt_2blocks(p) } else { aes.decrypt_2blocks(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) } + if e { aes.encrypt_2blocks(p) } else { aes.decrypt_2blocks(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) } + 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/cbc_alias_tests.rs b/crypto/aes/tests/cbc_alias_tests.rs new file mode 100644 index 00000000..517a3199 --- /dev/null +++ b/crypto/aes/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::{AES_128, AES_CBC_128, AES_CBC_192, AES_CBC_256}; +use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; +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: 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(); + 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/tests/ecb_alias_tests.rs b/crypto/aes/tests/ecb_alias_tests.rs new file mode 100644 index 00000000..ebe6c9c3 --- /dev/null +++ b/crypto/aes/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::{AES_128, AES_ECB_128, AES_ECB_192, AES_ECB_256}; +use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::{SimpleCipherDecryptor, SimpleCipherEncryptor}; +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: 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(); + 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/aes-lowmemory/tests/electronic_code_book_tests.rs b/crypto/aes/tests/electronic_code_book_tests.rs similarity index 82% rename from crypto/aes-lowmemory/tests/electronic_code_book_tests.rs rename to crypto/aes/tests/electronic_code_book_tests.rs index 2098315e..3600983f 100644 --- a/crypto/aes-lowmemory/tests/electronic_code_book_tests.rs +++ b/crypto/aes/tests/electronic_code_book_tests.rs @@ -3,23 +3,23 @@ //! 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_lowmemory::{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-lowmemory/tests/fips197_tests.rs b/crypto/aes/tests/fips197_tests.rs similarity index 85% rename from crypto/aes-lowmemory/tests/fips197_tests.rs rename to crypto/aes/tests/fips197_tests.rs index d1261b8d..f9353218 100644 --- a/crypto/aes-lowmemory/tests/fips197_tests.rs +++ b/crypto/aes/tests/fips197_tests.rs @@ -10,13 +10,13 @@ //! `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. -use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle_aes::{AES_128, AES_192, AES_256}; 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] = [ @@ -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, @@ -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); } @@ -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-lowmemory/tests/sp800_38a_tests.rs b/crypto/aes/tests/sp800_38a_tests.rs similarity index 86% rename from crypto/aes-lowmemory/tests/sp800_38a_tests.rs rename to crypto/aes/tests/sp800_38a_tests.rs index 8e975eca..f8afa817 100644 --- a/crypto/aes-lowmemory/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 @@ -15,8 +15,9 @@ //! //! 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::{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; /// The four plaintext blocks shared by every F.1 subsection. @@ -72,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); @@ -82,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); @@ -94,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); @@ -104,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); @@ -116,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); @@ -126,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); @@ -143,17 +144,17 @@ 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 { 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])); } @@ -162,12 +163,12 @@ 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])]; - 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..ca8e855e 100644 --- a/crypto/core-test-framework/src/electronic_code_book.rs +++ b/crypto/core-test-framework/src/electronic_code_book.rs @@ -30,11 +30,11 @@ 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 + /// * `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`]. @@ -93,58 +93,58 @@ 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 + // 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_blocks8(&mut batched); - assert_eq!(batched, singly, "encrypt_blocks8 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_blocks8(&mut batched); - assert_eq!(batched, singly, "decrypt_blocks8 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"); + 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 // 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/src/symmetric_ciphers.rs b/crypto/core-test-framework/src/symmetric_ciphers.rs index 2aa5f8d4..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, SymmetricCipher, 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,102 +20,13 @@ pub struct TestFrameworkSymmetricCipher { pub required_alignment: usize, } -impl TestFrameworkSymmetricCipher { +impl TestFrameworkSimpleCipher { /// pub fn new() -> Self { 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 + /// Exercises the [`SimpleCipherEncryptor`] / [`SimpleCipherDecryptor`] contract for a /// paired implementor. /// /// Checks, in order: @@ -139,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, ) { @@ -542,6 +452,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 +563,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 +659,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/src/traits.rs b/crypto/core/src/traits.rs index cfc77a29..8285227c 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 + /// [`SimpleCipherEncryptor`] / [`SimpleCipherDecryptor`] 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) @@ -204,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( @@ -290,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. @@ -298,47 +357,49 @@ 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); } - /// 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_blocks2`] 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_blocks8(&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_blocks2(pair); + self.encrypt_2blocks(pair); } } - /// The inverse cipher function on eight *independent* blocks, in place. - /// See [`ElectronicCodeBook::encrypt_blocks8`]. - fn decrypt_blocks8(&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_blocks2(pair); + self.decrypt_2blocks(pair); } } } @@ -1094,7 +1155,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 +/// [`SimpleCipherDecryptor`] impl all apply here too. pub trait StreamCipherDecryptor: Algorithm + Sized { @@ -1130,6 +1192,14 @@ pub trait StreamCipherDecryptor: 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 +/// [`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 @@ -1329,14 +1347,14 @@ pub trait SymmetricCipher: Alg /// [`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 @@ -1459,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 @@ -1585,6 +1603,146 @@ pub trait SymmetricCipherEncryptor< } } +/// 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 +/// [`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 +/// exactly what `FINAL_LEN = 0` says. +/// +/// The point of the blanket impl is that a caller can hold a CFB, CFB8 or CTR value through the +/// same trait as a padded CBC one, and write code that does not care which mode it was handed. It +/// applies to every present and future implementor, so a new stream mode gets the arbitrary-length +/// API by writing one method. +/// +/// Note that both traits then offer `do_encrypt_init` and `do_encrypt_init_rng` with identical +/// signatures. Where both are in scope, a call needs qualifying -- +/// ` as StreamCipherEncryptor<..>>::do_encrypt_init(&key)` -- though either resolves to the +/// same function. +impl + SimpleCipherEncryptor 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 [`SimpleCipherDecryptor`] with `FINAL_LEN = 0`. The mirror of +/// the [`StreamCipherEncryptor`] blanket impl above; see it for why this exists. +impl + SimpleCipherDecryptor 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/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..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_blocks8` / -//! `decrypt_blocks2`, for CFB it is `encrypt_blocks8` / `encrypt_blocks2`, 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, @@ -21,23 +21,23 @@ //! 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 +//! 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 //! 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_lowmemory::{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. +/// `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. -struct UnpairedAes128(Aes128); +struct UnpairedAes128(AES_128); impl Algorithm for UnpairedAes128 { const ALG_NAME: &'static str = "AES-128 (unpaired)"; @@ -81,15 +81,15 @@ 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. + // encrypt_2blocks / decrypt_2blocks deliberately left as the trait defaults. } type UnpairedAes128Cbc = Cbc; @@ -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 ---- @@ -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) { @@ -168,8 +168,8 @@ 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. + // 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( || ciphertext.clone(), @@ -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| { @@ -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| { @@ -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| { @@ -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| { @@ -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 ---- @@ -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_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), @@ -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| { @@ -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| { @@ -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,14 +485,14 @@ 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_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) { 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. @@ -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), @@ -549,14 +549,14 @@ 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 [ // 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( @@ -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| { @@ -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| { @@ -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/cbc.rs b/crypto/modes/src/cbc.rs index a5ea5ce1..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_blocks8`], then any remaining pair through -//! [`ElectronicCodeBook::decrypt_blocks2`], 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 +//! 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 four blocks (SM4) for barely more than the cost of one. Encryption //! cannot, and does not. use crate::iv::random_iv; @@ -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()) { @@ -123,17 +123,17 @@ where self.chain = cj1; } - /// Decrypts eight consecutive blocks with one [`ElectronicCodeBook::decrypt_blocks8`] 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_blocks8(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_blocks8`, then pairs through `decrypt_blocks2`, + /// 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 76178b1d..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_blocks2`] or [`ElectronicCodeBook::decrypt_blocks8`]. 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,8 +117,8 @@ //! //! 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 +//! 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. @@ -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]; @@ -285,19 +285,18 @@ where } } - /// Decrypts eight consecutive blocks with one [`ElectronicCodeBook::encrypt_blocks8`] 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_blocks8(&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 717e6bf9..1497533d 100644 --- a/crypto/modes/src/cfb8.rs +++ b/crypto/modes/src/cfb8.rs @@ -75,9 +75,9 @@ //! //! 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_blocks2`], which is where a bit-sliced engine earns back a large +//! 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,17 +253,17 @@ 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_blocks8); + 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() { - 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 cb3564d8..ca6e7704 100644 --- a/crypto/modes/src/ctr.rs +++ b/crypto/modes/src/ctr.rs @@ -92,8 +92,8 @@ //! 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 -//! [`ElectronicCodeBook::encrypt_blocks2`]. Only the bytes that finish a partially-used keystream +//! 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. //! //! Like the rest of CFB and CTR, only the **forward** cipher function is ever used, in both @@ -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_lowmemory::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_lowmemory::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_lowmemory::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 @@ -367,13 +367,13 @@ 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_blocks8); + 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() { - 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..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_blocks8`] / [`ElectronicCodeBook::encrypt_blocks2`] 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,20 +127,20 @@ 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_blocks8(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() { - self.perm.encrypt_blocks2(pair); + self.perm.encrypt_2blocks(pair); } for block in tail.iter_mut() { self.perm.encrypt_block(block); @@ -164,19 +164,19 @@ 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_blocks8(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() { - 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/src/lib.rs b/crypto/modes/src/lib.rs index 3f65074a..0bc4f077 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 `AES_128` and friends, //! or anything else implementing [`ElectronicCodeBook`] -- into something that can encrypt more than //! one block. This crate provides: //! @@ -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 [`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` +//! 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 @@ -31,27 +39,29 @@ //! 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`. 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::{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 @@ -64,12 +74,12 @@ //! [Security Considerations](#security-considerations)). //! //! ``` -//! use bouncycastle_aes_lowmemory::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"); @@ -90,12 +100,12 @@ //! the concatenation: //! //! ``` -//! use bouncycastle_aes_lowmemory::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"); @@ -119,13 +129,13 @@ //! exactly as long as the plaintext: //! //! ``` -//! use bouncycastle_aes_lowmemory::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"); @@ -150,12 +160,12 @@ //! Streaming works at any byte boundary, and the chunking is not visible in the output: //! //! ``` -//! use bouncycastle_aes_lowmemory::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"); @@ -183,12 +193,12 @@ //! The codebook property that makes it unsuitable for data is visible in the ciphertext: //! //! ``` -//! use bouncycastle_aes_lowmemory::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"); @@ -205,12 +215,12 @@ //! Using the wrong direction does not compile: //! //! ```compile_fail -//! use bouncycastle_aes_lowmemory::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`. @@ -232,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. @@ -283,14 +293,14 @@ //! an error at `do_final` rather than something padded -- for formats defined on whole blocks. //! //! ``` -//! use bouncycastle_aes_lowmemory::Aes128; +//! 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}; //! -//! 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"); @@ -353,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 @@ -522,8 +532,8 @@ pub use ecb::Ecb; // Imports needed for docs #[allow(unused_imports)] use bouncycastle_core::traits::{ - BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook, StreamCipherDecryptor, - StreamCipherEncryptor, + BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook, SimpleCipherDecryptor, + SimpleCipherEncryptor, StreamCipherDecryptor, StreamCipherEncryptor, }; // end of imports needed for docs diff --git a/crypto/modes/tests/acvp_cfb8_tests.rs b/crypto/modes/tests/acvp_cfb8_tests.rs index a67c62f8..9748b91c 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 @@ -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_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`. @@ -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::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -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_blocks8` 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, } } @@ -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}"), } } @@ -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 933b01d4..458722b5 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. @@ -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_blocks2`] and [`ElectronicCodeBook::encrypt_blocks8`], 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 @@ -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::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -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_blocks8`. + /// 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. @@ -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 8dcbb3df..d717b647 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::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -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, @@ -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 e33d0593..f79abfb6 100644 --- a/crypto/modes/tests/acvp_ecb_tests.rs +++ b/crypto/modes/tests/acvp_ecb_tests.rs @@ -6,18 +6,18 @@ //! 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 -//! 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 //! 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::{AES_128, AES_192, AES_256}; use bouncycastle_core::key_material::{ KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations, }; @@ -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, } @@ -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}"), } } @@ -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/acvp_tests.rs b/crypto/modes/tests/acvp_tests.rs index 37b48d96..980cffab 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 @@ -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 @@ -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::{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 28185e83..90d4c3bd 100644 --- a/crypto/modes/tests/cbc_tests.rs +++ b/crypto/modes/tests/cbc_tests.rs @@ -6,17 +6,17 @@ mod common; -use bouncycastle_aes_lowmemory::{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; 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"); @@ -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. @@ -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_blocks8` 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_blocks8" - ); + // ...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. @@ -370,20 +366,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 bfea1b17..24eced43 100644 --- a/crypto/modes/tests/cfb8_tests.rs +++ b/crypto/modes/tests/cfb8_tests.rs @@ -13,18 +13,18 @@ mod common; -use bouncycastle_aes_lowmemory::{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; 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_blocks2` and `decrypt_blocks8`, 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() { @@ -411,24 +411,24 @@ 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. /// /// [`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 +/// 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_blocks2"); + 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_blocks8` 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_blocks8. - let mut d = SwappedEightCfb8::::do_decrypt_init(&key, &iv).unwrap(); - assert_ne!(dec(&mut d, &ct), plaintext, "nine bytes must go through encrypt_blocks8"); + // ...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"); } @@ -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 863afd79..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 @@ -13,7 +13,7 @@ mod common; -use bouncycastle_aes_lowmemory::{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, @@ -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_blocks2` and `decrypt_blocks8`, 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) @@ -440,16 +440,16 @@ 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 /// 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(); @@ -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_blocks8` 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 -/// (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_blocks8. - let mut d = SwappedEightCfb::::do_decrypt_init(&key, &iv).unwrap(); - assert_ne!(dec(&mut d, &ct), plaintext, "nine blocks must go through encrypt_blocks8"); + // ...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, @@ -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/common/mod.rs b/crypto/modes/tests/common/mod.rs index 306b3052..3307fa17 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,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_blocks2` or `decrypt_blocks8`. 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, @@ -154,39 +154,39 @@ 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)"); } - fn encrypt_blocks8(&self, blocks: &mut [[u8; TOY_LEN]; 8]) { - self.inner.encrypt_blocks8(blocks); + fn encrypt_4blocks(&self, blocks: &mut [[u8; TOY_LEN]; 4]) { + self.inner.encrypt_4blocks(blocks); } - fn decrypt_blocks8(&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_blocks8` / `decrypt_blocks8` 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_blocks8` -/// 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_blocks8(&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_blocks8(&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_bc_java_tests.rs b/crypto/modes/tests/ctr_bc_java_tests.rs index b0babd62..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_lowmemory::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 f9af6444..40c5318f 100644 --- a/crypto/modes/tests/ctr_tests.rs +++ b/crypto/modes/tests/ctr_tests.rs @@ -23,21 +23,21 @@ mod common; -use bouncycastle_aes_lowmemory::{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}; 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. @@ -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 @@ -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(); @@ -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_blocks8"); + 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 ------------------------------------------------------------------------ @@ -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 9a93c6f8..116dcc6b 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::{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; @@ -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]; @@ -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 db1f2c66..b5d387ca 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 @@ -12,21 +12,21 @@ mod common; -use bouncycastle_aes_lowmemory::{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, - SymmetricCipherEncryptor, + BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook, SimpleCipherDecryptor, + SimpleCipherEncryptor, }; 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 @@ -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,41 +231,37 @@ 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); } -/// 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_blocks8"); - 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_blocks8"); - 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) { @@ -348,7 +344,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 +409,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/simple_cipher_api_tests.rs b/crypto/modes/tests/simple_cipher_api_tests.rs new file mode 100644 index 00000000..eae97149 --- /dev/null +++ b/crypto/modes/tests/simple_cipher_api_tests.rs @@ -0,0 +1,282 @@ +//! The stream modes through the [`SimpleCipherEncryptor`] / [`SimpleCipherDecryptor`] API. +//! +//! `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. +//! +//! 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 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. + +mod common; + +use bouncycastle_aes::AES_128; +use bouncycastle_core::key_material::{KeyMaterial, KeyType}; +use bouncycastle_core::traits::{ + SimpleCipherDecryptor, SimpleCipherEncryptor, StreamCipherDecryptor, StreamCipherEncryptor, +}; +use bouncycastle_core_test_framework::symmetric_ciphers::TestFrameworkSimpleCipher; +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 = TestFrameworkSimpleCipher::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 + + SimpleCipherEncryptor, + D: StreamCipherDecryptor + + 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(); + + // 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 SimpleCipherEncryptor>::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 SimpleCipherEncryptor>::encrypt_out_len(len), + len, + "encrypt_out_len is the identity" + ); + assert_eq!( + as SimpleCipherDecryptor>::decrypt_out_max_len(len), + len, + "decrypt_out_max_len is exact, not an upper bound" + ); + + let (enc, _) = + as SimpleCipherEncryptor>::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 SimpleCipherEncryptor>::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 SimpleCipherDecryptor>::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 SimpleCipherDecryptor>::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 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 SimpleCipherDecryptor<16, 16, 0>>::decrypt( + &key, &iv, &ct, + ) + .unwrap(); + assert_eq!(back, message); + + // CFB8 + let (iv, ct) = + as SimpleCipherEncryptor<16, 16, 0>>::encrypt( + &key, message, + ) + .unwrap(); + let back = as SimpleCipherDecryptor<16, 16, 0>>::decrypt( + &key, &iv, &ct, + ) + .unwrap(); + assert_eq!(back, message); + + // CTR + let (nonce, ct) = + 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 SimpleCipherDecryptor<16, 12, 0>>::decrypt( + &key, &nonce, &ct, + ) + .unwrap(); + assert_eq!(back, message); +} diff --git a/crypto/modes/tests/sp800_38a_cfb8_tests.rs b/crypto/modes/tests/sp800_38a_cfb8_tests.rs index d9fa1468..de6ed2c3 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::{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; @@ -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. /// @@ -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 9463f7bd..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_lowmemory::{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 ea9a7539..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_lowmemory::{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 1dee9ac7..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_lowmemory::{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/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::(); } diff --git a/mem_usage_benches/src/bench_aes_mem_usage.rs b/mem_usage_benches/src/bench_aes_mem_usage.rs index 3cf47b5c..9a328fe6 100644 --- a/mem_usage_benches/src/bench_aes_mem_usage.rs +++ b/mem_usage_benches/src/bench_aes_mem_usage.rs @@ -35,8 +35,9 @@ #![allow(dead_code)] #![allow(unused_imports)] -use bouncycastle::aes_lowmemory::{Aes128, Aes192, Aes256}; +use bouncycastle::aes::{AES_128, AES_192, AES_256}; 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() { @@ -51,9 +52,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 { @@ -66,59 +67,59 @@ 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"); +fn bench_aes256_encrypt_2blocks() { + eprintln!("AES_256::encrypt_2blocks"); - 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); + aes.encrypt_2blocks(&mut blocks); print!("{blocks:x?}"); } @@ -131,5 +132,5 @@ fn main() { // bench_aes128_encrypt_block() // bench_aes256_encrypt_block() // bench_aes256_decrypt_block() - // bench_aes256_encrypt_blocks2() + // bench_aes256_encrypt_2blocks() } 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;