Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e531536
core: KeyMaterialError::WeakKey, for a key an algorithm's specificati…
dghgit Sep 8, 2026
8d2ae6e
cli: block-mode plumbing takes the block length as a const parameter …
dghgit Sep 8, 2026
fb72d18
core: ElectronicCodeBook::ENCRYPTION_APPROVED (default true) marks a …
dghgit Sep 8, 2026
48ea3fc
modes: the Encrypting direction of Ecb, Cbc, Cfb, Cfb8 and Ctr assert…
dghgit Sep 8, 2026
ff57e12
tdes: add bouncycastle-tdes, a constant-time table-free TDEA engine (…
dghgit Sep 8, 2026
a7430f1
tdes: drop the summary.md reviewer-orientation doc
dghgit Sep 19, 2026
63ca623
mem_usage_benches: fence bench_tdes_mem_usage's shell recipes as ```text
dghgit Sep 19, 2026
f585745
tdes: fix the three CFB/CFB8/CTR doctests broken by StreamCipherEncry…
dghgit Sep 20, 2026
d9e7781
tdes: adapt to feature/simple-ciphers -- the API changes this branch …
dghgit Sep 28, 2026
0f1ff89
tdes: adapt to feature/simple-ciphers 0fb4e79 -- the TDES_CFB/CFB8/CT…
dghgit Sep 29, 2026
9387916
tdes: adopt the hazmat layout (#156 step 5)
dghgit Oct 1, 2026
2633b4d
tdes: project TDES_CBC and TDES_ECB through core's sealed Direction::…
dghgit Oct 1, 2026
494715b
tdes: the adaptations the dropped merges carried -- bouncycastle-ciph…
dghgit Oct 24, 2026
1f21fe5
tdes: onto the key objects -- TDESKey<P, M> hands out the mode worker…
dghgit Oct 24, 2026
133d7c9
tdes: the two engines and the keys compare in constant time
dghgit Oct 25, 2026
fc0547e
tdes: review fixes -- the keys' redundant key-type check goes, the st…
dghgit Oct 28, 2026
2d83b84
cli: the tdes commands take the aes contract -- PKCS7, --iv/--iv-file…
dghgit Oct 28, 2026
2fe44bf
tdes: the stack table sums the frame chain, as the AES table does
dghgit Oct 30, 2026
1cf33b1
tdes: a key checks the key type its mode asks for before the engine does
dghgit Oct 30, 2026
cabef79
tdes: the clippy nits from the camellia round
dghgit Oct 30, 2026
f663a18
tdes: the PR #142 findings that apply here -- the mode and key docs s…
dghgit Nov 3, 2026
8389606
tdes: the merge-time adaptations to feature/key-rework-memory, carrie…
dghgit Nov 3, 2026
b827c95
Merge release/0.1.3alpha (029a17b0, the squash merge of PR #190) into…
dghgit Nov 4, 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 @@ -31,6 +31,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 @@ -75,3 +76,4 @@ bouncycastle-rng.workspace = true
bouncycastle-sha2.workspace = true
bouncycastle-sha3.workspace = true
bouncycastle-sm3.workspace = true
bouncycastle-tdes.workspace = true
33 changes: 20 additions & 13 deletions cli/src/aes_cbc_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@
//! separately.

use crate::helpers::block_mode_helpers::{
BLOCK_LEN, CipherDirection, decrypt_stream, encrypt_stream, load_iv, load_key_object,
start_encryptor,
CipherDirection, decrypt_stream, encrypt_stream, load_iv, load_key_object, start_encryptor,
};
use bouncycastle::aes::{AES128Key, AES192Key, AES256Key};
use bouncycastle::aes::{AES_BLOCK_LEN, AES128Key, AES192Key, AES256Key};
use bouncycastle::cipher::modes::CBC;
use bouncycastle::cipher::padding::PKCS7;
use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey};
Expand All @@ -29,7 +28,7 @@ pub(crate) fn aes128_cbc_cmd(
run::<_, 16>(
action,
&load_key_object::<AES128Key<CBC<PKCS7>>, 16>(key, key_file, "AES-128"),
load_iv::<BLOCK_LEN>(iv, iv_file),
load_iv::<AES_BLOCK_LEN>(iv, iv_file),
output_hex,
);
}
Expand All @@ -45,7 +44,7 @@ pub(crate) fn aes192_cbc_cmd(
run::<_, 24>(
action,
&load_key_object::<AES192Key<CBC<PKCS7>>, 24>(key, key_file, "AES-192"),
load_iv::<BLOCK_LEN>(iv, iv_file),
load_iv::<AES_BLOCK_LEN>(iv, iv_file),
output_hex,
);
}
Expand All @@ -61,7 +60,7 @@ pub(crate) fn aes256_cbc_cmd(
run::<_, 32>(
action,
&load_key_object::<AES256Key<CBC<PKCS7>>, 32>(key, key_file, "AES-256"),
load_iv::<BLOCK_LEN>(iv, iv_file),
load_iv::<AES_BLOCK_LEN>(iv, iv_file),
output_hex,
);
}
Expand All @@ -70,19 +69,27 @@ pub(crate) fn aes256_cbc_cmd(
fn run<K, const KEY_LEN: usize>(
action: &CipherDirection,
key: &K,
iv: Option<[u8; BLOCK_LEN]>,
iv: Option<[u8; AES_BLOCK_LEN]>,
output_hex: bool,
) where
K: SymmetricEncryptionKey<KEY_LEN, BLOCK_LEN, BLOCK_LEN>
+ SymmetricDecryptionKey<KEY_LEN, BLOCK_LEN, BLOCK_LEN>,
K: SymmetricEncryptionKey<KEY_LEN, AES_BLOCK_LEN, AES_BLOCK_LEN>
+ SymmetricDecryptionKey<KEY_LEN, AES_BLOCK_LEN, AES_BLOCK_LEN>,
{
match action {
CipherDirection::Encrypt => {
let (enc, generated_iv) = start_encryptor(iv, |rng| key.create_encryptor(rng));
encrypt_stream::<_, BLOCK_LEN>(enc, generated_iv.as_ref(), output_hex, None)
}
CipherDirection::Decrypt => {
decrypt_stream::<_, BLOCK_LEN>(iv, |iv| key.create_decryptor(iv), output_hex, None)
encrypt_stream::<_, AES_BLOCK_LEN, AES_BLOCK_LEN>(
enc,
generated_iv.as_ref(),
output_hex,
None,
)
}
CipherDirection::Decrypt => decrypt_stream::<_, AES_BLOCK_LEN, AES_BLOCK_LEN>(
iv,
|iv| key.create_decryptor(iv),
output_hex,
None,
),
}
}
22 changes: 11 additions & 11 deletions cli/src/aes_cfb8_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +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_iv, load_key_object};
use crate::helpers::block_mode_helpers::{CipherDirection, load_iv, load_key_object};
use crate::helpers::stream_mode_helpers::run_stream_mode;
use bouncycastle::aes::{AES128Key, AES192Key, AES256Key};
use bouncycastle::aes::{AES_BLOCK_LEN, AES128Key, AES192Key, AES256Key};
use bouncycastle::cipher::modes::CFB8;
use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey};
use bouncycastle::core::traits::{StreamCipherDecryptor, StreamCipherEncryptor};
Expand All @@ -43,7 +43,7 @@ pub(crate) fn aes128_cfb8_cmd(
run::<_, 16>(
action,
&load_key_object::<AES128Key<CFB8>, 16>(key, key_file, "AES-128"),
load_iv::<BLOCK_LEN>(iv, iv_file),
load_iv::<AES_BLOCK_LEN>(iv, iv_file),
output_hex,
);
}
Expand All @@ -59,7 +59,7 @@ pub(crate) fn aes192_cfb8_cmd(
run::<_, 24>(
action,
&load_key_object::<AES192Key<CFB8>, 24>(key, key_file, "AES-192"),
load_iv::<BLOCK_LEN>(iv, iv_file),
load_iv::<AES_BLOCK_LEN>(iv, iv_file),
output_hex,
);
}
Expand All @@ -75,7 +75,7 @@ pub(crate) fn aes256_cfb8_cmd(
run::<_, 32>(
action,
&load_key_object::<AES256Key<CFB8>, 32>(key, key_file, "AES-256"),
load_iv::<BLOCK_LEN>(iv, iv_file),
load_iv::<AES_BLOCK_LEN>(iv, iv_file),
output_hex,
);
}
Expand All @@ -84,13 +84,13 @@ pub(crate) fn aes256_cfb8_cmd(
fn run<'k, K, const KEY_LEN: usize>(
action: &CipherDirection,
key: &'k K,
iv: Option<[u8; BLOCK_LEN]>,
iv: Option<[u8; AES_BLOCK_LEN]>,
output_hex: bool,
) where
K: SymmetricEncryptionKey<KEY_LEN, BLOCK_LEN, 0>
+ SymmetricDecryptionKey<KEY_LEN, BLOCK_LEN, 0>,
K::Encryptor<'k>: StreamCipherEncryptor<BLOCK_LEN>,
K::Decryptor<'k>: StreamCipherDecryptor<BLOCK_LEN>,
K: SymmetricEncryptionKey<KEY_LEN, AES_BLOCK_LEN, 0>
+ SymmetricDecryptionKey<KEY_LEN, AES_BLOCK_LEN, 0>,
K::Encryptor<'k>: StreamCipherEncryptor<AES_BLOCK_LEN>,
K::Decryptor<'k>: StreamCipherDecryptor<AES_BLOCK_LEN>,
{
run_stream_mode::<K, KEY_LEN, BLOCK_LEN>(action, key, iv, output_hex)
run_stream_mode::<K, KEY_LEN, AES_BLOCK_LEN>(action, key, iv, output_hex)
}
22 changes: 11 additions & 11 deletions cli/src/aes_cfb_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +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_iv, load_key_object};
use crate::helpers::block_mode_helpers::{CipherDirection, load_iv, load_key_object};
use crate::helpers::stream_mode_helpers::run_stream_mode;
use bouncycastle::aes::{AES128Key, AES192Key, AES256Key};
use bouncycastle::aes::{AES_BLOCK_LEN, AES128Key, AES192Key, AES256Key};
use bouncycastle::cipher::modes::CFB;
use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey};
use bouncycastle::core::traits::{StreamCipherDecryptor, StreamCipherEncryptor};
Expand All @@ -44,7 +44,7 @@ pub(crate) fn aes128_cfb_cmd(
run::<_, 16>(
action,
&load_key_object::<AES128Key<CFB>, 16>(key, key_file, "AES-128"),
load_iv::<BLOCK_LEN>(iv, iv_file),
load_iv::<AES_BLOCK_LEN>(iv, iv_file),
output_hex,
);
}
Expand All @@ -60,7 +60,7 @@ pub(crate) fn aes192_cfb_cmd(
run::<_, 24>(
action,
&load_key_object::<AES192Key<CFB>, 24>(key, key_file, "AES-192"),
load_iv::<BLOCK_LEN>(iv, iv_file),
load_iv::<AES_BLOCK_LEN>(iv, iv_file),
output_hex,
);
}
Expand All @@ -76,7 +76,7 @@ pub(crate) fn aes256_cfb_cmd(
run::<_, 32>(
action,
&load_key_object::<AES256Key<CFB>, 32>(key, key_file, "AES-256"),
load_iv::<BLOCK_LEN>(iv, iv_file),
load_iv::<AES_BLOCK_LEN>(iv, iv_file),
output_hex,
);
}
Expand All @@ -85,13 +85,13 @@ pub(crate) fn aes256_cfb_cmd(
fn run<'k, K, const KEY_LEN: usize>(
action: &CipherDirection,
key: &'k K,
iv: Option<[u8; BLOCK_LEN]>,
iv: Option<[u8; AES_BLOCK_LEN]>,
output_hex: bool,
) where
K: SymmetricEncryptionKey<KEY_LEN, BLOCK_LEN, 0>
+ SymmetricDecryptionKey<KEY_LEN, BLOCK_LEN, 0>,
K::Encryptor<'k>: StreamCipherEncryptor<BLOCK_LEN>,
K::Decryptor<'k>: StreamCipherDecryptor<BLOCK_LEN>,
K: SymmetricEncryptionKey<KEY_LEN, AES_BLOCK_LEN, 0>
+ SymmetricDecryptionKey<KEY_LEN, AES_BLOCK_LEN, 0>,
K::Encryptor<'k>: StreamCipherEncryptor<AES_BLOCK_LEN>,
K::Decryptor<'k>: StreamCipherDecryptor<AES_BLOCK_LEN>,
{
run_stream_mode::<K, KEY_LEN, BLOCK_LEN>(action, key, iv, output_hex)
run_stream_mode::<K, KEY_LEN, AES_BLOCK_LEN>(action, key, iv, output_hex)
}
12 changes: 6 additions & 6 deletions cli/src/aes_ecb_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +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_object, start_encryptor,
CipherDirection, decrypt_stream, encrypt_stream, load_key_object, start_encryptor,
};
use bouncycastle::aes::{AES128Key, AES192Key, AES256Key};
use bouncycastle::aes::{AES_BLOCK_LEN, AES128Key, AES192Key, AES256Key};
use bouncycastle::cipher::modes::hazmat::ecb::ECB;
use bouncycastle::cipher::padding::NoPadding;
use bouncycastle::core::symmetric_key::{SymmetricDecryptionKey, SymmetricEncryptionKey};
Expand Down Expand Up @@ -69,16 +69,16 @@ pub(crate) fn aes256_ecb_cmd(
/// the loops write and read no IV, and `NoPadding` makes them refuse a partial final block.
fn run<K, const KEY_LEN: usize>(action: &CipherDirection, key: &K, output_hex: bool)
where
K: SymmetricEncryptionKey<KEY_LEN, 0, BLOCK_LEN>
+ SymmetricDecryptionKey<KEY_LEN, 0, BLOCK_LEN>,
K: SymmetricEncryptionKey<KEY_LEN, 0, AES_BLOCK_LEN>
+ SymmetricDecryptionKey<KEY_LEN, 0, AES_BLOCK_LEN>,
{
match action {
CipherDirection::Encrypt => {
// ECB has no IV: the constructor's RNG draws nothing, and nothing is written.
let (enc, _) = start_encryptor(None, |rng| key.create_encryptor(rng));
encrypt_stream::<_, 0>(enc, None, output_hex, Some(MODE))
encrypt_stream::<_, 0, AES_BLOCK_LEN>(enc, None, output_hex, Some(MODE))
}
CipherDirection::Decrypt => decrypt_stream::<_, 0>(
CipherDirection::Decrypt => decrypt_stream::<_, 0, AES_BLOCK_LEN>(
None,
|no_iv| key.create_decryptor(no_iv),
output_hex,
Expand Down
23 changes: 14 additions & 9 deletions cli/src/helpers/block_mode_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,8 @@ 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 read from stdin per call: 1 KiB = 64 blocks, shared with the stream-mode commands.
pub(crate) const CHUNK_LEN: usize = 64 * BLOCK_LEN;
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 @@ -87,6 +84,13 @@ pub(crate) enum CipherDirection {
Decrypt,
}

/// The one action a decryption-only command offers. A separate enum rather than a flag, so that
/// `tdes2-* encrypt` is rejected by the parser with the list of valid values, not by the command.
#[derive(ValueEnum, Clone, Debug)]
pub(crate) enum DecryptOnlyAction {
Decrypt,
}

/// Loads the key from `--key-file` (raw bytes; hex with `--hex-input`) or `--key` (hex), and
/// checks its length.
///
Expand Down Expand Up @@ -278,7 +282,7 @@ pub(crate) fn start_encryptor<E, const INIT_DATA_LEN: usize>(
/// the ciphertext; see [`start_encryptor`]. `aligned_mode` names a mode whose input must be a
/// whole number of blocks, for the error that reports a partial one (ECB under `NoPadding`); it is
/// `None` for a padded mode, which takes any length.
pub(crate) fn encrypt_stream<E, const INIT_DATA_LEN: usize>(
pub(crate) fn encrypt_stream<E, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize>(
mut enc: E,
generated_iv: Option<&[u8; INIT_DATA_LEN]>,
output_hex: bool,
Expand All @@ -292,6 +296,7 @@ pub(crate) fn encrypt_stream<E, const INIT_DATA_LEN: usize>(
write_bytes_or_hex(iv, output_hex);
}

const { assert!(CHUNK_LEN.is_multiple_of(BLOCK_LEN), "the chunk must be whole blocks") };
let mut out = [0u8; CHUNK_LEN];
let total = stream_chunks(|data| {
// Cannot fail: `out` has room for every whole block of `data` plus the fewer than
Expand All @@ -302,7 +307,7 @@ pub(crate) fn encrypt_stream<E, const INIT_DATA_LEN: usize>(
});

if let Some(mode) = aligned_mode {
check_aligned(mode, total);
check_aligned::<BLOCK_LEN>(mode, total);
}
// The padded final block; under `NoPadding` an aligned message has none, and this reports 0.
let (last, last_len) = enc.do_encrypt_final().unwrap_or_else(|e| {
Expand All @@ -317,7 +322,7 @@ pub(crate) fn encrypt_stream<E, const INIT_DATA_LEN: usize>(
/// Decrypts stdin to stdout through the worker `make_decryptor` gives for the init data (the IV):
/// `iv` if it was supplied, else the first `INIT_DATA_LEN` bytes of input -- one block for CBC,
/// nothing for ECB. `aligned_mode` is as for [`encrypt_stream`].
pub(crate) fn decrypt_stream<D, const INIT_DATA_LEN: usize>(
pub(crate) fn decrypt_stream<D, const INIT_DATA_LEN: usize, const BLOCK_LEN: usize>(
iv: Option<[u8; INIT_DATA_LEN]>,
make_decryptor: impl FnOnce(&[u8; INIT_DATA_LEN]) -> D,
output_hex: bool,
Expand Down Expand Up @@ -352,7 +357,7 @@ pub(crate) fn decrypt_stream<D, const INIT_DATA_LEN: usize>(
});

if let Some(mode) = aligned_mode {
check_aligned(mode, total);
check_aligned::<BLOCK_LEN>(mode, total);
}
// Releases the block held back above, unpadded; under `NoPadding` all of it is data. A
// padded ciphertext that is unaligned or has malformed padding fails here.
Expand Down Expand Up @@ -389,7 +394,7 @@ fn stream_chunks(mut process: impl FnMut(&[u8])) -> usize {
/// Exits with an error if `total` is not a whole number of blocks, for a mode that is defined only
/// on whole blocks and pads nothing. `NoPadding` would also refuse it at `do_*_final`, but without
/// saying how many bytes were left over.
fn check_aligned(mode: &str, total: usize) {
fn check_aligned<const BLOCK_LEN: usize>(mode: &str, total: usize) {
if !total.is_multiple_of(BLOCK_LEN) {
// Everything before the misaligned tail has already been written (bar the one block a
// decryptor holds back until `do_decrypt_final`), and in hex mode is
Expand Down
Loading
Loading