Skip to content

tdes: add bouncycastle-tdes, a constant-time table-free TDEA engine - #139

Open
dghgit wants to merge 19 commits into
feature/key-rework-memoryfrom
feature/tdes
Open

dghgit wants to merge 19 commits into
feature/key-rework-memoryfrom
feature/tdes

Conversation

@dghgit

@dghgit dghgit commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds bouncycastle-tdes: a constant-time, table-free Triple DES (TDEA) engine per SP 800-67r2, on the key objects of #172: TDES3Key<M> (three-key) and the decryption-only two-key TDES2Key<M>, over the engines TDESInternal and TDES2Internal.

Rebased onto feature/key-rework-memory (#172) on 2026-10-24 and ported to its key-object design; the base is now that branch. The review round of PR #140 is applied here in the last two commits: the keys' redundant key-type check is gone, the CLI matches the AES commands and gains a test suite, the Memory Usage table is measured, and the style and nit items are fixed. Wycheproof carries no TDEA vectors, so there is no suite to add.

Description

  • S-box layer is a compile-time-derived ANF Boolean circuit (no lookup tables, no secret-indexed memory access).
  • TDESKey<P, M> mirrors AESKey: the key holds the mode's state, its workers borrow it, and which role pair a key carries is decided by its mode (CBC<Blocks>, CBC<PKCS7>, ECB<..>, CFB, CFB8, CTR<6, 8>). The aliases TDES_CBC<'k, Dir, Pad>, TDES_CFB, TDES_CFB8, TDES_CTR, TDES_ECB and the TDES2_* decryptors name what the keys hand out. Keys and engines implement PartialEq / Eq in constant time over the expanded schedule, as on Key object rework: the traits lose their constructors, every mode gets a key, and workers borrow the key #172.
  • TDESInternal::new (its ElectronicCodeBook impl) rejects non-distinct component keys and any of the 64 weak/semi-weak/possibly-weak DES keys (structural check, not a table), as KeyMaterialError::WeakKey (added in this PR).
  • TDES2Internal sets ElectronicCodeBook::ENCRYPTION_APPROVED = false (added in this PR); the encrypting modes assert it in an inline const, so TDES2Key::create_encryptor fails to compile under every mode, per SP 800-131A Rev 2's "disallowed for encryption, legacy use for decryption". Two compile_fail doctests pin it.
  • CLI subcommands tdes-* / tdes2-* for every mode, through the key objects, with the aes*-* contract: tdes-cbc and tdes2-cbc are CBC<PKCS7> at any length, every command with an IV or nonce takes --iv/--iv-file (in band otherwise), and every command takes -d/--direction; the tdes2-* commands accept only decrypt there, so encrypt is refused by the parser. ECB stays unpadded with no IV, as aes*-ecb is.

Scope and Risk

New crate only; the core / cipher / cli changes it depends on (ENCRYPTION_APPROVED, the mode-side const assert on it, KeyMaterialError::WeakKey, generic block length in the CLI plumbing, DecryptOnlyAction) are additive and covered by existing suites. No behavioural change to AES or other existing ciphers.

Validation

  • NIST CAVP T{E,C}{CB,BC,FB64,FB8}MMT{2,3} vectors (bc-test-data), both key sizes, both directions: CBC and CFB through the keys, ECB through the engines.
  • Shared frameworks over every mode key: TestFrameworkFromKeyMaterial, TestFrameworkBlockCipher, TestFrameworkSymmetricCipher, TestFrameworkStreamCipher, TestFrameworkElectronicCodeBook.
  • cargo test --workspace: all passing; cargo test --workspace --all-targets --no-run builds the benches; cargo +nightly fmt --check clean.
  • cli/tests/tdes_cli_tests.rs pins COUNT = 0 of TCBCMMT3, TCFB64MMT3, TCFB8MMT3 and TECBMMT3 through the binary in both directions (encryption under --iv), and the two-key [DECRYPT] vectors through the tdes2-* commands, read from bc-test-data at test time.
  • cargo mutants -p bouncycastle-tdes after the review fixes: 500 mutants, 422 caught, 41 unviable, 33 missed (31 bit-permutation XOR/OR equivalences in spread, permute_p, lookup, rotl28, pc1, expand and is_weak, and two loop-bound </<= mutants in the compile-time anf_constants), 4 timeouts (the compile-time ANF constant derivation).

AI Usage Statement

  • Yes, non-trivial code changes were generated by AI

Assisted-by: Claude:claude-sonnet-5
Assisted-by: Claude:claude-mythos-5-1

🤖 Generated with Claude Code

dghgit added a commit that referenced this pull request Sep 19, 2026
mem_usage_benches/src/lib.rs makes every bench source a module of a lib
target, so its //! header is rustdoc'd and an indented block compiles
as a Rust doctest by default; the valgrind/ms_print shell one-liners
here weren't fenced, so `cargo test --workspace` failed to compile
them as Rust (CI on PR #139). Fenced as ```text like the other bench
binaries, per CLAUDE.md's note on this exact pitfall.

Assisted-by: Claude:claude-sonnet-5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dghgit added a commit that referenced this pull request Sep 19, 2026
Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every
bench source a module of a lib target, so its //! header is rustdoc'd
and an indented block compiles as a Rust doctest by default. Fenced
as ```text like the other bench binaries.

Assisted-by: Claude:claude-sonnet-5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@hubot
hubot deleted the feature/tdes branch September 19, 2026 01:02
@dghgit
dghgit restored the feature/tdes branch September 19, 2026 04:26
@ounsworth

ounsworth commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

We had agreed not to add more ciphers until we get through reviewing and locking in the overall architecture via the AES work. Closing this PR for now.

@ounsworth ounsworth closed this Sep 19, 2026
@dghgit dghgit reopened this Sep 19, 2026
@npajkovsky

Copy link
Copy Markdown
Collaborator

Why on earth would anybody want 3DES?

NIST deprecated 3DES for new use via SP 800-131A Rev. 2 (disallowed after December 31, 2023) and withdrew SP 800-67 Rev. 2 effective January 1, 2024.

@dghgit

dghgit commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor Author

Fair question, even though, as much as I hate to say it, I could point you at people still using DES, some of who should definitely know better... We still support it in BC-FIPS as well, as far as I can tell it's mainly used for dealing with legacy encrypted data these days. In this case this one is simply here as it's on the OpenSSL provider list, so it's one of the algorithms they'll want to invoke (at least for a while).

@hubot
hubot deleted the feature/tdes branch September 28, 2026 15:21
@hubot
hubot deleted the branch feature/key-rework-memory September 28, 2026 15:21
@hubot
hubot restored the feature/tdes branch September 28, 2026 15:45
@dghgit
dghgit changed the base branch from feature/xof-cshake to feature/simple-ciphers September 28, 2026 15:49
roy-basmacier pushed a commit to roy-basmacier/bc-rust that referenced this pull request Sep 30, 2026
Implements the KeyWrapper / KeyUnwrapper traits from the previous commit
for the two 128-bit-block key-wrap algorithms of NIST SP 800-38F, KW
(Sec 6.2; RFC 3394) and KWP (Sec 6.3; RFC 5649), generically over any
ElectronicCodeBook permutation, and publishes the AES instantiations.

modes:
- kw.rs: Kw<P, KEK_LEN>. The wrapping function W and its inverse
  (Algorithms 1 and 2) run in place on the caller's buffer, visiting the
  registers in the order the spec's shift register would present them,
  which RFC 3394 Sec 2.2.1 gives as the equivalent index-based form; the
  one block of scratch is a Secret. KW-AE/AD are Algorithms 3 and 4.
- kwp.rs: Kwp<P, KEK_LEN>. KWP-AE/AD are Algorithms 5 and 6, including
  the single-block path for plaintexts of at most one semiblock. The ICV,
  length-field and padding checks of Algorithm 6 steps 4, 7 and 8 are all
  made, combined bitwise, and reported as the one DecryptionFailed.
- The fixed-length API rejects invalid (KEY_LEN, CT_LEN) pairs at compile
  time via const assertions on the Table 1 limits; the run-time API
  returns InvalidInputLength outside them. Output buffers are zeroized on
  every failure. TKW (TDEA) is not included: it needs a 64-bit-block
  engine (PR bcgit#139).

aes:
- kw.rs: AES_KW_128/192/256 and AES_KWP_128/192/256 aliases, with the
  RFC 3394 Sec 4.1 and RFC 5649 7-octet vectors as doctests. No OIDs yet,
  in line with the other AES mode aliases.

tests:
- modes/tests/kw_tests.rs: all six RFC 3394 Sec 4 vectors and both
  RFC 5649 Sec 6 examples via TestFrameworkKeyWrap::test_kat, the shared
  framework for every AES key length at 26 payload lengths (every padding
  length of the single-block case included), and pins for the properties
  that distinguish KW, KWP and a raw block encryption.
- modes/tests/acvp_kw_tests.rs: the NIST ACVP-AES-KW and ACVP-AES-KWP
  sets from bc-test-data, 1800 cases each, through both APIs, including
  the 171 + 178 forged ciphertexts that must be rejected.
- aes/tests/kw_alias_tests.rs: the aliases name the right permutation and
  pass the framework.

benches: KW/KWP wrap and unwrap of a 256-bit key, and KWP over 4 KiB.

cli: aes{128,192,256}-kw and -kwp with wrap|unwrap, reading all of stdin
(key wrap is one-shot), sharing key loading with the other AES commands;
cli/tests/aes_kw_cli_tests.rs replays the RFC vectors in both directions
and pins the length rules, the fail-closed unwrap and the help text.

Assisted-by: Claude:claude-fable-5-1
dghgit and others added 14 commits October 24, 2026 21:34
…on says to avoid -- a weak or semi-weak key, or a bundle whose component keys are not distinct
…and CHUNK_LEN is a fixed 1 KiB, so the shared encrypt/decrypt loops serve block ciphers other than AES
…permutation kept for decrypting legacy data only, so a mode can refuse to encrypt with it at compile time
…s P::ENCRYPTION_APPROVED in an inline const, so building an encryptor over a decryption-only permutation is a compile error
…SP 800-67r2) whose S-box layer is an ANF circuit derived from the spec tables at compile time -- three-key TDES with TDES_* mode aliases, decryption-only two-key TDES2Key with TDES2_* decryptors, tdes-*/tdes2-* CLI subcommands, CAVP MMT vectors from bc-test-data
summary.md duplicated the crate docs' content and added a "Where the
plan changed" section citing decisions from an internal design
session that isn't part of this repo, plus several bc-java class-name
references not needed once the doc itself is gone. The crate docs in
src/lib.rs already cover the design, verification and the decisions a
reviewer would want to revisit.

No behavioural change; cargo fmt --check is clean.

Assisted-by: Claude:claude-sonnet-5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mem_usage_benches/src/lib.rs makes every bench source a module of a lib
target, so its //! header is rustdoc'd and an indented block compiles
as a Rust doctest by default; the valgrind/ms_print shell one-liners
here weren't fenced, so `cargo test --workspace` failed to compile
them as Rust (CI on PR #139). Fenced as ```text like the other bench
binaries, per CLAUDE.md's note on this exact pitfall.

Assisted-by: Claude:claude-sonnet-5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ptor::encrypt/encrypt_rng now returning (usize, [u8; INIT_DATA_LEN]) instead of just [u8; INIT_DATA_LEN] -- TDES_CFB, TDES_CFB8 (twice) and TDES_CTR each destructure the tuple instead of binding the old array-only return; no other TDES code is affected since these files are only type aliases over the generic Cfb/Cfb8/Ctr in crypto/modes, not their own trait impls

Assisted-by: Claude:claude-sonnet-5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…absorbed through its merges (SymmetricCipher/BlockCipherPadding renames, the ElectronicCodeBook batch methods, AES*Internal, core::security_strength, PaddedBlockCipher* adapters and the rest), restored in one commit after the linear rebase dropped those merges; the tree is identical to merging d1994a9 with 2d4d038

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…R and TDES2_* examples take the renamed in-place one-shots (encrypt_in_place/decrypt_in_place) and import the SymmetricCipher* supertraits their do_*_init calls now come from; and a compile_fail example pins that Ctr<TDES2Key, Encrypting, ..> is still refused, now through KeyStream::ENCRYPTION_APPROVED (which CtrKeyStream takes from its permutation, carried in the merge) since CTR's encrypting constructor moved into core's StreamCipher adapter

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TDES, the decryption-only TDES2Key and the TDES_ECB / TDES2_ECB aliases move to
bouncycastle_tdes::hazmat, with BLOCK_LEN, KEY_LEN and KEY_LEN_2KEY defined at the crate root; the
merge ahead of this ported ENCRYPTION_APPROVED onto the moved trait and CtrKeyStream files. No logic
change, no mutation run owed.

Assisted-by: Claude Code:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…Select

Follows aes 160ac18; the crate's own PaddedMode copy goes with it. Type aliases only, no
behaviour change, no mutation run owed.

Assisted-by: Claude Code:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…er paths for the modes and padding, core::hazmat::ElectronicCodeBook

The linear replay of this branch onto feature/key-rework-memory drops its
merges from feature/simple-ciphers, and the move of bouncycastle-modes and
bouncycastle-padding into bouncycastle-cipher, with Direction and
ElectronicCodeBook's new homes, was resolved only in those merges. This is
the tdes crate, its CLI commands and its memory bench exactly as the branch
tip 5197229 had them.

Assisted-by: Claude:claude-mythos-5-1
…s, the engines become TDESInternal / TDES2Internal

The engines are renamed for what they are under the key objects of
feature/key-rework-memory: TDES -> TDESInternal and TDES2Key -> TDES2Internal,
each Clone, implementing ElectronicCodeBook (new, ENCRYPTION_APPROVED) and
BlockPermutation (the block methods) separately, and ModeNames for the five
modes offered (the AEAD and MAC names are empty).

TDESKey<P: TDESEngine, M: Mode<P>> mirrors AESKey: the key holds the mode's
key state, FromKeyMaterial<24> / <16> build it per engine, and the six role
impls (symmetric, block and ECB, each direction) forward to the mode traits.
TDES3Key<M> and TDES2Key<M> name the engines. A TDES2Key carries the same
roles; its create_encryptor fails to compile through the const assert the
modes make on ENCRYPTION_APPROVED, which the compile_fail doctests on
TDES2Internal now express at the key.

The aliases take the key-state lifetime (TDES_CBC<'k, Dir, Pad>, ...), and
the docs and examples build a key and call encrypt / decrypt on it. CTR has
no usable default for an 8-byte block, so its key is TDES3Key<CTR<6, 8>>.

Tests: cbc_alias_tests and ecb_alias_tests become padded_alias_tests and
alg_name_tests on the key; mode_key_tests runs the FromKeyMaterial, block,
symmetric and stream frameworks over every mode and checks the two-key
decryptors against the raw permutation; cavp_mmt_tests drives the CBC and
CFB vectors through the keys and the ECB ones through the engines.

CLI: the ten tdes commands load a TDES3Key / TDES2Key under NoPadding and
keep their contract -- whole blocks, the IV in band, no --iv -- and the
tdes2-* commands take DecryptOnlyAction. Checked against the TECBMMT3,
TCBCMMT2, TECBMMT2, TCFB64MMT2 and TCFB8MMT2 COUNT = 0 vectors.

Assisted-by: Claude:claude-mythos-5-1
@dghgit
dghgit changed the base branch from feature/simple-ciphers to feature/key-rework-memory October 24, 2026 11:03
dghgit added a commit that referenced this pull request Oct 24, 2026
Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every
bench source a module of a lib target, so its //! header is rustdoc'd
and an indented block compiles as a Rust doctest by default. Fenced
as ```text like the other bench binaries.

Assisted-by: Claude:claude-sonnet-5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
hubot pushed a commit that referenced this pull request Oct 24, 2026
Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every
bench source a module of a lib target, so its //! header is rustdoc'd
and an indented block compiles as a Rust doctest by default. Fenced
as ```text like the other bench binaries.

Assisted-by: Claude:claude-sonnet-5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dghgit added a commit that referenced this pull request Oct 24, 2026
…```text

Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every
bench source a module of a lib target, so its //! header is rustdoc'd
and an indented block compiles as a Rust doctest by default. Fenced
as ```text like the other bench binaries.

Assisted-by: Claude:claude-sonnet-5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
hubot pushed a commit that referenced this pull request Oct 24, 2026
…```text

Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every
bench source a module of a lib target, so its //! header is rustdoc'd
and an indented block compiles as a Rust doctest by default. Fenced
as ```text like the other bench binaries.

Assisted-by: Claude:claude-sonnet-5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dghgit added a commit that referenced this pull request Oct 25, 2026
Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every
bench source a module of a lib target, so its //! header is rustdoc'd
and an indented block compiles as a Rust doctest by default. Fenced
as ```text like the other bench binaries.

Assisted-by: Claude:claude-sonnet-5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
hubot pushed a commit that referenced this pull request Oct 25, 2026
Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every
bench source a module of a lib target, so its //! header is rustdoc'd
and an indented block compiles as a Rust doctest by default. Fenced
as ```text like the other bench binaries.

Assisted-by: Claude:claude-sonnet-5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lifted onto this branch from feature/key-rework-memory (5607ba9), where AESKey
and AESInternal gained the same: the two engines compares the schedule through Secret's
byte-wise equality, which inspects every byte so the time taken does not say
where two differ, and a key is equal when its mode state is, bounded on the
state rather than on Mode::KeyState. The key objects never hand the material
back, so this is the only way two keys can be compared.

mode_key_tests: equality follows the material for a clone, the same bytes and
a single flipped bit, and a flipped parity bit, which PC-1 drops, leaves a bundle equal.

Assisted-by: Claude:claude-mythos-5-1
dghgit pushed a commit to roy-basmacier/bc-rust that referenced this pull request Oct 26, 2026
Implements the KeyWrapper / KeyUnwrapper traits from the previous commit
for the two 128-bit-block key-wrap algorithms of NIST SP 800-38F, KW
(Sec 6.2; RFC 3394) and KWP (Sec 6.3; RFC 5649), generically over any
ElectronicCodeBook permutation, and publishes the AES instantiations.

modes:
- kw.rs: Kw<P, KEK_LEN>. The wrapping function W and its inverse
  (Algorithms 1 and 2) run in place on the caller's buffer, visiting the
  registers in the order the spec's shift register would present them,
  which RFC 3394 Sec 2.2.1 gives as the equivalent index-based form; the
  one block of scratch is a Secret. KW-AE/AD are Algorithms 3 and 4.
- kwp.rs: Kwp<P, KEK_LEN>. KWP-AE/AD are Algorithms 5 and 6, including
  the single-block path for plaintexts of at most one semiblock. The ICV,
  length-field and padding checks of Algorithm 6 steps 4, 7 and 8 are all
  made, combined bitwise, and reported as the one DecryptionFailed.
- The fixed-length API rejects invalid (KEY_LEN, CT_LEN) pairs at compile
  time via const assertions on the Table 1 limits; the run-time API
  returns InvalidInputLength outside them. Output buffers are zeroized on
  every failure. TKW (TDEA) is not included: it needs a 64-bit-block
  engine (PR bcgit#139).

aes:
- kw.rs: AES_KW_128/192/256 and AES_KWP_128/192/256 aliases, with the
  RFC 3394 Sec 4.1 and RFC 5649 7-octet vectors as doctests. No OIDs yet,
  in line with the other AES mode aliases.

tests:
- modes/tests/kw_tests.rs: all six RFC 3394 Sec 4 vectors and both
  RFC 5649 Sec 6 examples via TestFrameworkKeyWrap::test_kat, the shared
  framework for every AES key length at 26 payload lengths (every padding
  length of the single-block case included), and pins for the properties
  that distinguish KW, KWP and a raw block encryption.
- modes/tests/acvp_kw_tests.rs: the NIST ACVP-AES-KW and ACVP-AES-KWP
  sets from bc-test-data, 1800 cases each, through both APIs, including
  the 171 + 178 forged ciphertexts that must be rejected.
- aes/tests/kw_alias_tests.rs: the aliases name the right permutation and
  pass the framework.

benches: KW/KWP wrap and unwrap of a 256-bit key, and KWP over 4 KiB.

cli: aes{128,192,256}-kw and -kwp with wrap|unwrap, reading all of stdin
(key wrap is one-shot), sharing key loading with the other AES commands;
cli/tests/aes_kw_cli_tests.rs replays the RFC vectors in both directions
and pins the length rules, the fail-closed unwrap and the help text.

Assisted-by: Claude:claude-fable-5-1
…ack table is measured, and the doc nits

The same round as PR #140's review, applied here. TDESKey's two constructors
ran check_key_type ahead of the engine's own validation, which is the same
check for every mode that has a role; it goes (a scoped `cargo mutants` on
key.rs is 0 missed; the crate lists 500 mutants). The constructors' docs say
the checks are the engine's.

The Security Considerations heading takes the style guide's form and the
anchor follows it. The Memory Usage section tabulates the measured stack per
entry point and engine: bench_tdes_mem_usage takes the AES bench's
measure-closure shape for new and the block entry points of both engines, the
two-key engine's decrypting ones only, under the TECBMMT3 COUNT = 0 bundle,
and the figures are the closure frames from the release build's frame-layout
remarks plus the return address.

The unit tests in hazmat/tdes.rs and hazmat/tdes2.rs that tested public
behaviour only (the engine sizes, the algorithm names and strengths, the
ENCRYPTION_APPROVED gate) move to alg_name_tests; the split/join, parity and
schedule pins stay. Nits: the manifest comment, the reference-style link in
key.rs that could not fit, and every doc or comment line past 100 columns.

Assisted-by: Claude:claude-mythos-5-1
… and -d, with a test suite

As for sm4 and camellia: tdes-cbc was CBC<NoPadding> with no --iv while
aes*-cbc on this base is PKCS7 at any length with --iv/--iv-file, and the
two-key commands took their action positionally. tdes-cbc and tdes2-cbc are
now CBC<PKCS7> over the same block-mode helpers; the stream commands, three-
and two-key, take --iv/--iv-file through load_iv (8 bytes, or the 6-byte CTR
nonce); every command takes the direction as -d/--direction, the two-key ones
with `decrypt` as the one value the parser accepts; tdes-ecb and tdes2-ecb
keep NoPadding and no IV, as aes*-ecb does.

The tdes commands had no CLI tests. tdes_cli_tests.rs pins COUNT = 0 of the
CAVP MMT files the crate's suite uses, read from bc-test-data at test time and
skipped with a warning without it: tdes-cbc, -cfb, -cfb8 and -ecb encrypt
under --iv reproduce TCBCMMT3 (plus the PKCS7 block, computed from the
engine), TCFB64MMT3, TCFB8MMT3 and TECBMMT3, and decrypt takes them back; the
tdes2-* commands decrypt the two-key [DECRYPT] vectors. The contract tests
cover the round trips of every mode, CBC's padding against ECB's alignment
rule, a supplied CTR nonce held to the library, -x, the parser's refusal of
`tdes2-* -d encrypt`, and the bundle-length, repeated-component and weak-key
rejections.

Assisted-by: Claude:claude-mythos-5-1
@dghgit

dghgit commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

The review round of PR #140 applied here, in 5f367891 and e7b7e68e (branch head e7b7e68e):

  1. Key roles. The keys never had the MAC-key path, but ran a check_key_type ahead of the engine's own validation, which is the same check for every mode that has a role; it is gone. A scoped cargo mutants on key.rs is 0 missed. Wycheproof carries no TDEA vectors, so there is no suite to add.
  2. CLI. tdes-cbc and tdes2-cbc are CBC<PKCS7> at any length; every command with an IV or nonce takes --iv/--iv-file; every command takes -d/--direction, the tdes2-* ones with decrypt as the only value the parser accepts; ECB stays unpadded with no IV, as aes*-ecb is. The commands had no CLI tests: cli/tests/tdes_cli_tests.rs pins COUNT = 0 of TCBCMMT3 (plus the PKCS7 block, computed from the engine), TCFB64MMT3, TCFB8MMT3 and TECBMMT3 through the binary in both directions, encryption under --iv, and the two-key [DECRYPT] vectors through the tdes2-* commands, read from bc-test-data at test time; plus the round trips, padding against alignment, a supplied CTR nonce held to the library, the parser's refusal of tdes2-* -d encrypt, and the bundle rejections.
  3. Heading is # 🚨 Security Considerations 🚨, anchor updated.
  4. Memory Usage tabulates measured per-entry-point stack for both engines (new 800 B / 544 B, one-block 48 B, two-block 80 B, four-block 112 B; the two-key engine's decrypting entry points only), by the same method and toolchain as the SM4 and Camellia tables.
  5. The public-behaviour unit tests in both engines (sizes, names and strengths, the ENCRYPTION_APPROVED gate) moved to alg_name_tests.rs; the split/join, parity and schedule pins stay.
    6–13. Manifest comment, the reference-style link in key.rs, and every doc line within 100 columns.
  6. cargo mutants -p bouncycastle-tdes after the changes: 500 mutants, 422 caught, 41 unviable, 33 missed (the bit-permutation XOR/OR equivalences and two loop bounds in the compile-time ANF derivation), 4 timeouts (that derivation). PR description updated.

cargo test --workspace, --all-targets --no-run, rustdoc with warnings denied and rustfmt are clean.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants