diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md
index 6705ef81..540406f8 100644
--- a/alpha_0.1.3_release_notes.md
+++ b/alpha_0.1.3_release_notes.md
@@ -30,7 +30,7 @@ permutation (NIST FIPS 197), re-exported from the umbrella crate.
New crate `bouncycastle-modes` (`bouncycastle::modes`): block cipher modes of operation
(NIST SP 800-38A), currently **CBC** (Sec 6.2). Re-exported from the umbrella crate.
-* `Cbc
` over any `BlockPermutation`, so the crate depends on no
+* `Cbc
` over any `ElectronicCodeBook`, so the crate depends on no
concrete cipher. The direction is a type parameter: `BlockCipherEncryptor` is implemented only
for `Cbc<_, Encrypting, _, _>` and `BlockCipherDecryptor` only for `Cbc<_, Decrypting, _, _>`,
making a wrong-direction call a compile error rather than a runtime check.
@@ -40,7 +40,7 @@ New crate `bouncycastle-modes` (`bouncycastle::modes`): block cipher modes of op
supplying your own. Known-answer tests drive `do_encrypt_init_rng` with a fixed-output test RNG.
* **Parallel decryption.** Sec 6.2 notes CBC decryption's inverse cipher calls can run in
parallel, so `do_decrypt_blocks[_out]` walks the ciphertext in pairs through
- `BlockPermutation::decrypt_blocks2`, with a one-block remainder for odd `N`. Measured against an
+ `ElectronicCodeBook::decrypt_blocks2`, with a one-block remainder for odd `N`. Measured against an
otherwise identical permutation that does not override the pair methods, this is **1.83x** the
decryption throughput (67.9 vs 37.1 MiB/s, AES-128, 16 KiB, N=8). CBC encryption is serial by
construction and does not use it.
@@ -71,8 +71,9 @@ New crate `bouncycastle-modes` (`bouncycastle::modes`): block cipher modes of op
not be secret (SP 800-38A Sec 5.3), so this is sound.
* Input must be a whole number of 16-byte blocks. Unaligned input is rejected with a message
pointing at the missing padding layer rather than being silently padded.
-* Reads do not respect block boundaries, so a block split across two reads is carried over;
- verified by round-tripping 64 KiB through `dd bs=3`.
+* Reads need not respect block boundaries: bytes accumulate in a 1 KiB buffer that goes through the flat
+ `do_*_out::<1024>` when full, and the whole-block remainder at end of input goes one block at a time; verified by
+ round-tripping 64 KiB through `dd bs=3`.
* Verified against SP 800-38A F.2: prepending the spec's IV to the spec's ciphertext and running
`decrypt` reproduces the spec's plaintext for all three key lengths. The `encrypt` direction was
cross-checked against an independent CBC implementation under the IV the CLI generated.
@@ -81,18 +82,17 @@ New crate `bouncycastle-modes` (`bouncycastle::modes`): block cipher modes of op
the F.2 vectors, round trips across the chunk boundary, a fresh IV per invocation, hex/binary
agreement, `--key-file` in both hex and binary, and every error path with its message.
-`core`: new `BlockPermutation` trait (`crypto/core/src/traits.rs`), the raw
+`core`: new `ElectronicCodeBook` trait (`crypto/core/src/traits.rs`), the raw
keyed permutation -- `CIPH_K` / `CIPH^-1_K` of SP 800-38A Sec 5.1 -- that a mode is built on.
`new`, `encrypt_block`, `decrypt_block`, plus provided `encrypt_blocks2` / `decrypt_blocks2` that
default to two single-block calls and which bit-sliced implementations override. The block methods
are infallible; only `new` can fail, and only on the key. `bouncycastle-aes-lowmemory` implements
-it for all three key lengths (and `BlockCipher`, which is metadata only and is
-`BlockPermutation`'s supertrait; the data-encryption traits are still deliberately not
-implemented there).
+it for all three key lengths (the data-encryption traits are still deliberately not implemented
+there).
Testing:
-* `core-test-framework` gains `TestFrameworkBlockPermutation`, which pins the trait contract:
+* `core-test-framework` gains `TestFrameworkElectronicCodeBook`, which pins the trait contract:
both directions are inverses either way round, the permutation is injective, and the pair
methods are indistinguishable from two single-block calls **including their order** -- the check
that makes an override safe.
@@ -130,8 +130,10 @@ Testing:
Block cipher traits (PR #96):
* The single `BlockCipher` streaming trait is split into `BlockCipherEncryptor` and `BlockCipherDecryptor` (mirroring
- `KEMEncapsulator` / `KEMDecapsulator`) so the direction is encoded in the implementing type. A minimal `BlockCipher`
- supertrait carries the shared `MAX_SECURITY_STRENGTH`; the `SymmetricCipher` one-shot API is no longer a supertrait.
+ `KEMEncapsulator` / `KEMDecapsulator`) so the direction is encoded in the implementing type. Both, and
+ `ElectronicCodeBook`, are bounded on `Algorithm`, whose `MAX_SECURITY_STRENGTH` is the strength the `_init`
+ constructors enforce (a mode reports its permutation's name and strength); the `SymmetricCipher` one-shot API is no
+ longer a supertrait.
* The single-block `do_{en,de}crypt_block[_out]` methods are replaced by multi-block
`do_{en,de}crypt_blocks[_out]`, taking `&[[u8; BLOCK_LEN]; N]` so the block count is compile-time and
input/output lengths cannot disagree.
@@ -139,10 +141,19 @@ Block cipher traits (PR #96):
pattern.
* The `do_{en,de}crypt_final[_out]` methods are removed: the traits are now strictly block-aligned, and padding of
arbitrary-length data belongs to a separate `PaddedEncryptor` / `PaddedDecryptor` layer built on top.
-* One-shot static APIs are provided (default) methods implemented once in the traits -- `encrypt_blocks`,
- `encrypt_blocks_rng`, `encrypt_blocks_out`, `encrypt_blocks_out_rng` on `BlockCipherEncryptor` and `decrypt_blocks`,
- `decrypt_blocks_out` on `BlockCipherDecryptor` -- so every block-aligned mode gets the house-standard
- take-data-return-result API at no cost to implementors.
+* One-shot static APIs are provided (default) methods implemented once in the traits -- `encrypt`, `encrypt_rng` on
+ `BlockCipherEncryptor` and `decrypt` on `BlockCipherDecryptor` -- so every block-aligned mode gets the
+ house-standard one-shot API at no cost to implementors. They take a flat `&mut [u8; LEN]` and work **in place**
+ (plaintext in, ciphertext out in the same bytes; `encrypt` returns the generated init data). `LEN` must be a whole
+ number of blocks, and this is enforced at **compile time** by an inline `const` assertion at the instantiating call
+ site, so there is no runtime length check and no error variant for it. Data whose length is only known at run
+ time goes block by block or through the padding layer. (Earlier forms took `[[u8; BLOCK_LEN]; N]`, then separate
+ input and output arrays; both were replaced before release.)
+* The streaming API is flat and in place as well: `do_{en,de}crypt(&mut [u8; LEN])`, with the same compile-time
+ alignment check, are provided methods. The single block-shaped method left is the implementor hook
+ `do_{en,de}crypt_blocks(&mut [[u8; BLOCK_LEN]; N])`, which is what guarantees an implementation never sees a
+ partial block; an implementor writes only `do_{en,de}crypt_init[_rng]` and that hook. The data methods keep a
+ `Result` only for modes with a per-initialization data limit (counter-based modes); CBC never fails them.
Testing:
diff --git a/cli/src/aes_cbc_cmd.rs b/cli/src/aes_cbc_cmd.rs
index 40727c85..c338b0d3 100644
--- a/cli/src/aes_cbc_cmd.rs
+++ b/cli/src/aes_cbc_cmd.rs
@@ -37,7 +37,7 @@ use bouncycastle::core::key_material::{
KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations,
};
use bouncycastle::core::traits::{
- BlockCipherDecryptor, BlockCipherEncryptor, BlockPermutation, SecurityStrength,
+ BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook, SecurityStrength,
};
use bouncycastle::hex;
use bouncycastle::modes::{Cbc, Decrypting, Encrypting};
@@ -49,12 +49,12 @@ use std::{fs, io};
/// The AES block length in bytes.
const BLOCK_LEN: usize = 16;
-/// Blocks processed per call: 64 blocks = 1 KiB, matching the other streaming commands.
+/// Bytes processed per call: 1 KiB = 64 blocks, matching the other streaming commands.
///
-/// A whole chunk goes through `do_*_blocks[_out]::` in one call, which for decryption
-/// means 32 pairs down the `decrypt_blocks2` path. The at-most-63-block tail at end of input is
-/// flushed one block at a time; it is bounded, so its cost does not scale with the input.
-const CHUNK_BLOCKS: usize = 64;
+/// A full chunk goes through `do_*::` in one call, in place, which for decryption means
+/// 32 pairs down the `decrypt_blocks2` path. The at-most-63-block tail at end of input goes one
+/// block at a time; it is bounded, so its cost does not scale with the input.
+const CHUNK_LEN: usize = 64 * BLOCK_LEN;
#[derive(ValueEnum, Clone, Debug)]
pub(crate) enum AESCBCAction {
@@ -172,7 +172,7 @@ fn load_key(
/// Encrypts stdin to stdout, writing the generated IV first.
fn encrypt_stream(key: &KeyMaterial, output_hex: bool)
where
- P: BlockPermutation,
+ P: ElectronicCodeBook,
{
let (mut enc, iv) = Cbc::::do_encrypt_init(key)
.unwrap_or_else(|e| {
@@ -183,21 +183,18 @@ where
// The IV goes out ahead of the ciphertext, so `decrypt` can pick it up.
write_bytes_or_hex(&iv, output_hex);
- let mut out = [[0u8; BLOCK_LEN]; CHUNK_BLOCKS];
-
- stream_blocks(|blocks| match <&[[u8; BLOCK_LEN]; CHUNK_BLOCKS]>::try_from(blocks) {
- Ok(full_chunk) => {
- // Cannot fail: the mode's block methods are infallible for a constructed value.
- enc.do_encrypt_blocks_out(full_chunk, &mut out).unwrap();
- write_blocks(&out, output_hex);
- }
- Err(_) => {
- // The bounded tail at end of input.
- for block in blocks.iter() {
- let [c] = enc.do_encrypt_blocks(&[*block]).unwrap();
- write_bytes_or_hex(&c, output_hex);
+ // The cipher works in place: `data` holds plaintext on the way in and ciphertext on the way out.
+ stream_aligned(|data| {
+ if let Ok(chunk) = <&mut [u8; CHUNK_LEN]>::try_from(&mut *data) {
+ // Cannot fail: CBC has no per-IV data limit.
+ enc.do_encrypt(chunk).unwrap();
+ } else {
+ // The bounded tail at end of input: whole blocks, fewer than a chunk.
+ for block in data.as_chunks_mut::().0 {
+ enc.do_encrypt(block).unwrap();
}
}
+ write_bytes_or_hex(data, output_hex);
});
finish(output_hex);
@@ -206,7 +203,7 @@ where
/// Decrypts stdin to stdout, taking the IV from the first block of input.
fn decrypt_stream(key: &KeyMaterial, output_hex: bool)
where
- P: BlockPermutation,
+ P: ElectronicCodeBook,
{
// The leading block is the IV, not ciphertext.
let mut iv = [0u8; BLOCK_LEN];
@@ -224,90 +221,58 @@ where
exit(-1);
});
- let mut out = [[0u8; BLOCK_LEN]; CHUNK_BLOCKS];
-
- stream_blocks(|blocks| match <&[[u8; BLOCK_LEN]; CHUNK_BLOCKS]>::try_from(blocks) {
- Ok(full_chunk) => {
+ stream_aligned(|data| {
+ if let Ok(chunk) = <&mut [u8; CHUNK_LEN]>::try_from(&mut *data) {
// A full chunk is 32 pairs, so this is the `decrypt_blocks2` path.
- dec.do_decrypt_blocks_out(full_chunk, &mut out).unwrap();
- write_blocks(&out, output_hex);
- }
- Err(_) => {
- for block in blocks.iter() {
- let [p] = dec.do_decrypt_blocks(&[*block]).unwrap();
- write_bytes_or_hex(&p, output_hex);
+ dec.do_decrypt(chunk).unwrap();
+ } else {
+ for block in data.as_chunks_mut::().0 {
+ dec.do_decrypt(block).unwrap();
}
}
+ write_bytes_or_hex(data, output_hex);
});
finish(output_hex);
}
-/// Reads stdin a block at a time, calling `process` with a full `CHUNK_BLOCKS` slice whenever one
-/// is available and once more at end of input with whatever whole blocks remain.
+/// Reads stdin and hands it to `process` in block-aligned pieces, mutably so it can be transformed
+/// in place: a full `CHUNK_LEN` bytes each time one has accumulated, then once more at end of input
+/// with whatever whole blocks remain (fewer than a chunk). Reads need not respect block or chunk boundaries -- bytes simply accumulate in the
+/// buffer until it is full -- so a block split across two reads needs no special handling.
///
-/// `process` therefore sees a slice of exactly `CHUNK_BLOCKS` for every call but the last, which is
-/// how the callers can hand a fixed-size array to `do_*_blocks_out::` and fall back
-/// to single blocks only for the bounded tail.
-///
-/// Reads do not respect block boundaries, so a block can arrive split across two reads; the
-/// partial block is carried over rather than assumed complete. Input whose total length is not a
-/// multiple of `BLOCK_LEN` is an error, because CBC is not defined on a partial block and there is
-/// no padding layer to appeal to.
-fn stream_blocks(mut process: impl FnMut(&[[u8; BLOCK_LEN]])) {
- let mut staged = [[0u8; BLOCK_LEN]; CHUNK_BLOCKS];
- let mut read_buf = [0u8; BLOCK_LEN * CHUNK_BLOCKS];
- let mut partial = [0u8; BLOCK_LEN];
- let mut partial_len = 0usize;
- let mut blocks = 0usize;
+/// Input whose total length is not a multiple of `BLOCK_LEN` is an error, because CBC is not
+/// defined on a partial block and there is no padding layer to appeal to.
+fn stream_aligned(mut process: impl FnMut(&mut [u8])) {
+ let mut buf = [0u8; CHUNK_LEN];
+ let mut filled = 0usize;
loop {
- let n = io::stdin().read(&mut read_buf).unwrap_or_else(|e| {
+ let n = io::stdin().read(&mut buf[filled..]).unwrap_or_else(|e| {
eprintln!("Error: failed to read from stdin: {e}");
exit(-1);
});
if n == 0 {
break;
}
-
- let mut src = &read_buf[..n];
- while !src.is_empty() {
- let take = core::cmp::min(BLOCK_LEN - partial_len, src.len());
- partial[partial_len..partial_len + take].copy_from_slice(&src[..take]);
- partial_len += take;
- src = &src[take..];
-
- if partial_len == BLOCK_LEN {
- staged[blocks] = partial;
- blocks += 1;
- partial_len = 0;
-
- if blocks == CHUNK_BLOCKS {
- process(&staged);
- blocks = 0;
- }
- }
+ filled += n;
+ if filled == CHUNK_LEN {
+ process(&mut buf);
+ filled = 0;
}
}
- if partial_len != 0 {
+ if !filled.is_multiple_of(BLOCK_LEN) {
eprintln!(
- "Error: input is not a whole number of {BLOCK_LEN}-byte blocks ({partial_len} \
- trailing byte(s)). CBC is defined only on whole blocks (SP 800-38A Sec 5.2), and \
- this build has no padding layer, so the input must be padded by the caller."
+ "Error: input is not a whole number of {BLOCK_LEN}-byte blocks ({} trailing byte(s)). \
+ CBC is defined only on whole blocks (SP 800-38A Sec 5.2), and this build has no \
+ padding layer, so the input must be padded by the caller.",
+ filled % BLOCK_LEN
);
exit(-1);
}
-
- if blocks != 0 {
- process(&staged[..blocks]);
- }
-}
-
-/// Writes a run of whole blocks.
-fn write_blocks(blocks: &[[u8; BLOCK_LEN]], output_hex: bool) {
- for block in blocks.iter() {
- write_bytes_or_hex(block, output_hex);
+ if filled != 0 {
+ process(&mut buf[..filled]);
}
}
diff --git a/crypto/aes-lowmemory/Cargo.toml b/crypto/aes-lowmemory/Cargo.toml
index 93316d45..f6cbff4d 100644
--- a/crypto/aes-lowmemory/Cargo.toml
+++ b/crypto/aes-lowmemory/Cargo.toml
@@ -6,6 +6,8 @@ edition.workspace = true
[dependencies]
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
[dev-dependencies]
bouncycastle-core-test-framework.workspace = true
diff --git a/crypto/aes-lowmemory/src/aes.rs b/crypto/aes-lowmemory/src/aes.rs
index 1b889ab7..08198459 100644
--- a/crypto/aes-lowmemory/src/aes.rs
+++ b/crypto/aes-lowmemory/src/aes.rs
@@ -6,7 +6,7 @@ use crate::sbox::{inv_sbox, sbox};
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, BlockCipher, BlockPermutation, SecurityStrength};
+use bouncycastle_core::traits::{Algorithm, ElectronicCodeBook, SecurityStrength};
use bouncycastle_utils::secret::Secret;
/// The AES block length in bytes: 16 (FIPS 197 Sec 3.4, `Nb` = 4 words).
@@ -221,36 +221,14 @@ impl Algorithm for Aes256 {
const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit;
}
-// `BlockCipher` here is metadata only -- it declares `MAX_SECURITY_STRENGTH` and nothing else, and
-// it is the supertrait `BlockPermutation` requires. It is *not* one of the data-encryption traits
-// (`SymmetricCipher`, `BlockCipherEncryptor`, `BlockCipherDecryptor`, `AEADCipher`), which this
-// crate still deliberately does not implement: those are mode-of-operation concerns. See the crate
-// docs.
-//
-// Both `Algorithm` and `BlockCipher` declare `MAX_SECURITY_STRENGTH`, so a bare
-// `Aes128::MAX_SECURITY_STRENGTH` is ambiguous; qualify it as `::...` or
-// `::...` at the use site.
-
-impl BlockCipher for Aes128 {
- const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit;
-}
-
-impl BlockCipher for Aes192 {
- const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit;
-}
-
-impl BlockCipher for Aes256 {
- const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit;
-}
-
-// The three `BlockPermutation` impls are one-line delegations to the inherent methods above. They
+// 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
// 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 BlockPermutation<16, BLOCK_LEN> for Aes128 {
+impl ElectronicCodeBook<16, BLOCK_LEN> for Aes128 {
fn new(key: &KeyMaterial<16>) -> Result {
Aes128::new(key)
}
@@ -268,7 +246,7 @@ impl BlockPermutation<16, BLOCK_LEN> for Aes128 {
}
}
-impl BlockPermutation<24, BLOCK_LEN> for Aes192 {
+impl ElectronicCodeBook<24, BLOCK_LEN> for Aes192 {
fn new(key: &KeyMaterial<24>) -> Result {
Aes192::new(key)
}
@@ -286,7 +264,7 @@ impl BlockPermutation<24, BLOCK_LEN> for Aes192 {
}
}
-impl BlockPermutation<32, BLOCK_LEN> for Aes256 {
+impl ElectronicCodeBook<32, BLOCK_LEN> for Aes256 {
fn new(key: &KeyMaterial<32>) -> Result {
Aes256::new(key)
}
diff --git a/crypto/aes-lowmemory/src/cbc.rs b/crypto/aes-lowmemory/src/cbc.rs
new file mode 100644
index 00000000..d68f6e2a
--- /dev/null
+++ b/crypto/aes-lowmemory/src/cbc.rs
@@ -0,0 +1,93 @@
+//! 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/lib.rs b/crypto/aes-lowmemory/src/lib.rs
index 866a5167..c7ede6c5 100644
--- a/crypto/aes-lowmemory/src/lib.rs
+++ b/crypto/aes-lowmemory/src/lib.rs
@@ -56,6 +56,32 @@
//! assert_eq!(blocks, [[0u8; 16], [1u8; 16]]);
//! ```
//!
+//! ## CBC mode
+//!
+//! To encrypt more than one block, use a mode of operation from `bouncycastle-modes`. This crate
+//! provides [`AES_CBC_128`], [`AES_CBC_192`] and [`AES_CBC_256`] as aliases that fill in the const
+//! parameters, with the direction left as the type parameter:
+//!
+//! ```
+//! 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)
+//! .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);
+//! ```
+//!
//! There is no one-shot static on the permutation, because `Aes128::new(&key)?.encrypt_block(..)`
//! already *is* the one shot. Data-level one-shots belong to the modes of operation, which take
//! arbitrary-length input and generate their own initialisation data.
@@ -166,10 +192,12 @@
mod aes;
mod bitslice;
+mod cbc;
mod round;
mod sbox;
mod schedule;
pub use aes::{Aes, Aes128, Aes192, Aes256, BLOCK_LEN};
pub use bitslice::Block;
+pub use cbc::{AES_CBC_128, AES_CBC_192, AES_CBC_256};
pub use schedule::{Aes128Params, Aes192Params, Aes256Params, AesParams};
diff --git a/crypto/aes-lowmemory/summary.md b/crypto/aes-lowmemory/summary.md
index 20ab8fca..cf300350 100644
--- a/crypto/aes-lowmemory/summary.md
+++ b/crypto/aes-lowmemory/summary.md
@@ -408,7 +408,7 @@ files (see the ML-KEM and ML-DSA suites).
| Item | Why |
|---|---|
-| `BlockPermutation` trait impls, and `encrypt_blocks2`/`decrypt_blocks2` as trait methods | The trait does not exist in `crypto/core`, which has the mode-level `BlockCipher` / `BlockCipherEncryptor` / `BlockCipherDecryptor`. Introducing it is the plan's separate "PR A". The two-block entry points are inherent methods for now; promoting them to provided trait methods is a one-line delegation once the trait lands. |
+| `ElectronicCodeBook` trait impls, and `encrypt_blocks2`/`decrypt_blocks2` as trait methods | The trait does not exist in `crypto/core`, which has the mode-level `BlockCipher` / `BlockCipherEncryptor` / `BlockCipherDecryptor`. Introducing it is the plan's separate "PR A". The two-block entry points are inherent methods for now; promoting them to provided trait methods is a one-line delegation once the trait lands. |
| `core-test-framework` conformance test | Follows from the above — there is no test suite for a raw permutation yet. |
| ACVP MCT (Monte Carlo) groups — 6 cases | Their expected `resultsArray` comes from a chained key/plaintext update rule defined in the ACVP AES specification, not in FIPS 197. Implementing it from anything other than that specification would be guesswork. The test reports the skip count so the gap is visible rather than silent. |
| CLI subcommand | A bare permutation only does ECB. `aes128-cbc-*` / `-cfb-*` belong with the modes crate. |
@@ -477,7 +477,7 @@ they print a warning and pass.
than a technical one.
2. **Confirm the PR base branch.** The plan specifies `release/0.1.3alpha`, set explicitly — GitHub
defaults to `main`.
-3. Decide whether `BlockPermutation` (plan PR A) lands before or after this crate, since it
+3. Decide whether `ElectronicCodeBook` (plan PR A) lands before or after this crate, since it
determines whether the two-block entry points become trait methods now or later (§6).
4. Note in the PR description that the plan's layout claim (§5.1) and PR B (§5.3) are superseded, so
the plan document does not mislead the next reader.
diff --git a/crypto/aes-lowmemory/tests/block_permutation_tests.rs b/crypto/aes-lowmemory/tests/electronic_code_book_tests.rs
similarity index 50%
rename from crypto/aes-lowmemory/tests/block_permutation_tests.rs
rename to crypto/aes-lowmemory/tests/electronic_code_book_tests.rs
index d6119d97..2098315e 100644
--- a/crypto/aes-lowmemory/tests/block_permutation_tests.rs
+++ b/crypto/aes-lowmemory/tests/electronic_code_book_tests.rs
@@ -1,4 +1,4 @@
-//! `BlockPermutation` trait conformance, via the shared test framework.
+//! `ElectronicCodeBook` trait conformance, via the shared test framework.
//!
//! The framework checks the properties every implementor must have -- both directions are
//! inverses, the permutation is injective, the pair methods are indistinguishable from two
@@ -7,19 +7,19 @@
//! `decrypt_blocks2`, so the default implementation is not what runs.
use bouncycastle_aes_lowmemory::{Aes128, Aes192, Aes256, BLOCK_LEN};
-use bouncycastle_core_test_framework::block_permutation::TestFrameworkBlockPermutation;
+use bouncycastle_core_test_framework::electronic_code_book::TestFrameworkElectronicCodeBook;
#[test]
-fn aes128_conforms_to_block_permutation() {
- TestFrameworkBlockPermutation::new().test::<16, BLOCK_LEN, Aes128>();
+fn aes128_conforms_to_electronic_code_book() {
+ TestFrameworkElectronicCodeBook::new().test::<16, BLOCK_LEN, Aes128>();
}
#[test]
-fn aes192_conforms_to_block_permutation() {
- TestFrameworkBlockPermutation::new().test::<24, BLOCK_LEN, Aes192>();
+fn aes192_conforms_to_electronic_code_book() {
+ TestFrameworkElectronicCodeBook::new().test::<24, BLOCK_LEN, Aes192>();
}
#[test]
-fn aes256_conforms_to_block_permutation() {
- TestFrameworkBlockPermutation::new().test::<32, BLOCK_LEN, Aes256>();
+fn aes256_conforms_to_electronic_code_book() {
+ TestFrameworkElectronicCodeBook::new().test::<32, BLOCK_LEN, Aes256>();
}
diff --git a/crypto/core-test-framework/src/block_permutation.rs b/crypto/core-test-framework/src/electronic_code_book.rs
similarity index 91%
rename from crypto/core-test-framework/src/block_permutation.rs
rename to crypto/core-test-framework/src/electronic_code_book.rs
index 6eed66fe..1c41a35d 100644
--- a/crypto/core-test-framework/src/block_permutation.rs
+++ b/crypto/core-test-framework/src/electronic_code_book.rs
@@ -1,24 +1,24 @@
-//! Shared conformance tests for [`BlockPermutation`] implementors.
+//! Shared conformance tests for [`ElectronicCodeBook`] implementors.
use crate::DUMMY_SEED;
use bouncycastle_core::errors::SymmetricCipherError;
use bouncycastle_core::key_material::{
KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations,
};
-use bouncycastle_core::traits::{BlockCipher, BlockPermutation, SecurityStrength};
+use bouncycastle_core::traits::{ElectronicCodeBook, SecurityStrength};
/// Instance of the test framework.
-pub struct TestFrameworkBlockPermutation {
+pub struct TestFrameworkElectronicCodeBook {
// Put any config options here
}
-impl Default for TestFrameworkBlockPermutation {
+impl Default for TestFrameworkElectronicCodeBook {
fn default() -> Self {
Self::new()
}
}
-impl TestFrameworkBlockPermutation {
+impl TestFrameworkElectronicCodeBook {
///
pub fn new() -> Self {
Self {}
@@ -35,11 +35,13 @@ impl TestFrameworkBlockPermutation {
/// semantics, and it is the reason the pair methods are worth having in the trait at all;
/// * the pair methods round-trip each other;
/// * a key of the wrong [`KeyType`] is rejected;
- /// * the security-strength policy matches [`BlockCipher::MAX_SECURITY_STRENGTH`].
+ /// * the security-strength policy matches [`Algorithm::MAX_SECURITY_STRENGTH`].
+ ///
+ /// [`Algorithm::MAX_SECURITY_STRENGTH`]: bouncycastle_core::traits::Algorithm::MAX_SECURITY_STRENGTH
pub fn test<
const KEY_LEN: usize,
const BLOCK_LEN: usize,
- P: BlockPermutation,
+ P: ElectronicCodeBook,
>(
&self,
) {
@@ -152,11 +154,11 @@ impl TestFrameworkBlockPermutation {
match P::new(&key) {
Ok(_) => assert!(
- ss >= &::MAX_SECURITY_STRENGTH,
+ ss >= &P::MAX_SECURITY_STRENGTH,
"should have required a key at least as strong as the algorithm"
),
Err(SymmetricCipherError::KeyMaterialError(_)) => assert!(
- ss < &
::MAX_SECURITY_STRENGTH,
+ ss < &P::MAX_SECURITY_STRENGTH,
"should not have rejected a key strong enough for the algorithm"
),
_ => panic!("Unexpected error"),
diff --git a/crypto/core-test-framework/src/lib.rs b/crypto/core-test-framework/src/lib.rs
index f5519d95..45d922e4 100644
--- a/crypto/core-test-framework/src/lib.rs
+++ b/crypto/core-test-framework/src/lib.rs
@@ -14,7 +14,7 @@
// properly document everything.
#![forbid(missing_docs)]
-pub mod block_permutation;
+pub mod electronic_code_book;
pub mod hash;
pub mod kdf;
pub mod kem;
diff --git a/crypto/core-test-framework/src/symmetric_ciphers.rs b/crypto/core-test-framework/src/symmetric_ciphers.rs
index 180e5851..07c0584c 100644
--- a/crypto/core-test-framework/src/symmetric_ciphers.rs
+++ b/crypto/core-test-framework/src/symmetric_ciphers.rs
@@ -1,6 +1,6 @@
//! Generic behaviour tests for the symmetric cipher traits.
-use crate::DUMMY_SEED;
+use crate::{DUMMY_SEED, FixedSeedRNG};
use bouncycastle_core::errors::SymmetricCipherError;
use bouncycastle_core::key_material::{
KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations,
@@ -140,75 +140,71 @@ impl TestFrameworkBlockCipher {
let (mut encryptor, iv) = E::do_encrypt_init(&key).unwrap();
let mut decryptor = D::do_decrypt_init(&key, &iv).unwrap();
- // one block at a time (N = 1)
+ // one block at a time, through the flat streaming methods (LEN = BLOCK_LEN), in place
for msg_chunk in DUMMY_SEED.as_chunks::().0.iter() {
- let ct = encryptor.do_encrypt_blocks(&[*msg_chunk]).unwrap();
- let [pt] = decryptor.do_decrypt_blocks(&ct).unwrap();
- assert_eq!(msg_chunk, &pt);
+ let mut buf = *msg_chunk;
+ encryptor.do_encrypt(&mut buf).unwrap();
+ decryptor.do_decrypt(&mut buf).unwrap();
+ assert_eq!(msg_chunk, &buf);
}
- // do it again using the _out versions
-
- let (mut encryptor, iv) = E::do_encrypt_init(&key).unwrap();
- let mut decryptor = D::do_decrypt_init(&key, &iv).unwrap();
-
- let mut ct = [[0u8; BLOCK_LEN]; 1];
- let mut pt = [[0u8; BLOCK_LEN]; 1];
- for msg_chunk in DUMMY_SEED.as_chunks::().0.iter() {
- let ct_bytes_written = encryptor.do_encrypt_blocks_out(&[*msg_chunk], &mut ct).unwrap();
- assert_eq!(ct_bytes_written, BLOCK_LEN);
-
- let pt_bytes_written = decryptor.do_decrypt_blocks_out(&ct, &mut pt).unwrap();
- assert_eq!(pt_bytes_written, BLOCK_LEN);
-
- assert_eq!(msg_chunk, &pt[0]);
- }
-
- // multi-block (N = 2): blocks encrypted together must decrypt both together and one at a time,
- // and blocks encrypted one at a time must decrypt together.
+ // multi-block (N = 2) through the implementor hook `do_*_blocks`: blocks encrypted together
+ // must decrypt both together and one at a time, and blocks encrypted one at a time must
+ // decrypt together.
let (mut encryptor, iv) = E::do_encrypt_init(&key).unwrap();
let mut decryptor = D::do_decrypt_init(&key, &iv).unwrap();
- let mut ct = [[0u8; BLOCK_LEN]; 2];
- let mut pt = [[0u8; BLOCK_LEN]; 2];
for msg_pair in DUMMY_SEED.as_chunks::().0.as_chunks::<2>().0.iter() {
- // encrypt together, decrypt together (by value)
- let ct_by_value = encryptor.do_encrypt_blocks(msg_pair).unwrap();
- let pt_by_value = decryptor.do_decrypt_blocks(&ct_by_value).unwrap();
- assert_eq!(msg_pair, &pt_by_value);
-
- // encrypt together (_out), decrypt one at a time
- let ct_bytes_written = encryptor.do_encrypt_blocks_out(msg_pair, &mut ct).unwrap();
- assert_eq!(ct_bytes_written, 2 * BLOCK_LEN);
- for (msg_chunk, ct_chunk) in msg_pair.iter().zip(ct.iter()) {
- let [pt] = decryptor.do_decrypt_blocks(&[*ct_chunk]).unwrap();
- assert_eq!(msg_chunk, &pt);
+ // encrypt together, decrypt together
+ let mut buf = *msg_pair;
+ encryptor.do_encrypt_blocks(&mut buf).unwrap();
+ decryptor.do_decrypt_blocks(&mut buf).unwrap();
+ assert_eq!(msg_pair, &buf);
+
+ // encrypt together, decrypt one at a time
+ let mut buf = *msg_pair;
+ encryptor.do_encrypt_blocks(&mut buf).unwrap();
+ for (msg_chunk, block) in msg_pair.iter().zip(buf.iter_mut()) {
+ decryptor.do_decrypt(block).unwrap();
+ assert_eq!(msg_chunk, block);
}
- // encrypt one at a time, decrypt together (_out)
- for (msg_chunk, ct_chunk) in msg_pair.iter().zip(ct.iter_mut()) {
- let [c] = encryptor.do_encrypt_blocks(&[*msg_chunk]).unwrap();
- *ct_chunk = c;
+ // encrypt one at a time, decrypt together
+ let mut buf = *msg_pair;
+ for block in buf.iter_mut() {
+ encryptor.do_encrypt(block).unwrap();
}
- let pt_bytes_written = decryptor.do_decrypt_blocks_out(&ct, &mut pt).unwrap();
- assert_eq!(pt_bytes_written, 2 * BLOCK_LEN);
- assert_eq!(msg_pair, &pt);
+ decryptor.do_decrypt_blocks(&mut buf).unwrap();
+ assert_eq!(msg_pair, &buf);
}
- // one-shot API: must agree with the streaming API for the same key, and round-trip
- let two_blocks: &[[u8; BLOCK_LEN]; 2] =
- &DUMMY_SEED.as_chunks::().0.as_chunks::<2>().0[0];
- let (iv, ct) = E::encrypt_blocks(&key, two_blocks).unwrap();
- assert_eq!(D::decrypt_blocks(&key, &iv, &ct).unwrap(), *two_blocks);
+ // one-shot API: a block-aligned byte array, in place. It must round-trip and agree with the
+ // streaming API for the same key and init data. Only LEN = BLOCK_LEN can be formed
+ // generically here (`2 * BLOCK_LEN` needs generic_const_exprs); multi-block one-shots are
+ // covered by the modes crate's tests with a concrete BLOCK_LEN.
+ let one_block: &[u8; BLOCK_LEN] = &DUMMY_SEED.as_chunks::().0[0];
+ let mut buf = *one_block;
+ let iv = E::encrypt(&key, &mut buf).unwrap();
+ let ct = buf;
+ D::decrypt(&key, &iv, &mut buf).unwrap();
+ assert_eq!(buf, *one_block);
+ // ...and it must agree with the streaming API under the same init data.
let mut streamed = D::do_decrypt_init(&key, &iv).unwrap();
- assert_eq!(streamed.do_decrypt_blocks(&ct).unwrap(), *two_blocks);
-
- let mut ct = [[0u8; BLOCK_LEN]; 2];
- let mut pt = [[0u8; BLOCK_LEN]; 2];
- let (iv, n) = E::encrypt_blocks_out(&key, two_blocks, &mut ct).unwrap();
- assert_eq!(n, 2 * BLOCK_LEN);
- assert_eq!(D::decrypt_blocks_out(&key, &iv, &ct, &mut pt).unwrap(), 2 * BLOCK_LEN);
- assert_eq!(pt, *two_blocks);
+ let mut buf = ct;
+ streamed.do_decrypt(&mut buf).unwrap();
+ assert_eq!(buf, *one_block);
+
+ // the RNG-taking one-shot must give the streaming API's answer for the same RNG stream
+ let pinned = [0xA5u8; INIT_DATA_LEN];
+ let mut expected = *one_block;
+ let (mut streamed, iv_streamed) =
+ E::do_encrypt_init_rng(&key, &mut FixedSeedRNG::::new(pinned)).unwrap();
+ streamed.do_encrypt(&mut expected).unwrap();
+ let mut buf = *one_block;
+ let iv = E::encrypt_rng(&key, &mut FixedSeedRNG::::new(pinned), &mut buf)
+ .unwrap();
+ assert_eq!(iv, iv_streamed);
+ assert_eq!(buf, expected);
// test that the iv is random (ie not the same on two runs)
let (_encryptor, iv1) = E::do_encrypt_init(&key).unwrap();
diff --git a/crypto/core-test-framework/summary.md b/crypto/core-test-framework/summary.md
index dcd404e7..738de37a 100644
--- a/crypto/core-test-framework/summary.md
+++ b/crypto/core-test-framework/summary.md
@@ -1,8 +1,8 @@
-# `crypto/core-test-framework` — changes for `BlockPermutation` and CBC
+# `crypto/core-test-framework` — changes for `ElectronicCodeBook` and CBC
Changes made on branch `feature/officialfrancismendoza/100-AES-lightengine-CBC-mode` while adding
`crypto/aes-lowmemory` and `crypto/modes`. Two things: a **new** per-trait suite for
-`core::traits::BlockPermutation`, and a **bug fix** to the existing `TestFrameworkBlockCipher`.
+`core::traits::ElectronicCodeBook`, and a **bug fix** to the existing `TestFrameworkBlockCipher`.
For what this crate is for in general, see its [`src/lib.rs`](src/lib.rs) docs: one KAT-style
harness per `core` trait, so that behaviour which should be consistent across implementations of a
@@ -11,17 +11,17 @@ here rather than re-written per implementation.
---
-## 1. New: `TestFrameworkBlockPermutation`
+## 1. New: `TestFrameworkElectronicCodeBook`
-[`src/block_permutation.rs`](src/block_permutation.rs), registered as `pub mod block_permutation;`
+[`src/electronic_code_book.rs`](src/electronic_code_book.rs), registered as `pub mod electronic_code_book;`
in [`src/lib.rs`](src/lib.rs).
-`core::traits::BlockPermutation` is new in this branch: the raw keyed
+`core::traits::ElectronicCodeBook` is new in this branch: the raw keyed
permutation (`CIPH_K` / `CIPH^-1_K` of SP 800-38A Sec 5.1) that a mode of operation is built on.
It needed a conformance suite like every other `core` trait.
```rust
-TestFrameworkBlockPermutation::new().test::();
+TestFrameworkElectronicCodeBook::new().test::();
```
### What it checks, and why each check exists
@@ -39,7 +39,7 @@ TestFrameworkBlockPermutation::new().test::();
### The order check is the load-bearing one
-`BlockPermutation::encrypt_blocks2` and `decrypt_blocks2` are *provided* methods: the default is
+`ElectronicCodeBook::encrypt_blocks2` and `decrypt_blocks2` are *provided* methods: the default is
two single-block calls, and implementations are free to override them. `bouncycastle-aes-lowmemory`
does, because a pair of blocks is exactly what its bit-sliced state holds, so the pair form costs
barely more than one block.
@@ -56,7 +56,7 @@ takes the pair path.
### Current implementors
-* `crypto/aes-lowmemory/tests/block_permutation_tests.rs` — AES-128, AES-192, AES-256.
+* `crypto/aes-lowmemory/tests/electronic_code_book_tests.rs` — AES-128, AES-192, AES-256.
* `crypto/modes/tests/cbc_tests.rs` — the toy permutation, checked before anything is concluded
from it.
@@ -169,7 +169,7 @@ cargo fmt --all -- --check
This crate has no tests of its own — it *is* tests — so it is verified by its consumers. The two
new suites are exercised by:
-* `cargo test -p bouncycastle-aes-lowmemory --test block_permutation_tests` (3 tests)
+* `cargo test -p bouncycastle-aes-lowmemory --test electronic_code_book_tests` (3 tests)
* `cargo test -p bouncycastle-modes --test cbc_tests` (11 tests, including
`cbc_conforms_to_the_block_cipher_framework`, which is what the §2 fix unblocked, and
`the_toy_permutation_conforms_to_the_trait`)
@@ -180,7 +180,7 @@ new suites are exercised by:
1. **Fix the same loop in `TestFrameworkSymmetricCipher` and `TestFrameworkAEADCipher`** (§3).
Three lines each, and the next implementor of either trait will otherwise hit the panic.
-2. **Decide whether the `Default` impl added to `TestFrameworkBlockPermutation` should be added to
+2. **Decide whether the `Default` impl added to `TestFrameworkElectronicCodeBook` should be added to
the other suites** for consistency — they all have `new()` and no `Default`, which clippy
flags on new code but not on existing code.
3. When `crypto/padding` (PR #97) merges, its toy XOR-CBC cipher becomes a second
diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs
index 2092d7bd..28402454 100644
--- a/crypto/core/src/traits.rs
+++ b/crypto/core/src/traits.rs
@@ -12,137 +12,142 @@ use crate::key_material::KeyMaterial;
use crate::key_material::KeyType;
// end of imports needed for docs
-/// Metadata about a cryptographic algorithm.
-pub trait Algorithm {
- /// String name for the algorithm, used consistently across the library.
- const ALG_NAME: &'static str;
- /// Maximum security strength supported by the algorithm.
- /// In other words, this algorithm can produce outputs up to this security strength,
- /// but may produce outputs with lower security strength, for example, if asked to truncate.
- const MAX_SECURITY_STRENGTH: SecurityStrength;
-}
-
-/// Some algorithms have an assigned OID.
-pub trait AlgorithmOID {
- /// The OID in component form -- each u32 is one OID component.
- const OID: &'static [u32];
- /// The OID in its DER-encoded form.
- const OID_DER: &'static [u8];
-}
-
-// todo -- split all the SymmetricCipher traits into Encryptor and Decryptor
-/// 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 {
+/// The basic functions of an Authenticated Encryption with Addititional Data cipher.
+pub trait AEADCipher:
+ SymmetricCipher + Sized
+{
#[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)
+ /// that is not encrypted but is protected by the authentication tag; ie it can be sent along with the ciphertext
+ /// and any tampering with it will result in the decryption operation failing the tag check.
/// 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(
+ /// Returns a tuple containing a generated nonce, the ciphertext and the tag.
+ fn aead_encrypt(
key: &KeyMaterial,
+ aad: &[u8],
plaintext: &[u8],
- ) -> Result<([u8; INIT_DATA_LEN], Vec), SymmetricCipherError>;
+ ) -> Result<([u8; NONCE_LEN], Vec, [u8; TAG_LEN]), 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(
+ /// A distinguishing feature of AEAD ciphers is the ability to provide additional authenticated data (AAD)
+ /// that is not encrypted but is protected by the authentication tag; ie it can be sent along with the ciphertext
+ /// and any tampering with it will result in the decryption operation failing the tag check.
+ /// Returns a tuple containing the randomly-generated nonce, number of bytes written to the ciphertext buffer, and the tag.
+ /// If you need a deterministic mode where you feed in the nonce, use the streaming API of [`BlockCipherEncryptor`]
+ /// or [`StreamCipher`] as appropriate and feed the nonce into the IV field.
+ fn aead_encrypt_out(
key: &KeyMaterial,
+ aad: &[u8],
plaintext: &[u8],
ciphertext: &mut [u8],
- ) -> Result<([u8; INIT_DATA_LEN], usize), SymmetricCipherError>;
+ ) -> Result<([u8; NONCE_LEN], usize, [u8; TAG_LEN]), SymmetricCipherError>;
+ /// All AEAD ciphers will also be either a block cipher ([`BlockCipherEncryptor`] / [`BlockCipherDecryptor`]) or a [`StreamCipher`], and so will already
+ /// have a streaming API.
+ /// This allows you to finish either style of streaming API flow with AEAD specific do_final()
+ /// that computes and returns the authentication tag.
+ fn do_aead_encrypt_final(self) -> Result<[u8; TAG_LEN], 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(
+ fn aead_decrypt(
key: &KeyMaterial,
- init_data: [u8; INIT_DATA_LEN],
+ nonce: &[u8; NONCE_LEN],
+ aad: &[u8],
ciphertext: &[u8],
+ tag: &[u8; TAG_LEN],
) -> 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(
+ /// Returns the number of bytes written to the plaintext buffer.
+ fn aead_decrypt_out(
key: &KeyMaterial,
- init_data: [u8; INIT_DATA_LEN],
+ nonce: &[u8; NONCE_LEN],
+ aad: &[u8],
ciphertext: &[u8],
+ tag: &[u8; TAG_LEN],
plaintext: &mut [u8],
) -> Result;
+ /// All AEAD ciphers will also be either a block cipher ([`BlockCipherEncryptor`] / [`BlockCipherDecryptor`]) or a [`StreamCipher`], and so will already
+ /// have a streaming API.
+ /// This allows you to finish either style of streaming API flow with AEAD specific do_final()
+ /// that computes and returns the authentication tag.
+ fn do_aead_decrypt_final(self, tag: &[u8; TAG_LEN]) -> Result<(), SymmetricCipherError>;
}
-/// Metadata shared by [`BlockCipherEncryptor`] and [`BlockCipherDecryptor`].
-pub trait BlockCipher {
- /// Maximum security strength supported by the algorithm; keys tagged with a lower strength are
- /// rejected by the `_init` constructors.
+/// Metadata about a cryptographic algorithm.
+pub trait Algorithm {
+ /// String name for the algorithm, used consistently across the library.
+ const ALG_NAME: &'static str;
+ /// Maximum security strength supported by the algorithm.
+ /// In other words, this algorithm can produce outputs up to this security strength,
+ /// but may produce outputs with lower security strength, for example, if asked to truncate.
const MAX_SECURITY_STRENGTH: SecurityStrength;
}
-/// A keyed block permutation: the `CIPH_K` / `CIPH^-1_K` of NIST SP 800-38A Sec 5.1.
-///
-/// This is the raw primitive a mode of operation is built on, not something to encrypt data with.
-/// It transforms exactly one block, so applying it directly to data is ECB, which is not
-/// confidential. [`BlockCipherEncryptor`] and [`BlockCipherDecryptor`] are the *mode* traits --
-/// they carry initialization data and chaining state; this one carries only a key schedule.
-///
-/// Implementors are expected to hold that key schedule in a zeroize-on-drop wrapper
-/// (`bouncycastle_utils::secret::Secret`), so it is scrubbed when the value is dropped.
-///
-/// # Why the block methods are infallible
-///
-/// Every length here is fixed by a type, and a constructed value is always ready to use, so there
-/// is nothing a caller can get wrong once [`BlockPermutation::new`] has returned. Only `new` can
-/// fail, and only because of the key.
-pub trait BlockPermutation:
- BlockCipher + Sized
-{
- /// Expands the key.
- ///
- /// # Errors
- /// Rejects a key whose [`KeyType`] is not [`KeyType::SymmetricCipherKey`], and one whose
- /// security strength is below [`BlockCipher::MAX_SECURITY_STRENGTH`], both as a
- /// [`SymmetricCipherError::KeyMaterialError`].
- fn new(key: &KeyMaterial) -> Result;
-
- /// The forward cipher function, in place.
- fn encrypt_block(&self, block: &mut [u8; BLOCK_LEN]);
+/// Some algorithms have an assigned OID.
+pub trait AlgorithmOID {
+ /// The OID in component form -- each u32 is one OID component.
+ const OID: &'static [u32];
+ /// The OID in its DER-encoded form.
+ const OID_DER: &'static [u8];
+}
- /// The inverse cipher function, in place.
- fn decrypt_block(&self, block: &mut [u8; BLOCK_LEN]);
+/// The decryption half of a block cipher's streaming API; see [`BlockCipherEncryptor`], whose
+/// notes on in-place operation, compile-time lengths and the `Result` all apply here too.
+pub trait BlockCipherDecryptor<
+ const KEY_LEN: usize,
+ const INIT_DATA_LEN: usize,
+ const BLOCK_LEN: usize,
+>: Algorithm + Sized
+{
+ /// Begins a streaming decryption flow from the init data returned by [`BlockCipherEncryptor::do_encrypt_init`].
+ fn do_decrypt_init(
+ key: &KeyMaterial,
+ init_data: &[u8; INIT_DATA_LEN],
+ ) -> Result;
+ /// The implementor hook: decrypts `N` consecutive whole blocks in place. See
+ /// [`BlockCipherEncryptor::do_encrypt_blocks`]; callers should normally use the flat
+ /// [`BlockCipherDecryptor::do_decrypt`] instead.
+ fn do_decrypt_blocks(
+ &mut self,
+ blocks: &mut [[u8; BLOCK_LEN]; N],
+ ) -> Result<(), SymmetricCipherError>;
- /// The forward cipher function on two *independent* blocks, in place.
- ///
- /// Provided as two [`BlockPermutation::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`.
- ///
- /// Overrides must be indistinguishable from the default, including the order of the two
- /// results. `TestFrameworkBlockPermutation` pins that.
- ///
- /// 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]) {
- let [a, b] = blocks;
- self.encrypt_block(a);
- self.encrypt_block(b);
+ /// Streaming: decrypts `LEN` bytes, a whole number of blocks, in place. `LEN % BLOCK_LEN == 0`
+ /// is checked at compile time, and the blocks are fed to the hook pairs first, then the tail,
+ /// exactly as for [`BlockCipherEncryptor::do_encrypt`].
+ fn do_decrypt(
+ &mut self,
+ data: &mut [u8; LEN],
+ ) -> Result<(), SymmetricCipherError> {
+ const {
+ assert!(
+ LEN.is_multiple_of(BLOCK_LEN),
+ "length must be a whole number of BLOCK_LEN-byte blocks"
+ )
+ };
+ let (blocks, _) = data.as_chunks_mut::();
+ let (pairs, tail) = blocks.as_chunks_mut::<2>();
+ for pair in pairs.iter_mut() {
+ self.do_decrypt_blocks(pair)?;
+ }
+ for block in tail.iter_mut() {
+ self.do_decrypt_blocks(core::array::from_mut(block))?;
+ }
+ Ok(())
}
- /// The inverse cipher function on two *independent* blocks, in place.
- /// See [`BlockPermutation::encrypt_blocks2`].
- fn decrypt_blocks2(&self, blocks: &mut [[u8; BLOCK_LEN]; 2]) {
- let [a, b] = blocks;
- self.decrypt_block(a);
- self.decrypt_block(b);
+ /// One-shot: decrypts `LEN` bytes in place from the given init data. `LEN % BLOCK_LEN == 0` is
+ /// checked at compile time exactly as for [`BlockCipherEncryptor::encrypt`].
+ fn decrypt(
+ key: &KeyMaterial,
+ init_data: &[u8; INIT_DATA_LEN],
+ data: &mut [u8; LEN],
+ ) -> Result<(), SymmetricCipherError> {
+ Self::do_decrypt_init(key, init_data)?.do_decrypt(data)
}
}
@@ -161,11 +166,33 @@ pub trait BlockPermutation:
/// In order for these APIs to be usable securely in all contexts, the init data will be generated
/// securely by the block cipher implementation and returned along with the ciphertext, and there is no API for the
/// user to provide the init data. If you require this functionality, see the documentation for the underlying implementation.
+///
+/// # Everything is in place
+///
+/// Every data method here transforms its buffer in place: the plaintext goes in, the ciphertext
+/// comes out in the same bytes. A block cipher mode never changes the length of its data, so a
+/// separate output buffer would only ever be a copy, and a copy of plaintext is one more thing to
+/// scrub. Callers that need to keep the plaintext copy it first.
+///
+/// # Lengths are checked at compile time
+///
+/// Every buffer is a `[u8; LEN]`, and `LEN % BLOCK_LEN == 0` is checked by an inline `const`
+/// assertion when the method is instantiated: a misaligned length is a compile error at the call
+/// site, not a runtime `Err`, which is why there is no length variant of [`SymmetricCipherError`]
+/// here. Data whose length is only known at run time is fed in block by block, or through the
+/// padding layer.
+///
+/// # Why the data methods still return `Result`
+///
+/// Nothing about the buffer can go wrong, and a constructed value is always ready to use, so a
+/// mode like CBC never returns `Err` from them. The `Result` is for modes with a per-initialization
+/// data limit -- a counter-based mode must refuse to encrypt past the point where its counter would
+/// repeat -- which a streaming API cannot check any earlier than the call that would cross it.
pub trait BlockCipherEncryptor<
const KEY_LEN: usize,
const INIT_DATA_LEN: usize,
const BLOCK_LEN: usize,
->: BlockCipher + Sized
+>: Algorithm + Sized
{
/// Begins a streaming encryption flow, returning the generated init data (e.g. IV).
/// Sources randomness from the library's default OS-backed RNG.
@@ -177,234 +204,128 @@ pub trait BlockCipherEncryptor<
key: &KeyMaterial,
rng: &mut dyn RNG,
) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError>;
- /// Encrypts `N` consecutive blocks of plaintext. A sequence of calls is equivalent to one call over
- /// the concatenation.
+ /// The implementor hook: encrypts `N` consecutive whole blocks in place. A sequence of calls
+ /// is equivalent to one call over the concatenation.
+ ///
+ /// This is the only method an implementor writes besides the two `_init` constructors; the
+ /// block shape is what guarantees it never sees a partial block. Callers should normally use
+ /// the flat [`BlockCipherEncryptor::do_encrypt`] instead.
fn do_encrypt_blocks(
&mut self,
- plaintext: &[[u8; BLOCK_LEN]; N],
- ) -> Result<[[u8; BLOCK_LEN]; N], SymmetricCipherError>;
- /// Encrypts `N` consecutive blocks of plaintext into the provided buffer. Returns `N * BLOCK_LEN`.
- fn do_encrypt_blocks_out(
- &mut self,
- plaintext: &[[u8; BLOCK_LEN]; N],
- ciphertext: &mut [[u8; BLOCK_LEN]; N],
- ) -> Result;
+ blocks: &mut [[u8; BLOCK_LEN]; N],
+ ) -> Result<(), SymmetricCipherError>;
- /// One-shot: encrypts `N` blocks under a fresh init. Returns the generated init data and the ciphertext.
- fn encrypt_blocks(
- key: &KeyMaterial,
- plaintext: &[[u8; BLOCK_LEN]; N],
- ) -> Result<([u8; INIT_DATA_LEN], [[u8; BLOCK_LEN]; N]), SymmetricCipherError> {
- let (mut enc, init_data) = Self::do_encrypt_init(key)?;
- Ok((init_data, enc.do_encrypt_blocks(plaintext)?))
- }
- /// As [`BlockCipherEncryptor::encrypt_blocks`], but sources randomness from the provided RNG.
- fn encrypt_blocks_rng(
- key: &KeyMaterial,
- rng: &mut dyn RNG,
- plaintext: &[[u8; BLOCK_LEN]; N],
- ) -> Result<([u8; INIT_DATA_LEN], [[u8; BLOCK_LEN]; N]), SymmetricCipherError> {
- let (mut enc, init_data) = Self::do_encrypt_init_rng(key, rng)?;
- Ok((init_data, enc.do_encrypt_blocks(plaintext)?))
+ /// Streaming: encrypts `LEN` bytes, a whole number of blocks, in place. A sequence of calls
+ /// is equivalent to one call over the concatenation.
+ ///
+ /// `LEN % BLOCK_LEN == 0` is checked **at compile time**; see the trait docs.
+ ///
+ /// Blocks are fed to [`BlockCipherEncryptor::do_encrypt_blocks`] in pairs first, so a mode
+ /// that overrides its two-block path gets to use it, then the at-most-one block left over. This
+ /// is equivalent to a single `do_encrypt_blocks::<{LEN / BLOCK_LEN}>` call, which cannot be
+ /// written without `generic_const_exprs`.
+ fn do_encrypt(
+ &mut self,
+ data: &mut [u8; LEN],
+ ) -> Result<(), SymmetricCipherError> {
+ const {
+ assert!(
+ LEN.is_multiple_of(BLOCK_LEN),
+ "length must be a whole number of BLOCK_LEN-byte blocks"
+ )
+ };
+ // The remainders are provably empty (asserted above) and ignored.
+ let (blocks, _) = data.as_chunks_mut::();
+ let (pairs, tail) = blocks.as_chunks_mut::<2>();
+ for pair in pairs.iter_mut() {
+ self.do_encrypt_blocks(pair)?;
+ }
+ for block in tail.iter_mut() {
+ self.do_encrypt_blocks(core::array::from_mut(block))?;
+ }
+ Ok(())
}
- /// One-shot: encrypts `N` blocks under a fresh init into the provided buffer.
- /// Returns the generated init data and `N * BLOCK_LEN`.
- fn encrypt_blocks_out(
+
+ /// One-shot: encrypts `LEN` bytes in place under a fresh init, and returns the generated init
+ /// data. `LEN % BLOCK_LEN == 0` is checked **at compile time**; see the trait docs.
+ fn encrypt(
key: &KeyMaterial,
- plaintext: &[[u8; BLOCK_LEN]; N],
- ciphertext: &mut [[u8; BLOCK_LEN]; N],
- ) -> Result<([u8; INIT_DATA_LEN], usize), SymmetricCipherError> {
+ data: &mut [u8; LEN],
+ ) -> Result<[u8; INIT_DATA_LEN], SymmetricCipherError> {
let (mut enc, init_data) = Self::do_encrypt_init(key)?;
- Ok((init_data, enc.do_encrypt_blocks_out(plaintext, ciphertext)?))
+ enc.do_encrypt(data)?;
+ Ok(init_data)
}
- /// As [`BlockCipherEncryptor::encrypt_blocks_out`], but sources randomness from the provided RNG.
- fn encrypt_blocks_out_rng(
+ /// As [`BlockCipherEncryptor::encrypt`], but sources randomness from the provided RNG.
+ fn encrypt_rng(
key: &KeyMaterial,
rng: &mut dyn RNG,
- plaintext: &[[u8; BLOCK_LEN]; N],
- ciphertext: &mut [[u8; BLOCK_LEN]; N],
- ) -> Result<([u8; INIT_DATA_LEN], usize), SymmetricCipherError> {
+ data: &mut [u8; LEN],
+ ) -> Result<[u8; INIT_DATA_LEN], SymmetricCipherError> {
let (mut enc, init_data) = Self::do_encrypt_init_rng(key, rng)?;
- Ok((init_data, enc.do_encrypt_blocks_out(plaintext, ciphertext)?))
+ enc.do_encrypt(data)?;
+ Ok(init_data)
}
}
-/// The decryption half of a block cipher's streaming API; see [`BlockCipherEncryptor`].
-pub trait BlockCipherDecryptor<
- const KEY_LEN: usize,
- const INIT_DATA_LEN: usize,
- const BLOCK_LEN: usize,
->: BlockCipher + Sized
+/// A keyed block permutation: the `CIPH_K` / `CIPH^-1_K` of NIST SP 800-38A Sec 5.1.
+///
+/// This is the raw primitive a mode of operation is built on, not something to encrypt data with.
+/// It transforms exactly one block, so applying it directly to data is ECB (Sec 6.1), which is not
+/// confidential -- the trait is named for the mode it *is* when used that way, as a reminder.
+/// [`BlockCipherEncryptor`] and [`BlockCipherDecryptor`] are the *mode* traits --
+/// they carry initialization data and chaining state; this one carries only a key schedule.
+///
+/// Implementors are expected to hold that key schedule in a zeroize-on-drop wrapper
+/// (`bouncycastle_utils::secret::Secret`), so it is scrubbed when the value is dropped.
+///
+/// # Why the block methods are infallible
+///
+/// Every length here is fixed by a type, and a constructed value is always ready to use, so there
+/// is nothing a caller can get wrong once [`ElectronicCodeBook::new`] has returned. Only `new` can
+/// fail, and only because of the key.
+pub trait ElectronicCodeBook:
+ Algorithm + Sized
{
- /// Begins a streaming decryption flow from the init data returned by [`BlockCipherEncryptor::do_encrypt_init`].
- fn do_decrypt_init(
- key: &KeyMaterial,
- init_data: &[u8; INIT_DATA_LEN],
- ) -> Result;
- /// Decrypts `N` consecutive blocks of ciphertext. A sequence of calls is equivalent to one call over
- /// the concatenation.
- fn do_decrypt_blocks(
- &mut self,
- ciphertext: &[[u8; BLOCK_LEN]; N],
- ) -> Result<[[u8; BLOCK_LEN]; N], SymmetricCipherError>;
- /// Decrypts `N` consecutive blocks of ciphertext into the provided buffer. Returns `N * BLOCK_LEN`.
- fn do_decrypt_blocks_out(
- &mut self,
- ciphertext: &[[u8; BLOCK_LEN]; N],
- plaintext: &mut [[u8; BLOCK_LEN]; N],
- ) -> Result;
+ /// Expands the key.
+ ///
+ /// # Errors
+ /// Rejects a key whose [`KeyType`] is not [`KeyType::SymmetricCipherKey`], and one whose
+ /// security strength is below [`Algorithm::MAX_SECURITY_STRENGTH`], both as a
+ /// [`SymmetricCipherError::KeyMaterialError`].
+ fn new(key: &KeyMaterial) -> Result;
- /// One-shot: decrypts `N` blocks from the given init data.
- fn decrypt_blocks(
- key: &KeyMaterial,
- init_data: &[u8; INIT_DATA_LEN],
- ciphertext: &[[u8; BLOCK_LEN]; N],
- ) -> Result<[[u8; BLOCK_LEN]; N], SymmetricCipherError> {
- Self::do_decrypt_init(key, init_data)?.do_decrypt_blocks(ciphertext)
- }
- /// One-shot: decrypts `N` blocks from the given init data into the provided buffer. Returns `N * BLOCK_LEN`.
- fn decrypt_blocks_out(
- key: &KeyMaterial,
- init_data: &[u8; INIT_DATA_LEN],
- ciphertext: &[[u8; BLOCK_LEN]; N],
- plaintext: &mut [[u8; BLOCK_LEN]; N],
- ) -> Result {
- Self::do_decrypt_init(key, init_data)?.do_decrypt_blocks_out(ciphertext, plaintext)
+ /// The forward cipher function, in place.
+ fn encrypt_block(&self, block: &mut [u8; BLOCK_LEN]);
+
+ /// The inverse cipher function, in place.
+ fn decrypt_block(&self, block: &mut [u8; BLOCK_LEN]);
+
+ /// The forward cipher function on two *independent* blocks, in place.
+ ///
+ /// 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`.
+ ///
+ /// Overrides must be indistinguishable from the default, including the order of the two
+ /// results. `TestFrameworkElectronicCodeBook` pins that.
+ ///
+ /// 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]) {
+ let [a, b] = blocks;
+ self.encrypt_block(a);
+ self.encrypt_block(b);
}
-}
-/// A block padding scheme, used to extend arbitrary-length data to a whole number of blocks so that it
-/// can be processed by a [`BlockCipherEncryptor`]. Implementations are pure functions of the block
-/// contents: no key, no state.
-///
-/// Only the final, partial block of a message is ever padded; the padding layer sitting between the
-/// caller and the block cipher is responsible for routing whole blocks straight through.
-pub trait Padding {
- /// Pads `block` in place: bytes `0..data_len` are data and are left untouched, bytes
- /// `data_len..BLOCK_LEN` are overwritten with padding. `data_len` must be less than `BLOCK_LEN`
- /// (a full block of data requires a whole additional block of padding, which the caller supplies
- /// as `data_len = 0`).
- fn pad(block: &mut [u8; BLOCK_LEN], data_len: usize) -> Result<(), PaddingError>;
- /// Returns the number of data bytes in a padded `block`, or [`PaddingError::InvalidPadding`].
- /// Implementations must run in constant time with respect to the block contents, so that a
- /// decryptor built on them does not leak a padding oracle.
- fn unpad(block: &[u8; BLOCK_LEN]) -> Result;
-}
-
-/// The basic functions of an Authenticated Encryption with Addititional Data cipher.
-pub trait AEADCipher:
- SymmetricCipher + Sized
-{
- #[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)
- /// that is not encrypted but is protected by the authentication tag; ie it can be sent along with the ciphertext
- /// and any tampering with it will result in the decryption operation failing the tag check.
- /// This function returns the ciphertext as a `Vec`, and therefore is only available when compiling with std.
- /// Returns a tuple containing a generated nonce, the ciphertext and the tag.
- fn aead_encrypt(
- key: &KeyMaterial,
- aad: &[u8],
- plaintext: &[u8],
- ) -> Result<([u8; NONCE_LEN], Vec, [u8; TAG_LEN]), SymmetricCipherError>;
- /// 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)
- /// that is not encrypted but is protected by the authentication tag; ie it can be sent along with the ciphertext
- /// and any tampering with it will result in the decryption operation failing the tag check.
- /// Returns a tuple containing the randomly-generated nonce, number of bytes written to the ciphertext buffer, and the tag.
- /// If you need a deterministic mode where you feed in the nonce, use the streaming API of [`BlockCipherEncryptor`]
- /// or [`StreamCipher`] as appropriate and feed the nonce into the IV field.
- fn aead_encrypt_out(
- key: &KeyMaterial,
- aad: &[u8],
- plaintext: &[u8],
- ciphertext: &mut [u8],
- ) -> Result<([u8; NONCE_LEN], usize, [u8; TAG_LEN]), SymmetricCipherError>;
- /// All AEAD ciphers will also be either a block cipher ([`BlockCipherEncryptor`] / [`BlockCipherDecryptor`]) or a [`StreamCipher`], and so will already
- /// have a streaming API.
- /// This allows you to finish either style of streaming API flow with AEAD specific do_final()
- /// that computes and returns the authentication tag.
- fn do_aead_encrypt_final(self) -> Result<[u8; TAG_LEN], 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.
- fn aead_decrypt(
- key: &KeyMaterial,
- nonce: &[u8; NONCE_LEN],
- aad: &[u8],
- ciphertext: &[u8],
- tag: &[u8; TAG_LEN],
- ) -> 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 the number of bytes written to the plaintext buffer.
- fn aead_decrypt_out(
- key: &KeyMaterial,
- nonce: &[u8; NONCE_LEN],
- aad: &[u8],
- ciphertext: &[u8],
- tag: &[u8; TAG_LEN],
- plaintext: &mut [u8],
- ) -> Result;
- /// All AEAD ciphers will also be either a block cipher ([`BlockCipherEncryptor`] / [`BlockCipherDecryptor`]) or a [`StreamCipher`], and so will already
- /// have a streaming API.
- /// This allows you to finish either style of streaming API flow with AEAD specific do_final()
- /// that computes and returns the authentication tag.
- fn do_aead_decrypt_final(self, tag: &[u8; TAG_LEN]) -> Result<(), SymmetricCipherError>;
-}
-
-/// The basic functions of a stream cipher, which differ from those of a block cipher only in that
-/// a stream cipher is assumed to have no underlying block size tied to the implementation, and so the caller gets to specify
-/// the block size for the streaming APIs.
-pub trait StreamCipher:
- SymmetricCipher + Sized
-{
- /// Constructor that begins a flow of the streaming API for encrypting one block at a time.
- /// Allows for the implementation to return init data such as an IV which is generated prior to encrypting the first block.
- fn do_stream_encrypt_init(
- key: &KeyMaterial,
- ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError>;
- /// Encrypts a single block of plaintext.
- fn do_stream_encrypt_block(
- &mut self,
- plaintext: &[u8; BLOCK_LEN],
- ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>;
- /// Encrypts a single block of plaintext and writes the ciphertext to the provided buffer.
- fn do_stream_encrypt_block_out(
- &mut self,
- plaintext: &[u8; BLOCK_LEN],
- ciphertext: &mut [u8; BLOCK_LEN],
- ) -> Result;
- /// Encrypts the final block of plaintext.
- fn do_stream_encrypt_final(
- &mut self,
- plaintext: &[u8; BLOCK_LEN],
- ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>;
- /// Encrypts the final block of plaintext and writes the ciphertext to the provided buffer.
- fn do_stream_encrypt_final_out(
- &mut self,
- plaintext: &[u8; BLOCK_LEN],
- ciphertext: &mut [u8; BLOCK_LEN],
- ) -> Result;
- /// Constructor that begins a flow of the streaming API for decryption one block at a time.
- fn do_stream_decrypt_init(
- key: &KeyMaterial,
- init_data: &[u8; INIT_DATA_LEN],
- ) -> Result;
- /// Decrypts a single block of ciphertext.
- fn do_stream_decrypt_block(
- &mut self,
- ciphertext: &[u8; BLOCK_LEN],
- ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>;
- /// Decrypts a single block of ciphertext and writes the plaintext to the provided buffer.
- fn do_stream_decrypt_block_out(
- &mut self,
- ciphertext: &[u8; BLOCK_LEN],
- plaintext: &mut [u8; BLOCK_LEN],
- ) -> Result;
+ /// The inverse cipher function on two *independent* blocks, in place.
+ /// See [`ElectronicCodeBook::encrypt_blocks2`].
+ fn decrypt_blocks2(&self, blocks: &mut [[u8; BLOCK_LEN]; 2]) {
+ let [a, b] = blocks;
+ self.decrypt_block(a);
+ self.decrypt_block(b);
+ }
}
/// A hash function is a cryptographic primitive that takes an input of any length and produces a fixed-size output.
@@ -584,8 +505,8 @@ pub trait KDF: Default {
/// A Key Encapsulation Mechanism (KEM) is defined as a set of three operations:
/// key generation, encapsulation, and decapsulation.
///
-/// This trait represents the encapsulation operation performed by the holder of the public key.
-/// Decapsulation operations are performed by the corresponding [`KEMDecapsulator`] trait, and key
+/// This trait represents the decapsulation operation performed by the holder of the private key.
+/// Encapsulation operations are performed by the corresponding [`KEMEncapsulator`] trait, and key
/// generation is provided as an inherent associated function directly on the algorithm struct.
/// There are several reasons for this split: first is architectural; some complex algorithms may
/// benefit from having the encapsulation and decapsulation implementations split into separate modules.
@@ -593,35 +514,27 @@ pub trait KDF: Default {
/// can no longer be created, but existing ciphertexts can still be decapsulated. Splitting the traits
/// makes this policy easier to enforce.
///
-/// The arrays used to encode public keys, ciphertexts, and shared secrets are statically-sized
+/// The arrays used to encode private keys, ciphertexts, and shared secrets are statically-sized
/// because this allows us to safely remove runtime checks for array lengths, which overall reduces
/// the fallibility of the library. This design choice could make this trait complicated to apply
/// to a KEM algorithm that does not have fixed sizes for the encodings of these objects.
-pub trait KEMEncapsulator<
- PK: KEMPublicKey,
- const PK_LEN: usize,
+pub trait KEMDecapsulator<
+ SK: KEMPrivateKey,
+ const SK_LEN: usize,
const CT_LEN: usize,
const SS_LEN: usize,
>: Sized
{
- /// Performs an encapsulation against the given public key.
- /// Sources randomness from the library's default OS-backed RNG.
- /// Returns the ciphertext and derived shared secret.
- fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>;
- /// Performs an encapsulation against the given public key.
- /// Sources randomness from the provided RNG.
- /// Returns the ciphertext and derived shared secret.
- fn encaps_rng(
- pk: &PK,
- rng: &mut dyn RNG,
- ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>;
+ /// Performs a decapsulation of the given ciphertext.
+ /// Returns the derived shared secret.
+ fn decaps(sk: &SK, ct: &[u8]) -> Result, KEMError>;
}
/// A Key Encapsulation Mechanism (KEM) is defined as a set of three operations:
/// key generation, encapsulation, and decapsulation.
///
-/// This trait represents the decapsulation operation performed by the holder of the private key.
-/// Encapsulation operations are performed by the corresponding [`KEMEncapsulator`] trait, and key
+/// This trait represents the encapsulation operation performed by the holder of the public key.
+/// Decapsulation operations are performed by the corresponding [`KEMDecapsulator`] trait, and key
/// generation is provided as an inherent associated function directly on the algorithm struct.
/// There are several reasons for this split: first is architectural; some complex algorithms may
/// benefit from having the encapsulation and decapsulation implementations split into separate modules.
@@ -629,20 +542,39 @@ pub trait KEMEncapsulator<
/// can no longer be created, but existing ciphertexts can still be decapsulated. Splitting the traits
/// makes this policy easier to enforce.
///
-/// The arrays used to encode private keys, ciphertexts, and shared secrets are statically-sized
+/// The arrays used to encode public keys, ciphertexts, and shared secrets are statically-sized
/// because this allows us to safely remove runtime checks for array lengths, which overall reduces
/// the fallibility of the library. This design choice could make this trait complicated to apply
/// to a KEM algorithm that does not have fixed sizes for the encodings of these objects.
-pub trait KEMDecapsulator<
- SK: KEMPrivateKey,
- const SK_LEN: usize,
+pub trait KEMEncapsulator<
+ PK: KEMPublicKey,
+ const PK_LEN: usize,
const CT_LEN: usize,
const SS_LEN: usize,
>: Sized
{
- /// Performs a decapsulation of the given ciphertext.
- /// Returns the derived shared secret.
- fn decaps(sk: &SK, ct: &[u8]) -> Result, KEMError>;
+ /// Performs an encapsulation against the given public key.
+ /// Sources randomness from the library's default OS-backed RNG.
+ /// Returns the ciphertext and derived shared secret.
+ fn encaps(pk: &PK) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>;
+ /// Performs an encapsulation against the given public key.
+ /// Sources randomness from the provided RNG.
+ /// Returns the ciphertext and derived shared secret.
+ fn encaps_rng(
+ pk: &PK,
+ rng: &mut dyn RNG,
+ ) -> Result<(KeyMaterial, [u8; CT_LEN]), KEMError>;
+}
+
+/// A private key for a KEM algorithm, often denoted "sk" (for "secret key").
+pub trait KEMPrivateKey: PartialEq + Eq + Clone + Sized {
+ /// Write it out to bytes in its standard encoding.
+ fn encode(&self) -> [u8; SK_LEN];
+ /// Write it out to bytes in its standard encoding.
+ /// The entire output buffer is zeroized before the encoding is written.
+ fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize;
+ /// Read it in from bytes in its standard encoding.
+ fn from_bytes(bytes: &[u8]) -> Result;
}
// todo: could the public and private key types impl Into> and From>
@@ -661,17 +593,6 @@ pub trait KEMPublicKey:
fn from_bytes(bytes: &[u8]) -> Result;
}
-/// A private key for a KEM algorithm, often denoted "sk" (for "secret key").
-pub trait KEMPrivateKey: PartialEq + Eq + Clone + Sized {
- /// Write it out to bytes in its standard encoding.
- fn encode(&self) -> [u8; SK_LEN];
- /// Write it out to bytes in its standard encoding.
- /// The entire output buffer is zeroized before the encoding is written.
- fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize;
- /// Read it in from bytes in its standard encoding.
- fn from_bytes(bytes: &[u8]) -> Result;
-}
-
/// A Message Authentication Code algorithm is a keyed hash function that behaves somewhat like a symmetric signature function.
/// A MAC algorithm takes in a key and some data, and produces a MAC (message authentication code) that
/// can be used to verify the integrity of data.
@@ -787,6 +708,134 @@ pub trait MAC: Sized {
fn max_security_strength(&self) -> SecurityStrength;
}
+/// A block padding scheme, used to extend arbitrary-length data to a whole number of blocks so that it
+/// can be processed by a [`BlockCipherEncryptor`]. Implementations are pure functions of the block
+/// contents: no key, no state.
+///
+/// Only the final, partial block of a message is ever padded; the padding layer sitting between the
+/// caller and the block cipher is responsible for routing whole blocks straight through.
+pub trait Padding {
+ /// Pads `block` in place: bytes `0..data_len` are data and are left untouched, bytes
+ /// `data_len..BLOCK_LEN` are overwritten with padding. `data_len` must be less than `BLOCK_LEN`
+ /// (a full block of data requires a whole additional block of padding, which the caller supplies
+ /// as `data_len = 0`).
+ fn pad(block: &mut [u8; BLOCK_LEN], data_len: usize) -> Result<(), PaddingError>;
+ /// Returns the number of data bytes in a padded `block`, or [`PaddingError::InvalidPadding`].
+ /// Implementations must run in constant time with respect to the block contents, so that a
+ /// decryptor built on them does not leak a padding oracle.
+ fn unpad(block: &[u8; BLOCK_LEN]) -> Result;
+}
+
+/// Pre-Hashed Signature Verifier is an extension to [`SignatureVerifier`] that adds functionality specific to signature
+/// primatives that can operate on a pre-hashed message instead of the full message.
+pub trait PHSignatureVerifier<
+ PK: SignaturePublicKey,
+ const PK_LEN: usize,
+ const SIG_LEN: usize,
+ const PH_LEN: usize,
+>: SignatureVerifier
+{
+ /// On success, returns Ok(())
+ /// On failure, returns Err([`SignatureError::SignatureVerificationFailed`]); may also return other types of [`SignatureError`] as appropriate (such as for invalid-length inputs).
+ fn verify_ph(
+ pk: &PK,
+ ph: &[u8; PH_LEN],
+ ctx: Option<&[u8]>,
+ sig: &[u8],
+ ) -> Result<(), SignatureError>;
+}
+
+/// Pre-Hashed Signer is an extension to [`Signer`] that adds functionality specific to signature
+/// primatives that can operate on a pre-hashed message instead of the full message.
+pub trait PHSigner<
+ PK: SignaturePublicKey,
+ SK: SignaturePrivateKey,
+ const PK_LEN: usize,
+ const SK_LEN: usize,
+ const SIG_LEN: usize,
+ const PH_LEN: usize,
+>: Signer
+{
+ /// Produce a signature for the provided pre-hashed message and context.
+ ///
+ /// `ctx` accepts a zero-length byte array.
+ ///
+ /// A note about the `ctx` context parameter:
+ /// This is a newer addition to cryptographic signature primitives. It allows for binding the
+ /// signature to some external property of the application so that a signature will fail to validate
+ /// if removed from its intended context.
+ /// This is particularly useful at preventing content confusion attacks between data formats that
+ /// have very similar data structures, for example S/MIME emails, signed PDFs, and signed executables
+ /// that all use the Cryptographic Message Syntax (CMS) data format, or multiple data objects that
+ /// all use the JWS data format.
+ /// To be properly effective, the ctx value must not be under the control of the attacker, which generally
+ /// means that it needs to be a value that is never transmitted over the wire, but rather is something
+ /// known to the application by context.
+ /// For example, "email" vs "pdf" would be a good choice since the application should know what it is
+ /// attempting to sign or verify.
+ /// The `ctx` param can also be used to bind the signed content to a transaction ID or a username,
+ /// but care should be taken to ensure that an attacker attempting a
+ /// content confusion attack not also cause the signed / verifier to use an incorrect transaction ID or username.
+ ///
+ /// Not all signature primitives will support a context value, so you may need to consult the
+ /// documentation for the underlying primitive for how it handles a ctx in that case, for example, it
+ /// might throw an error, ignore the provided ctx value, or append the ctx to the msg in a non-standard way.
+ fn sign_ph(
+ sk: &SK,
+ ph: &[u8; PH_LEN],
+ ctx: Option<&[u8]>,
+ ) -> Result<[u8; SIG_LEN], SignatureError>;
+ /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
+ /// The entire output buffer is zeroized before the signature is written.
+ fn sign_ph_out(
+ sk: &SK,
+ ph: &[u8; PH_LEN],
+ ctx: Option<&[u8]>,
+ output: &mut [u8; SIG_LEN],
+ ) -> Result;
+}
+
+/// An interface for random number generation.
+/// This interface is meant to be simpler and more ergonomic than the interfaces provided by the
+/// `rng` crate, but that one should
+/// be used by applications that intend to submit to FIPS certification as it more closely aligns with the
+/// requirements of SP 800-90A.
+/// Note: this interface produces bytes. If you want a [`KeyMaterialTrait`], then use [`KeyMaterial::from_rng`].
+///
+/// Implementors are expected to also implement [`Default`] (default-construction should produce a
+/// securely OS-seeded instance), but this is intentionally *not* a supertrait bound: requiring
+/// `Default` would make `RNG` not dyn-compatible, and `&mut dyn RNG` is needed so RNG instances
+/// can be handed around as trait objects.
+pub trait RNG {
+ // TODO: add back once we figure out streaming interaction with entropy sources.
+ // fn add_seed_bytes(&mut self, additional_seed: &[u8]) -> Result<(), RNGError>;
+
+ /// Provide additional key material to be mixed in to the existing RNG instance.
+ /// The exact behaviour will be implementation-specific, but this is intended for injecting
+ /// additional entropy, not as the primary method of seeding the RNG.
+ fn add_seed_keymaterial(
+ &mut self,
+ additional_seed: &dyn KeyMaterialTrait,
+ ) -> Result<(), RNGError>;
+ /// Returns the next random 32-bit integer.
+ fn next_int(&mut self) -> Result;
+
+ /// Returns the number of requested bytes.
+ fn next_bytes(&mut self, len: usize) -> Result, RNGError>;
+
+ /// Returns the number of bytes written.
+ /// The entire output buffer is zeroized before the random bytes are written.
+ fn next_bytes_out(&mut self, out: &mut [u8]) -> Result;
+
+ /// Fill the provided [`KeyMaterial`] with random bytes.
+ fn fill_keymaterial_out(&mut self, out: &mut dyn KeyMaterialTrait) -> Result;
+
+ /// Returns the Security Strength of this RNG.
+ // todo: we should do a refactor to make [Algorithm] be a `security_strength()` function instead of constant,
+ // then have `RNG: Algorithm`, then delete this function.
+ fn security_strength(&self) -> SecurityStrength;
+}
+
/// A general indicator used across the library for marking the security level of a cryptographic primitive,
/// and for tracking the security level of the algorithms that interacted with a given piece of data.
/// For example, if a KDF at the 128-bit security strength is used to produce a 512-bit key, that key
@@ -854,191 +903,29 @@ impl SecurityStrength {
/// For example, 15 bytes (120-bits) is rounded down to 112-bit.
pub fn from_bytes(bytes: usize) -> Self {
Self::from_bits(bytes * 8)
- }
-
- /// Outputs the security strength in bits for easier computation.
- pub fn as_int(&self) -> u32 {
- match self {
- Self::None => 0,
- Self::_112bit => 112,
- Self::_128bit => 128,
- Self::_192bit => 192,
- Self::_256bit => 256,
- }
- }
-}
-
-/// An interface for random number generation.
-/// This interface is meant to be simpler and more ergonomic than the interfaces provided by the
-/// `rng` crate, but that one should
-/// be used by applications that intend to submit to FIPS certification as it more closely aligns with the
-/// requirements of SP 800-90A.
-/// Note: this interface produces bytes. If you want a [`KeyMaterialTrait`], then use [`KeyMaterial::from_rng`].
-///
-/// Implementors are expected to also implement [`Default`] (default-construction should produce a
-/// securely OS-seeded instance), but this is intentionally *not* a supertrait bound: requiring
-/// `Default` would make `RNG` not dyn-compatible, and `&mut dyn RNG` is needed so RNG instances
-/// can be handed around as trait objects.
-pub trait RNG {
- // TODO: add back once we figure out streaming interaction with entropy sources.
- // fn add_seed_bytes(&mut self, additional_seed: &[u8]) -> Result<(), RNGError>;
-
- /// Provide additional key material to be mixed in to the existing RNG instance.
- /// The exact behaviour will be implementation-specific, but this is intended for injecting
- /// additional entropy, not as the primary method of seeding the RNG.
- fn add_seed_keymaterial(
- &mut self,
- additional_seed: &dyn KeyMaterialTrait,
- ) -> Result<(), RNGError>;
- /// Returns the next random 32-bit integer.
- fn next_int(&mut self) -> Result;
-
- /// Returns the number of requested bytes.
- fn next_bytes(&mut self, len: usize) -> Result, RNGError>;
-
- /// Returns the number of bytes written.
- /// The entire output buffer is zeroized before the random bytes are written.
- fn next_bytes_out(&mut self, out: &mut [u8]) -> Result;
-
- /// Fill the provided [`KeyMaterial`] with random bytes.
- fn fill_keymaterial_out(&mut self, out: &mut dyn KeyMaterialTrait) -> Result;
-
- /// Returns the Security Strength of this RNG.
- // todo: we should do a refactor to make [Algorithm] be a `security_strength()` function instead of constant,
- // then have `RNG: Algorithm`, then delete this function.
- fn security_strength(&self) -> SecurityStrength;
-}
-
-/// Allows a stateful object to suspend its operation by serializing its state into a byte array
-///so that it can be resumed later, potentially from a different host.
-///
-/// This is intended for situations where an object is being used through its streaming API
-/// (do_update, do_final) and the operation wants to be paused to a cache, for example while waiting
-/// for network IO.
-///
-/// This is not intended as a mechanism to clone the state of an object since in most cases `.clone()`
-/// will be more straightforward.
-///
-/// The serialized state MAY contain short-term sensitive values such as nonces or IVs,
-/// but it MUST NOT include a serialized private key.
-/// Keyed algorithms MUST instead impl
-/// [`SuspendableKeyed`] which requires the key to be supplied independently at the time of deserialization.
-pub trait Suspendable: Sized {
- /// Suspend operation by serializing out the state of the object.
- ///
- /// Note that this consumes `self` to prevent accidentally continuing to use the object after serialization.
- /// If you want to do this intentionally, then you will need to clone the object before serializing it.
- ///
- /// The serialized state MUST include a prefix indicating the version of the library that serialized it.
- fn suspend(self) -> [u8; SERIALIZED_STATE_LEN];
-
- /// Resume operation from a serialized state.
- ///
- /// Deserializers SHOULD check the version and reject serialized states from incompatible versions
- /// (including rejecting serializations from a future version of the library).
- /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its
- /// deserializer should reject serialized states from that version or older.
- fn from_suspended(state: [u8; SERIALIZED_STATE_LEN]) -> Result;
-}
-
-/// Similar to [`Suspendable`] in that it allows a stateful object to suspend its operation by
-/// serializing its state into a byte array so that it can be resumed later, potentially from a different host.
-///
-/// The difference is that this trait is for keyed algorithms -- MACs, symmetric ciphers, signatures, etc --
-/// which require a private key in order to resume successfully.
-/// For security reasons, the private key is not included in the serialized state
-/// and must be provided separately as part of the deserialization process.
-pub trait SuspendableKeyed: Sized {
- /// The type of key that must be re-supplied to resume this object.
- type Key: ?Sized;
-
- /// Suspend operation by serializing out the state of the object.
- ///
- /// Note that this consumes `self` to prevent accidentally continuing to use the object after serialization.
- /// If you want to do this intentionally, then you will need to clone the object before serializing it.
- ///
- /// The serialized state MUST include a prefix indicating the version of the library that serialized it.
- fn suspend(self) -> [u8; SERIALIZED_STATE_LEN];
-
- /// Resume operation from a serialized state and the key.
- ///
- /// Deserializers SHOULD check the version and reject serialized states from incompatible versions
- /// (including rejecting serializations from a future version of the library).
- /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its
- /// deserializer should reject serialized states from that version or older.
- fn from_suspended(
- state: [u8; SERIALIZED_STATE_LEN],
- key: &Self::Key,
- ) -> Result;
-}
-
-/// Pre-Hashed Signer is an extension to [`Signer`] that adds functionality specific to signature
-/// primatives that can operate on a pre-hashed message instead of the full message.
-pub trait PHSigner<
- PK: SignaturePublicKey,
- SK: SignaturePrivateKey,
- const PK_LEN: usize,
- const SK_LEN: usize,
- const SIG_LEN: usize,
- const PH_LEN: usize,
->: Signer
-{
- /// Produce a signature for the provided pre-hashed message and context.
- ///
- /// `ctx` accepts a zero-length byte array.
- ///
- /// A note about the `ctx` context parameter:
- /// This is a newer addition to cryptographic signature primitives. It allows for binding the
- /// signature to some external property of the application so that a signature will fail to validate
- /// if removed from its intended context.
- /// This is particularly useful at preventing content confusion attacks between data formats that
- /// have very similar data structures, for example S/MIME emails, signed PDFs, and signed executables
- /// that all use the Cryptographic Message Syntax (CMS) data format, or multiple data objects that
- /// all use the JWS data format.
- /// To be properly effective, the ctx value must not be under the control of the attacker, which generally
- /// means that it needs to be a value that is never transmitted over the wire, but rather is something
- /// known to the application by context.
- /// For example, "email" vs "pdf" would be a good choice since the application should know what it is
- /// attempting to sign or verify.
- /// The `ctx` param can also be used to bind the signed content to a transaction ID or a username,
- /// but care should be taken to ensure that an attacker attempting a
- /// content confusion attack not also cause the signed / verifier to use an incorrect transaction ID or username.
- ///
- /// Not all signature primitives will support a context value, so you may need to consult the
- /// documentation for the underlying primitive for how it handles a ctx in that case, for example, it
- /// might throw an error, ignore the provided ctx value, or append the ctx to the msg in a non-standard way.
- fn sign_ph(
- sk: &SK,
- ph: &[u8; PH_LEN],
- ctx: Option<&[u8]>,
- ) -> Result<[u8; SIG_LEN], SignatureError>;
- /// Returns the number of bytes written to the output buffer. Can be called with an oversized buffer.
- /// The entire output buffer is zeroized before the signature is written.
- fn sign_ph_out(
- sk: &SK,
- ph: &[u8; PH_LEN],
- ctx: Option<&[u8]>,
- output: &mut [u8; SIG_LEN],
- ) -> Result;
-}
-
-/// Pre-Hashed Signature Verifier is an extension to [`SignatureVerifier`] that adds functionality specific to signature
-/// primatives that can operate on a pre-hashed message instead of the full message.
-pub trait PHSignatureVerifier<
- PK: SignaturePublicKey,
- const PK_LEN: usize,
- const SIG_LEN: usize,
- const PH_LEN: usize,
->: SignatureVerifier
-{
- /// On success, returns Ok(())
- /// On failure, returns Err([`SignatureError::SignatureVerificationFailed`]); may also return other types of [`SignatureError`] as appropriate (such as for invalid-length inputs).
- fn verify_ph(
- pk: &PK,
- ph: &[u8; PH_LEN],
- ctx: Option<&[u8]>,
- sig: &[u8],
- ) -> Result<(), SignatureError>;
+ }
+
+ /// Outputs the security strength in bits for easier computation.
+ pub fn as_int(&self) -> u32 {
+ match self {
+ Self::None => 0,
+ Self::_112bit => 112,
+ Self::_128bit => 128,
+ Self::_192bit => 192,
+ Self::_256bit => 256,
+ }
+ }
+}
+
+/// A private key for a signature algorithm, often denoted "sk" (for "secret key").
+pub trait SignaturePrivateKey: PartialEq + Eq + Clone + Sized {
+ /// Write it out to bytes in its standard encoding.
+ fn encode(&self) -> [u8; SK_LEN];
+ /// Write it out to bytes in its standard encoding.
+ /// The entire output buffer is zeroized before the encoding is written.
+ fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize;
+ /// Read it in from bytes in its standard encoding.
+ fn from_bytes(bytes: &[u8]) -> Result;
}
// todo: could the public and private key types impl Into> and From>
@@ -1057,15 +944,42 @@ pub trait SignaturePublicKey:
fn from_bytes(bytes: &[u8]) -> Result;
}
-/// A private key for a signature algorithm, often denoted "sk" (for "secret key").
-pub trait SignaturePrivateKey: PartialEq + Eq + Clone + Sized {
- /// Write it out to bytes in its standard encoding.
- fn encode(&self) -> [u8; SK_LEN];
- /// Write it out to bytes in its standard encoding.
- /// The entire output buffer is zeroized before the encoding is written.
- fn encode_out(&self, out: &mut [u8; SK_LEN]) -> usize;
- /// Read it in from bytes in its standard encoding.
- fn from_bytes(bytes: &[u8]) -> Result;
+/// A digital signature algorithm is defined as a set of three operations:
+/// key generation, signing, and verification.
+///
+/// This trait represents the verification operations performed by the holder of the verification public key.
+/// Keygen and signing operations are performed by the corresponding [`Signer`] trait.
+/// There are several reasons for this split: first is architectural; some complex algorithms may
+/// benefit from having the signature generation and verification implementations split into separate modules.
+/// Second is for compliance: sometimes a policy soft-deprecates an algorithm so that new signatures
+/// can no longer be created, but existing signatures can still be verified. Splitting the traits
+/// makes this policy easier to enforce.
+///
+/// Here we statically-size the arrays used to encode public keys, private keys, and signature values
+/// because this allows us to safely remove runtime checks for array lengths, which overall reduces
+/// the fallibility of the library. This design choice could make this trait complicated to apply
+/// to a signature algorithm that do not have fixed sizes for the encodings of these objects.
+pub trait SignatureVerifier<
+ PK: SignaturePublicKey,
+ const PK_LEN: usize,
+ const SIG_LEN: usize,
+>: Sized
+{
+ /// On success, returns Ok(())
+ /// On failure, returns Err([`SignatureError::SignatureVerificationFailed`]); may also return other types of [`SignatureError`] as appropriate (such as for invalid-length inputs).
+ fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError>;
+
+ /// streaming verification API
+ fn verify_init(pk: &PK, ctx: Option<&[u8]>) -> Result;
+
+ // todo: make this a AsRef<[u8]> ?
+ /// Update the verifier with the next chunk of data.
+ /// This can be called multiple times.
+ fn verify_update(&mut self, msg_chunk: &[u8]);
+
+ /// On success, returns Ok(())
+ /// On failure, returns Err([`SignatureError::SignatureVerificationFailed`]); may also return other types of [`SignatureError`] as appropriate (such as for invalid-length inputs).
+ fn verify_final(self, sig: &[u8]) -> Result<(), SignatureError>;
}
/// A digital signature algorithm is defined as a set of three operations:
@@ -1143,42 +1057,168 @@ pub trait Signer, const SK_LEN: usize, const SIG
fn sign_final_out(self, output: &mut [u8; SIG_LEN]) -> Result;
}
-/// A digital signature algorithm is defined as a set of three operations:
-/// key generation, signing, and verification.
+/// The basic functions of a stream cipher, which differ from those of a block cipher only in that
+/// a stream cipher is assumed to have no underlying block size tied to the implementation, and so the caller gets to specify
+/// the block size for the streaming APIs.
+pub trait StreamCipher:
+ SymmetricCipher + Sized
+{
+ /// Constructor that begins a flow of the streaming API for encrypting one block at a time.
+ /// Allows for the implementation to return init data such as an IV which is generated prior to encrypting the first block.
+ fn do_stream_encrypt_init(
+ key: &KeyMaterial,
+ ) -> Result<(Self, [u8; INIT_DATA_LEN]), SymmetricCipherError>;
+ /// Encrypts a single block of plaintext.
+ fn do_stream_encrypt_block(
+ &mut self,
+ plaintext: &[u8; BLOCK_LEN],
+ ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>;
+ /// Encrypts a single block of plaintext and writes the ciphertext to the provided buffer.
+ fn do_stream_encrypt_block_out(
+ &mut self,
+ plaintext: &[u8; BLOCK_LEN],
+ ciphertext: &mut [u8; BLOCK_LEN],
+ ) -> Result;
+ /// Encrypts the final block of plaintext.
+ fn do_stream_encrypt_final(
+ &mut self,
+ plaintext: &[u8; BLOCK_LEN],
+ ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>;
+ /// Encrypts the final block of plaintext and writes the ciphertext to the provided buffer.
+ fn do_stream_encrypt_final_out(
+ &mut self,
+ plaintext: &[u8; BLOCK_LEN],
+ ciphertext: &mut [u8; BLOCK_LEN],
+ ) -> Result;
+ /// Constructor that begins a flow of the streaming API for decryption one block at a time.
+ fn do_stream_decrypt_init(
+ key: &KeyMaterial,
+ init_data: &[u8; INIT_DATA_LEN],
+ ) -> Result;
+ /// Decrypts a single block of ciphertext.
+ fn do_stream_decrypt_block(
+ &mut self,
+ ciphertext: &[u8; BLOCK_LEN],
+ ) -> Result<[u8; BLOCK_LEN], SymmetricCipherError>;
+ /// Decrypts a single block of ciphertext and writes the plaintext to the provided buffer.
+ fn do_stream_decrypt_block_out(
+ &mut self,
+ ciphertext: &[u8; BLOCK_LEN],
+ plaintext: &mut [u8; BLOCK_LEN],
+ ) -> Result;
+}
+
+/// Allows a stateful object to suspend its operation by serializing its state into a byte array
+///so that it can be resumed later, potentially from a different host.
///
-/// This trait represents the verification operations performed by the holder of the verification public key.
-/// Keygen and signing operations are performed by the corresponding [`Signer`] trait.
-/// There are several reasons for this split: first is architectural; some complex algorithms may
-/// benefit from having the signature generation and verification implementations split into separate modules.
-/// Second is for compliance: sometimes a policy soft-deprecates an algorithm so that new signatures
-/// can no longer be created, but existing signatures can still be verified. Splitting the traits
-/// makes this policy easier to enforce.
+/// This is intended for situations where an object is being used through its streaming API
+/// (do_update, do_final) and the operation wants to be paused to a cache, for example while waiting
+/// for network IO.
///
-/// Here we statically-size the arrays used to encode public keys, private keys, and signature values
-/// because this allows us to safely remove runtime checks for array lengths, which overall reduces
-/// the fallibility of the library. This design choice could make this trait complicated to apply
-/// to a signature algorithm that do not have fixed sizes for the encodings of these objects.
-pub trait SignatureVerifier<
- PK: SignaturePublicKey,
- const PK_LEN: usize,
- const SIG_LEN: usize,
->: Sized
-{
- /// On success, returns Ok(())
- /// On failure, returns Err([`SignatureError::SignatureVerificationFailed`]); may also return other types of [`SignatureError`] as appropriate (such as for invalid-length inputs).
- fn verify(pk: &PK, msg: &[u8], ctx: Option<&[u8]>, sig: &[u8]) -> Result<(), SignatureError>;
+/// This is not intended as a mechanism to clone the state of an object since in most cases `.clone()`
+/// will be more straightforward.
+///
+/// The serialized state MAY contain short-term sensitive values such as nonces or IVs,
+/// but it MUST NOT include a serialized private key.
+/// Keyed algorithms MUST instead impl
+/// [`SuspendableKeyed`] which requires the key to be supplied independently at the time of deserialization.
+pub trait Suspendable: Sized {
+ /// Suspend operation by serializing out the state of the object.
+ ///
+ /// Note that this consumes `self` to prevent accidentally continuing to use the object after serialization.
+ /// If you want to do this intentionally, then you will need to clone the object before serializing it.
+ ///
+ /// The serialized state MUST include a prefix indicating the version of the library that serialized it.
+ fn suspend(self) -> [u8; SERIALIZED_STATE_LEN];
- /// streaming verification API
- fn verify_init(pk: &PK, ctx: Option<&[u8]>) -> Result;
+ /// Resume operation from a serialized state.
+ ///
+ /// Deserializers SHOULD check the version and reject serialized states from incompatible versions
+ /// (including rejecting serializations from a future version of the library).
+ /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its
+ /// deserializer should reject serialized states from that version or older.
+ fn from_suspended(state: [u8; SERIALIZED_STATE_LEN]) -> Result;
+}
- // todo: make this a AsRef<[u8]> ?
- /// Update the verifier with the next chunk of data.
- /// This can be called multiple times.
- fn verify_update(&mut self, msg_chunk: &[u8]);
+/// Similar to [`Suspendable`] in that it allows a stateful object to suspend its operation by
+/// serializing its state into a byte array so that it can be resumed later, potentially from a different host.
+///
+/// The difference is that this trait is for keyed algorithms -- MACs, symmetric ciphers, signatures, etc --
+/// which require a private key in order to resume successfully.
+/// For security reasons, the private key is not included in the serialized state
+/// and must be provided separately as part of the deserialization process.
+pub trait SuspendableKeyed: Sized {
+ /// The type of key that must be re-supplied to resume this object.
+ type Key: ?Sized;
- /// On success, returns Ok(())
- /// On failure, returns Err([`SignatureError::SignatureVerificationFailed`]); may also return other types of [`SignatureError`] as appropriate (such as for invalid-length inputs).
- fn verify_final(self, sig: &[u8]) -> Result<(), SignatureError>;
+ /// Suspend operation by serializing out the state of the object.
+ ///
+ /// Note that this consumes `self` to prevent accidentally continuing to use the object after serialization.
+ /// If you want to do this intentionally, then you will need to clone the object before serializing it.
+ ///
+ /// The serialized state MUST include a prefix indicating the version of the library that serialized it.
+ fn suspend(self) -> [u8; SERIALIZED_STATE_LEN];
+
+ /// Resume operation from a serialized state and the key.
+ ///
+ /// Deserializers SHOULD check the version and reject serialized states from incompatible versions
+ /// (including rejecting serializations from a future version of the library).
+ /// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its
+ /// deserializer should reject serialized states from that version or older.
+ fn from_suspended(
+ state: [u8; SERIALIZED_STATE_LEN],
+ key: &Self::Key,
+ ) -> Result;
+}
+
+// todo -- split all the SymmetricCipher traits into Encryptor and Decryptor
+/// 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;
}
/// Extensible Output Functions (XOFs) are similar to hash functions, except that they can produce output of arbitrary length.
diff --git a/crypto/modes/benches/modes_benches.rs b/crypto/modes/benches/modes_benches.rs
index 66cdaea8..44d1315f 100644
--- a/crypto/modes/benches/modes_benches.rs
+++ b/crypto/modes/benches/modes_benches.rs
@@ -6,19 +6,22 @@
//! implementation hands blocks to `decrypt_blocks2` in 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 pair methods
-//! on `BlockPermutation`, so if it disappears, something has stopped taking the pair path.
+//! on `ElectronicCodeBook`, so if it disappears, something has stopped taking the pair path.
//!
//! `N = 1` is included to show the effect vanishing: with one block there is no pair to form, so
//! decryption falls back to the single-block path and the ratio should be about 1.
+//!
+//! 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.
use bouncycastle_aes_lowmemory::{Aes128, Aes256};
use bouncycastle_core::errors::SymmetricCipherError;
use bouncycastle_core::key_material::{KeyMaterial, KeyType};
use bouncycastle_core::traits::{
- BlockCipher, BlockCipherDecryptor, BlockCipherEncryptor, BlockPermutation, SecurityStrength,
+ Algorithm, BlockCipherDecryptor, BlockCipherEncryptor, ElectronicCodeBook, SecurityStrength,
};
use bouncycastle_modes::{Cbc, Decrypting, Encrypting};
-use criterion::{Criterion, Throughput, criterion_group, criterion_main};
+use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main};
use std::hint::black_box;
const BLOCK_LEN: usize = 16;
@@ -41,19 +44,20 @@ type Aes256Cbc = Cbc;
/// speeds up substantially between those two, so call granularity dominates that comparison.
struct UnpairedAes128(Aes128);
-impl BlockCipher for UnpairedAes128 {
+impl Algorithm for UnpairedAes128 {
+ const ALG_NAME: &'static str = "AES-128 (unpaired)";
const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit;
}
-impl BlockPermutation<16, BLOCK_LEN> 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.
}
@@ -80,97 +84,141 @@ fn bench_aes128(c: &mut Criterion) {
// ---- encryption: serial, one block at a time is all it can do ----
group.bench_function("16KiB encrypt -- N=1", |b| {
- b.iter(|| {
- let (mut enc, _) = Aes128Cbc::::do_encrypt_init(&k).unwrap();
- for block in blocks.iter() {
- black_box(enc.do_encrypt_blocks(&[*block]).unwrap());
- }
- })
+ b.iter_batched(
+ || blocks.clone(),
+ |mut scratch| {
+ let (mut enc, _) = Aes128Cbc::::do_encrypt_init(&k).unwrap();
+ for block in scratch.iter_mut() {
+ enc.do_encrypt(block).unwrap();
+ }
+ black_box(&scratch);
+ },
+ BatchSize::LargeInput,
+ )
});
group.bench_function("16KiB encrypt -- N=8", |b| {
- b.iter(|| {
- let (mut enc, _) = Aes128Cbc::::do_encrypt_init(&k).unwrap();
- for chunk in blocks.chunks_exact(8) {
- let arr: &[[u8; BLOCK_LEN]; 8] = chunk.try_into().unwrap();
- black_box(enc.do_encrypt_blocks(arr).unwrap());
- }
- })
+ b.iter_batched(
+ || blocks.clone(),
+ |mut scratch| {
+ let (mut enc, _) = Aes128Cbc::::do_encrypt_init(&k).unwrap();
+ for chunk in scratch.chunks_exact_mut(8) {
+ let arr: &mut [u8; 8 * BLOCK_LEN] =
+ chunk.as_flattened_mut().try_into().unwrap();
+ enc.do_encrypt(arr).unwrap();
+ }
+ black_box(&scratch);
+ },
+ BatchSize::LargeInput,
+ )
});
// ---- decryption: parallel, uses decrypt_blocks2 for every pair ----
let (mut enc, iv) = Aes128Cbc::::do_encrypt_init(&k).unwrap();
- let ciphertext: Vec<[u8; BLOCK_LEN]> = blocks
- .chunks_exact(8)
- .flat_map(|chunk| {
- let arr: &[[u8; BLOCK_LEN]; 8] = chunk.try_into().unwrap();
- enc.do_encrypt_blocks(arr).unwrap()
- })
- .collect();
+ let mut ciphertext = blocks.clone();
+ for chunk in ciphertext.chunks_exact_mut(8) {
+ let arr: &mut [[u8; BLOCK_LEN]; 8] = chunk.try_into().unwrap();
+ enc.do_encrypt_blocks(arr).unwrap();
+ }
// N=1 never forms a pair, so this is the single-block path: the ratio against encrypt should
// be about 1.
group.bench_function("16KiB decrypt -- N=1 (no pairing)", |b| {
- b.iter(|| {
- let mut dec = Aes128Cbc::::do_decrypt_init(&k, &iv).unwrap();
- for block in ciphertext.iter() {
- black_box(dec.do_decrypt_blocks(&[*block]).unwrap());
- }
- })
+ b.iter_batched(
+ || ciphertext.clone(),
+ |mut scratch| {
+ let mut dec = Aes128Cbc::::do_decrypt_init(&k, &iv).unwrap();
+ for block in scratch.iter_mut() {
+ dec.do_decrypt(block).unwrap();
+ }
+ black_box(&scratch);
+ },
+ BatchSize::LargeInput,
+ )
});
// N=2 and N=8 are all pairs, so every block goes through decrypt_blocks2.
group.bench_function("16KiB decrypt -- N=2 (all pairs)", |b| {
- b.iter(|| {
- let mut dec = Aes128Cbc::::do_decrypt_init(&k, &iv).unwrap();
- for chunk in ciphertext.chunks_exact(2) {
- let arr: &[[u8; BLOCK_LEN]; 2] = chunk.try_into().unwrap();
- black_box(dec.do_decrypt_blocks(arr).unwrap());
- }
- })
+ b.iter_batched(
+ || ciphertext.clone(),
+ |mut scratch| {
+ let mut dec = Aes128Cbc::::do_decrypt_init(&k, &iv).unwrap();
+ for chunk in scratch.chunks_exact_mut(2) {
+ let arr: &mut [u8; 2 * BLOCK_LEN] =
+ chunk.as_flattened_mut().try_into().unwrap();
+ dec.do_decrypt(arr).unwrap();
+ }
+ black_box(&scratch);
+ },
+ BatchSize::LargeInput,
+ )
});
group.bench_function("16KiB decrypt -- N=8 (all pairs)", |b| {
- b.iter(|| {
- let mut dec = Aes128Cbc::::do_decrypt_init(&k, &iv).unwrap();
- for chunk in ciphertext.chunks_exact(8) {
- let arr: &[[u8; BLOCK_LEN]; 8] = chunk.try_into().unwrap();
- black_box(dec.do_decrypt_blocks(arr).unwrap());
- }
- })
+ b.iter_batched(
+ || ciphertext.clone(),
+ |mut scratch| {
+ let mut dec = Aes128Cbc::::do_decrypt_init(&k, &iv).unwrap();
+ for chunk in scratch.chunks_exact_mut(8) {
+ let arr: &mut [u8; 8 * BLOCK_LEN] =
+ chunk.as_flattened_mut().try_into().unwrap();
+ dec.do_decrypt(arr).unwrap();
+ }
+ black_box(&scratch);
+ },
+ BatchSize::LargeInput,
+ )
});
// N=9 is four pairs plus a one-block remainder, so it exercises the tail path too.
group.bench_function("16KiB decrypt -- N=9 (pairs + remainder)", |b| {
- b.iter(|| {
- let mut dec = Aes128Cbc::::do_decrypt_init(&k, &iv).unwrap();
- for chunk in ciphertext.chunks_exact(9) {
- let arr: &[[u8; BLOCK_LEN]; 9] = chunk.try_into().unwrap();
- black_box(dec.do_decrypt_blocks(arr).unwrap());
- }
- })
+ b.iter_batched(
+ || ciphertext.clone(),
+ |mut scratch| {
+ let mut dec = Aes128Cbc::::do_decrypt_init(&k, &iv).unwrap();
+ for chunk in scratch.chunks_exact_mut(9) {
+ let arr: &mut [u8; 9 * BLOCK_LEN] =
+ chunk.as_flattened_mut().try_into().unwrap();
+ dec.do_decrypt(arr).unwrap();
+ }
+ black_box(&scratch);
+ },
+ BatchSize::LargeInput,
+ )
});
// 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| {
- b.iter(|| {
- let mut dec = Aes128Cbc::::do_decrypt_init(&k, &iv).unwrap();
- for chunk in ciphertext.chunks_exact(8) {
- let arr: &[[u8; BLOCK_LEN]; 8] = chunk.try_into().unwrap();
- black_box(dec.do_decrypt_blocks(arr).unwrap());
- }
- })
+ b.iter_batched(
+ || ciphertext.clone(),
+ |mut scratch| {
+ let mut dec = Aes128Cbc::::do_decrypt_init(&k, &iv).unwrap();
+ for chunk in scratch.chunks_exact_mut(8) {
+ let arr: &mut [u8; 8 * BLOCK_LEN] =
+ chunk.as_flattened_mut().try_into().unwrap();
+ dec.do_decrypt(arr).unwrap();
+ }
+ black_box(&scratch);
+ },
+ BatchSize::LargeInput,
+ )
});
group.bench_function("16KiB decrypt -- N=8, no pair path (trait default)", |b| {
- b.iter(|| {
- let mut dec = UnpairedAes128Cbc::::do_decrypt_init(&k, &iv).unwrap();
- for chunk in ciphertext.chunks_exact(8) {
- let arr: &[[u8; BLOCK_LEN]; 8] = chunk.try_into().unwrap();
- black_box(dec.do_decrypt_blocks(arr).unwrap());
- }
- })
+ b.iter_batched(
+ || ciphertext.clone(),
+ |mut scratch| {
+ let mut dec = UnpairedAes128Cbc::::do_decrypt_init(&k, &iv).unwrap();
+ for chunk in scratch.chunks_exact_mut(8) {
+ let arr: &mut [u8; 8 * BLOCK_LEN] =
+ chunk.as_flattened_mut().try_into().unwrap();
+ dec.do_decrypt(arr).unwrap();
+ }
+ black_box(&scratch);
+ },
+ BatchSize::LargeInput,
+ )
});
group.finish();
@@ -184,32 +232,42 @@ fn bench_aes256(c: &mut Criterion) {
group.throughput(Throughput::Bytes(DATA_LEN as u64));
group.bench_function("16KiB encrypt -- N=8", |b| {
- b.iter(|| {
- let (mut enc, _) = Aes256Cbc::::do_encrypt_init(&k).unwrap();
- for chunk in blocks.chunks_exact(8) {
- let arr: &[[u8; BLOCK_LEN]; 8] = chunk.try_into().unwrap();
- black_box(enc.do_encrypt_blocks(arr).unwrap());
- }
- })
+ b.iter_batched(
+ || blocks.clone(),
+ |mut scratch| {
+ let (mut enc, _) = Aes256Cbc::::do_encrypt_init(&k).unwrap();
+ for chunk in scratch.chunks_exact_mut(8) {
+ let arr: &mut [u8; 8 * BLOCK_LEN] =
+ chunk.as_flattened_mut().try_into().unwrap();
+ enc.do_encrypt(arr).unwrap();
+ }
+ black_box(&scratch);
+ },
+ BatchSize::LargeInput,
+ )
});
let (mut enc, iv) = Aes256Cbc::::do_encrypt_init(&k).unwrap();
- let ciphertext: Vec<[u8; BLOCK_LEN]> = blocks
- .chunks_exact(8)
- .flat_map(|chunk| {
- let arr: &[[u8; BLOCK_LEN]; 8] = chunk.try_into().unwrap();
- enc.do_encrypt_blocks(arr).unwrap()
- })
- .collect();
+ let mut ciphertext = blocks.clone();
+ for chunk in ciphertext.chunks_exact_mut(8) {
+ let arr: &mut [[u8; BLOCK_LEN]; 8] = chunk.try_into().unwrap();
+ enc.do_encrypt_blocks(arr).unwrap();
+ }
group.bench_function("16KiB decrypt -- N=8 (all pairs)", |b| {
- b.iter(|| {
- let mut dec = Aes256Cbc::