Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a8e3d76
core: KeyMaterialError::WeakKey, for a key an algorithm's specificati…
dghgit Sep 8, 2026
db5b9cc
cli: block-mode plumbing takes the block length as a const parameter …
dghgit Sep 8, 2026
97528a5
core: ElectronicCodeBook::ENCRYPTION_APPROVED (default true) marks a …
dghgit Sep 8, 2026
98eff6a
modes: the Encrypting direction of Ecb, Cbc, Cfb, Cfb8 and Ctr assert…
dghgit Sep 8, 2026
5519176
tdes: add bouncycastle-tdes, a constant-time table-free TDEA engine (…
dghgit Sep 8, 2026
7cda66c
tdes: drop the summary.md reviewer-orientation doc
dghgit Sep 19, 2026
a802f10
mem_usage_benches: fence bench_tdes_mem_usage's shell recipes as ```text
dghgit Sep 19, 2026
50d4685
tdes: fix the three CFB/CFB8/CTR doctests broken by StreamCipherEncry…
dghgit Sep 20, 2026
d7e98cf
tdes: adapt to feature/simple-ciphers -- the API changes this branch …
dghgit Sep 28, 2026
052978b
Merge branch 'feature/simple-ciphers' into feature/tdes
dghgit Sep 29, 2026
15e6d1b
tdes: adapt to feature/simple-ciphers 0fb4e79 -- the TDES_CFB/CFB8/CT…
dghgit Sep 29, 2026
c122cf8
Merge branch 'feature/simple-ciphers' into feature/tdes
dghgit Sep 30, 2026
2c73c6d
Merge branch 'feature/simple-ciphers' into feature/tdes
dghgit Oct 1, 2026
2c5be1c
tdes: adopt the hazmat layout (#156 step 5)
dghgit Oct 1, 2026
fd64c80
Merge branch 'feature/simple-ciphers' into feature/tdes
dghgit Oct 1, 2026
68cbef5
tdes: project TDES_CBC and TDES_ECB through core's sealed Direction::…
dghgit Oct 1, 2026
5197229
Merge branch 'feature/simple-ciphers' into feature/tdes
dghgit Oct 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ bouncycastle-rng = { path = "./crypto/rng" }
bouncycastle-sha2 = { path = "./crypto/sha2" }
bouncycastle-sha3 = { path = "./crypto/sha3" }
bouncycastle-sm3 = { path = "./crypto/sm3" }
bouncycastle-tdes = { path = "./crypto/tdes" }
bouncycastle-utils = { path = "./crypto/utils" }


Expand Down Expand Up @@ -64,3 +65,4 @@ bouncycastle-rng.workspace = true
bouncycastle-sha2.workspace = true
bouncycastle-sha3.workspace = true
bouncycastle-sm3.workspace = true
bouncycastle-tdes.workspace = true
27 changes: 15 additions & 12 deletions cli/src/aes_cbc_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
//! separately.

use crate::helpers::block_mode_helpers::{
BLOCK_LEN, CipherDirection, decrypt_stream, encrypt_stream, load_key,
CipherDirection, decrypt_stream, encrypt_stream, load_key,
};
use bouncycastle::aes::AES_BLOCK_LEN;
use bouncycastle::aes::hazmat::{AES128Internal, AES192Internal, AES256Internal};
use bouncycastle::cipher::modes::Cbc;
use bouncycastle::cipher::{Decrypting, Encrypting};
Expand Down Expand Up @@ -54,18 +55,20 @@ fn run<P, const KEY_LEN: usize>(
key: &KeyMaterial<KEY_LEN>,
output_hex: bool,
) where
P: ElectronicCodeBook<KEY_LEN, BLOCK_LEN>,
P: ElectronicCodeBook<KEY_LEN, AES_BLOCK_LEN>,
{
match action {
CipherDirection::Encrypt => {
encrypt_stream::<Cbc<P, Encrypting, KEY_LEN, BLOCK_LEN>, KEY_LEN, BLOCK_LEN>(
key, output_hex, MODE,
)
}
CipherDirection::Decrypt => {
decrypt_stream::<Cbc<P, Decrypting, KEY_LEN, BLOCK_LEN>, KEY_LEN, BLOCK_LEN>(
key, output_hex, MODE,
)
}
CipherDirection::Encrypt => encrypt_stream::<
Cbc<P, Encrypting, KEY_LEN, AES_BLOCK_LEN>,
KEY_LEN,
AES_BLOCK_LEN,
AES_BLOCK_LEN,
>(key, output_hex, MODE),
CipherDirection::Decrypt => decrypt_stream::<
Cbc<P, Decrypting, KEY_LEN, AES_BLOCK_LEN>,
KEY_LEN,
AES_BLOCK_LEN,
AES_BLOCK_LEN,
>(key, output_hex, MODE),
}
}
19 changes: 10 additions & 9 deletions cli/src/aes_ccm_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ use std::fs::File;
use std::io::{self, Read};
use std::process::exit;

use bouncycastle::aes::AES_BLOCK_LEN;
use bouncycastle::aes::hazmat::{AES128Internal, AES192Internal, AES256Internal};
use bouncycastle::cipher::modes::Ccm;
use bouncycastle::cipher::{Decrypting, Encrypting};
Expand All @@ -60,7 +61,7 @@ use bouncycastle::core::key_material::KeyMaterial;
use bouncycastle::hex;

use crate::helpers;
use crate::helpers::block_mode_helpers::{BLOCK_LEN, CipherDirection, load_key};
use crate::helpers::block_mode_helpers::{CipherDirection, load_key};

/// Bytes of `--aad-file` read per call, matching the other commands' streaming chunk.
const CHUNK_LEN: usize = 1024;
Expand Down Expand Up @@ -249,10 +250,10 @@ fn load_aad(aad: &Option<String>, aad_file: &Option<String>) -> Aad {
/// encoding that does not match the AAD; that is reported and the command exits rather than
/// producing it.
fn feed_aad<P, Dir, const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize>(
ccm: &mut Ccm<P, Dir, KEY_LEN, BLOCK_LEN, NONCE_LEN, TAG_LEN>,
ccm: &mut Ccm<P, Dir, KEY_LEN, AES_BLOCK_LEN, NONCE_LEN, TAG_LEN>,
aad: &mut Aad,
) where
P: ElectronicCodeBook<KEY_LEN, BLOCK_LEN>,
P: ElectronicCodeBook<KEY_LEN, AES_BLOCK_LEN>,
{
match aad {
Aad::Bytes(bytes) => {
Expand Down Expand Up @@ -307,7 +308,7 @@ fn run<P, const KEY_LEN: usize>(
tag_len: usize,
output_hex: bool,
) where
P: ElectronicCodeBook<KEY_LEN, BLOCK_LEN>,
P: ElectronicCodeBook<KEY_LEN, AES_BLOCK_LEN>,
{
// Reject this before opening nonce/AAD files or waiting for stdin. Appendix A.1: "t is an
// element of {4, 6, 8, 10, 12, 14, 16}".
Expand Down Expand Up @@ -380,14 +381,14 @@ fn payload_past_the_q_limit<P, const KEY_LEN: usize, const NONCE_LEN: usize, con
payload_len: usize,
) -> !
where
P: ElectronicCodeBook<KEY_LEN, BLOCK_LEN>,
P: ElectronicCodeBook<KEY_LEN, AES_BLOCK_LEN>,
{
eprintln!("Error: {msg}");
eprintln!(
" Payload is {payload_len} bytes; with a {NONCE_LEN}-byte nonce, q = {} and the \
limit is {} bytes.",
15 - NONCE_LEN,
Ccm::<P, Encrypting, KEY_LEN, BLOCK_LEN, NONCE_LEN, TAG_LEN>::MAX_PAYLOAD_LEN,
Ccm::<P, Encrypting, KEY_LEN, AES_BLOCK_LEN, NONCE_LEN, TAG_LEN>::MAX_PAYLOAD_LEN,
);
eprintln!(" Use a shorter nonce for a larger payload.");
exit(-1)
Expand All @@ -408,12 +409,12 @@ fn go<P, const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize>(
encrypt: bool,
output_hex: bool,
) where
P: ElectronicCodeBook<KEY_LEN, BLOCK_LEN>,
P: ElectronicCodeBook<KEY_LEN, AES_BLOCK_LEN>,
{
type Enc<P, const K: usize, const N: usize, const T: usize> =
Ccm<P, Encrypting, K, BLOCK_LEN, N, T>;
Ccm<P, Encrypting, K, AES_BLOCK_LEN, N, T>;
type Dec<P, const K: usize, const N: usize, const T: usize> =
Ccm<P, Decrypting, K, BLOCK_LEN, N, T>;
Ccm<P, Decrypting, K, AES_BLOCK_LEN, N, T>;

// `run` dispatched on this exact length, so the conversion cannot fail.
let Ok(nonce) = <[u8; NONCE_LEN]>::try_from(nonce_bytes) else {
Expand Down
11 changes: 6 additions & 5 deletions cli/src/aes_cfb8_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@
//! plaintext byte, corrupts the following 16 bytes, and then decryption resynchronises. Do not
//! decrypt data you have not authenticated separately.

use crate::helpers::block_mode_helpers::{BLOCK_LEN, CipherDirection, load_key};
use crate::helpers::block_mode_helpers::{CipherDirection, load_key};
use crate::helpers::stream_mode_helpers::run_stream_mode;
use bouncycastle::aes::AES_BLOCK_LEN;
use bouncycastle::aes::hazmat::{AES128Internal, AES192Internal, AES256Internal};
use bouncycastle::cipher::modes::Cfb8;
use bouncycastle::cipher::{Decrypting, Encrypting};
Expand Down Expand Up @@ -66,12 +67,12 @@ fn run<P, const KEY_LEN: usize>(
key: &KeyMaterial<KEY_LEN>,
output_hex: bool,
) where
P: ElectronicCodeBook<KEY_LEN, BLOCK_LEN>,
P: ElectronicCodeBook<KEY_LEN, AES_BLOCK_LEN>,
{
run_stream_mode::<
Cfb8<P, Encrypting, KEY_LEN, BLOCK_LEN>,
Cfb8<P, Decrypting, KEY_LEN, BLOCK_LEN>,
Cfb8<P, Encrypting, KEY_LEN, AES_BLOCK_LEN>,
Cfb8<P, Decrypting, KEY_LEN, AES_BLOCK_LEN>,
KEY_LEN,
BLOCK_LEN,
AES_BLOCK_LEN,
>(action, key, output_hex)
}
11 changes: 6 additions & 5 deletions cli/src/aes_cfb_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@
//! of the plaintext in the *same* block, so an attacker edits the block they aimed at, at the cost
//! of randomising the next one. Do not decrypt data you have not authenticated separately.

use crate::helpers::block_mode_helpers::{BLOCK_LEN, CipherDirection, load_key};
use crate::helpers::block_mode_helpers::{CipherDirection, load_key};
use crate::helpers::stream_mode_helpers::run_stream_mode;
use bouncycastle::aes::AES_BLOCK_LEN;
use bouncycastle::aes::hazmat::{AES128Internal, AES192Internal, AES256Internal};
use bouncycastle::cipher::modes::Cfb;
use bouncycastle::cipher::{Decrypting, Encrypting};
Expand Down Expand Up @@ -67,12 +68,12 @@ fn run<P, const KEY_LEN: usize>(
key: &KeyMaterial<KEY_LEN>,
output_hex: bool,
) where
P: ElectronicCodeBook<KEY_LEN, BLOCK_LEN>,
P: ElectronicCodeBook<KEY_LEN, AES_BLOCK_LEN>,
{
run_stream_mode::<
Cfb<P, Encrypting, KEY_LEN, BLOCK_LEN>,
Cfb<P, Decrypting, KEY_LEN, BLOCK_LEN>,
Cfb<P, Encrypting, KEY_LEN, AES_BLOCK_LEN>,
Cfb<P, Decrypting, KEY_LEN, AES_BLOCK_LEN>,
KEY_LEN,
BLOCK_LEN,
AES_BLOCK_LEN,
>(action, key, output_hex)
}
10 changes: 5 additions & 5 deletions cli/src/aes_ctr_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
//! nonce is drawn from the OS-backed DRBG for exactly that reason, and there is no way to supply
//! one.

use crate::helpers::block_mode_helpers::{BLOCK_LEN, CipherDirection, load_key};
use crate::helpers::block_mode_helpers::{CipherDirection, load_key};
use crate::helpers::stream_mode_helpers::run_stream_mode;
use bouncycastle::aes::CTR_NONCE_LEN;
use bouncycastle::aes::hazmat::{AES128Internal, AES192Internal, AES256Internal};
use bouncycastle::aes::{AES_BLOCK_LEN, CTR_NONCE_LEN};
use bouncycastle::cipher::modes::Ctr;
use bouncycastle::cipher::{Decrypting, Encrypting};
use bouncycastle::core::hazmat::ElectronicCodeBook;
Expand Down Expand Up @@ -75,11 +75,11 @@ fn run<P, const KEY_LEN: usize>(
key: &KeyMaterial<KEY_LEN>,
output_hex: bool,
) where
P: ElectronicCodeBook<KEY_LEN, BLOCK_LEN>,
P: ElectronicCodeBook<KEY_LEN, AES_BLOCK_LEN>,
{
run_stream_mode::<
Ctr<P, Encrypting, KEY_LEN, BLOCK_LEN, CTR_NONCE_LEN>,
Ctr<P, Decrypting, KEY_LEN, BLOCK_LEN, CTR_NONCE_LEN>,
Ctr<P, Encrypting, KEY_LEN, AES_BLOCK_LEN, CTR_NONCE_LEN>,
Ctr<P, Decrypting, KEY_LEN, AES_BLOCK_LEN, CTR_NONCE_LEN>,
KEY_LEN,
CTR_NONCE_LEN,
>(action, key, output_hex)
Expand Down
9 changes: 5 additions & 4 deletions cli/src/aes_ecb_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
//! `aes*-cbc` or `aes*-cfb` under separate authentication, or better an AEAD.

use crate::helpers::block_mode_helpers::{
BLOCK_LEN, CipherDirection, decrypt_stream, encrypt_stream, load_key,
CipherDirection, decrypt_stream, encrypt_stream, load_key,
};
use bouncycastle::aes::AES_BLOCK_LEN;
use bouncycastle::aes::hazmat::{AES128Internal, AES192Internal, AES256Internal};
use bouncycastle::cipher::modes::hazmat::Ecb;
use bouncycastle::cipher::{Decrypting, Encrypting};
Expand Down Expand Up @@ -61,16 +62,16 @@ fn run<P, const KEY_LEN: usize>(
key: &KeyMaterial<KEY_LEN>,
output_hex: bool,
) where
P: ElectronicCodeBook<KEY_LEN, BLOCK_LEN>,
P: ElectronicCodeBook<KEY_LEN, AES_BLOCK_LEN>,
{
match action {
CipherDirection::Encrypt => {
encrypt_stream::<Ecb<P, Encrypting, KEY_LEN, BLOCK_LEN>, KEY_LEN, 0>(
encrypt_stream::<Ecb<P, Encrypting, KEY_LEN, AES_BLOCK_LEN>, KEY_LEN, 0, AES_BLOCK_LEN>(
key, output_hex, MODE,
)
}
CipherDirection::Decrypt => {
decrypt_stream::<Ecb<P, Decrypting, KEY_LEN, BLOCK_LEN>, KEY_LEN, 0>(
decrypt_stream::<Ecb<P, Decrypting, KEY_LEN, AES_BLOCK_LEN>, KEY_LEN, 0, AES_BLOCK_LEN>(
key, output_hex, MODE,
)
}
Expand Down
63 changes: 43 additions & 20 deletions cli/src/helpers/block_mode_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
//! Shared plumbing for the block-cipher-mode subcommands: `aes{128,192,256}-{cbc,ecb}`.
//! Shared plumbing for the block-cipher-mode subcommands: `aes{128,192,256}-{cbc,ecb}` and
//! `tdes-{cbc,ecb}`.
//!
//! Everything here is mode-independent -- key loading, stdin framing, block-alignment enforcement,
//! output formatting -- and is generic over the mode via [`BlockCipherEncryptor`] /
//! [`BlockCipherDecryptor`]. `aes_cbc_cmd` and `aes_ecb_cmd` are thin dispatchers over it, so the
//! commands cannot drift apart on the parts that matter for correctness.
//! [`BlockCipherDecryptor`]. `aes_cbc_cmd`, `aes_ecb_cmd`, `tdes_cbc_cmd` and `tdes_ecb_cmd` are thin
//! dispatchers over it, so the commands cannot drift apart on the parts that matter for correctness.
//! The block length is a const parameter -- 16 for AES, 8 for TDES -- so the framing below is
//! written once for both.
//!
//! The CFB and CTR commands are stream ciphers and live in [`crate::helpers::stream_mode_helpers`] instead;
//! they share
Expand Down Expand Up @@ -31,8 +34,8 @@
//! # Input must be block-aligned
//!
//! The modes in this module are defined only on whole blocks (SP 800-38A Sec 5.2), and these
//! commands apply no padding, so input that is not a multiple of 16 bytes is rejected rather than
//! silently padded. (The CFB commands have no such requirement; see [`crate::helpers::stream_mode_helpers`].)
//! commands apply no padding, so input that is not a multiple of the block length (16 bytes for AES,
//! 8 for TDES) is rejected rather than silently padded. (The CFB commands have no such requirement; see [`crate::helpers::stream_mode_helpers`].)
//! Padding is the caller's business; the library offers `bouncycastle_cipher::padding` for it, but wiring a
//! padding scheme into the CLI would change the on-the-wire format and is a separate decision.
//!
Expand All @@ -58,15 +61,14 @@ use std::io;
use std::io::{Read, Write};
use std::process::exit;

/// The AES block length in bytes.
pub(crate) const BLOCK_LEN: usize = 16;

/// Bytes processed per call: 1 KiB = 64 blocks, matching the other streaming commands.
/// Bytes processed per call: 1 KiB, matching the other streaming commands. That is 64 AES blocks
/// or 128 TDES blocks; every block length the commands use divides it, which `do_*` checks at
/// compile time.
///
/// A full chunk goes through `do_*::<CHUNK_LEN>` in one call, in place, which for decryption means
/// 32 pairs down the mode's two-block 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.
pub(crate) const CHUNK_LEN: usize = 64 * BLOCK_LEN;
/// the mode's eight-block and two-block paths. The sub-chunk tail at end of input goes one block at
/// a time; it is bounded, so its cost does not scale with the input.
pub(crate) const CHUNK_LEN: usize = 1024;

/// Which direction to run. Shared by cipher subcommands, -- see the specific subcommand's
/// own `--help` (`bc-rust aes128-ccm --help` and friends) for what `encrypt`/`decrypt` actually do
Expand All @@ -79,10 +81,21 @@ pub(crate) enum CipherDirection {
Decrypt,
}

/// The only action the `tdes2-*` commands offer. Two-key TDEA is disallowed for encryption (NIST
/// SP 800-131A Rev 2 Table 1) and the library will not compile an encrypting mode over it, so the
/// command line does not have the word: `tdes2-cbc encrypt` is rejected by the argument parser.
#[derive(ValueEnum, Clone, Debug)]
pub(crate) enum DecryptOnlyAction {
/// Decrypt stdin to stdout, exactly as the corresponding `tdes-*` command's `decrypt` does:
/// the IV or nonce is read from the leading bytes of the input, as written by whatever
/// legacy system produced the ciphertext.
Decrypt,
}

/// Loads the key from `--key` (hex) or `--key-file` (binary or hex), and checks its length.
///
/// `KEY_LEN` is exact: AES has three key lengths and the command selects one, so a key of the
/// wrong length is a mistake rather than something to truncate or pad.
/// `KEY_LEN` is exact: each command selects one key length (AES has three, TDES one), so a key of
/// the wrong length is a mistake rather than something to truncate or pad.
pub(crate) fn load_key<const KEY_LEN: usize>(
key: &Option<String>,
key_file: &Option<String>,
Expand Down Expand Up @@ -145,7 +158,12 @@ pub(crate) fn load_key<const KEY_LEN: usize>(
/// `INIT_DATA_LEN` is the mode's: one block for CBC, 0 for ECB, in which case nothing is written
/// ahead of the ciphertext. `mode` names the mode in error messages ("CBC", "ECB"); it has no
/// effect on the output.
pub(crate) fn encrypt_stream<E, const KEY_LEN: usize, const INIT_DATA_LEN: usize>(
pub(crate) fn encrypt_stream<
E,
const KEY_LEN: usize,
const INIT_DATA_LEN: usize,
const BLOCK_LEN: usize,
>(
key: &KeyMaterial<KEY_LEN>,
output_hex: bool,
mode: &str,
Expand All @@ -163,7 +181,7 @@ pub(crate) fn encrypt_stream<E, const KEY_LEN: usize, const INIT_DATA_LEN: usize
}

// The cipher works in place: `data` holds plaintext on the way in and ciphertext on the way out.
stream_aligned(mode, |data| {
stream_aligned::<BLOCK_LEN>(mode, |data| {
if let Ok(chunk) = <&mut [u8; CHUNK_LEN]>::try_from(&mut *data) {
// Cannot fail: none of these modes has a per-IV data limit.
enc.do_encrypt(chunk).unwrap();
Expand All @@ -181,7 +199,12 @@ pub(crate) fn encrypt_stream<E, const KEY_LEN: usize, const INIT_DATA_LEN: usize

/// Decrypts stdin to stdout under the mode `D`, taking the init data (the IV) from the first
/// `INIT_DATA_LEN` bytes of input -- one block for CBC, nothing for ECB.
pub(crate) fn decrypt_stream<D, const KEY_LEN: usize, const INIT_DATA_LEN: usize>(
pub(crate) fn decrypt_stream<
D,
const KEY_LEN: usize,
const INIT_DATA_LEN: usize,
const BLOCK_LEN: usize,
>(
key: &KeyMaterial<KEY_LEN>,
output_hex: bool,
mode: &str,
Expand All @@ -205,9 +228,9 @@ pub(crate) fn decrypt_stream<D, const KEY_LEN: usize, const INIT_DATA_LEN: usize
exit(-1);
});

stream_aligned(mode, |data| {
stream_aligned::<BLOCK_LEN>(mode, |data| {
if let Ok(chunk) = <&mut [u8; CHUNK_LEN]>::try_from(&mut *data) {
// A full chunk is 32 pairs, so this is the mode's two-block path.
// A full chunk is many whole blocks, so this is the mode's batched paths.
dec.do_decrypt(chunk).unwrap();
} else {
for block in data.as_chunks_mut::<BLOCK_LEN>().0 {
Expand All @@ -227,7 +250,7 @@ pub(crate) fn decrypt_stream<D, const KEY_LEN: usize, const INIT_DATA_LEN: usize
///
/// Input whose total length is not a multiple of `BLOCK_LEN` is an error, because none of these
/// modes is defined on a partial block and these commands do not pad.
fn stream_aligned(mode: &str, mut process: impl FnMut(&mut [u8])) {
fn stream_aligned<const BLOCK_LEN: usize>(mode: &str, mut process: impl FnMut(&mut [u8])) {
let mut buf = [0u8; CHUNK_LEN];
let mut filled = 0usize;

Expand Down
3 changes: 2 additions & 1 deletion cli/src/helpers/stream_mode_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Shared plumbing for the stream-cipher-mode subcommands: `aes{128,192,256}-{cfb,cfb8,ctr}`.
//! Shared plumbing for the stream-cipher-mode subcommands: `aes{128,192,256}-{cfb,cfb8,ctr}` and
//! `tdes-{cfb,cfb8,ctr}`.
//!
//! The stream-cipher counterpart of [`crate::helpers::block_mode_helpers`], and deliberately parallel to it:
//! same key loading (reused directly from there), same IV convention, same `-x` hex output, same
Expand Down
Loading
Loading