Skip to content

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

Open
dghgit wants to merge 17 commits into
feature/simple-ciphersfrom
feature/tdes
Open

dghgit wants to merge 17 commits into
feature/simple-ciphersfrom
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, with three-key TDES and decryption-only two-key TDES2Key.

Description

  • S-box layer is a compile-time-derived ANF Boolean circuit (no lookup tables, no secret-indexed memory access).
  • TDES::new rejects non-distinct component keys and any of the 64 weak/semi-weak/possibly-weak DES keys (structural check, not a table).
  • TDES2Key sets ElectronicCodeBook::ENCRYPTION_APPROVED = false (added in this PR) so its Encrypting mode aliases fail to compile, per SP 800-131A Rev 2's "disallowed for encryption, legacy use for decryption".
  • CLI subcommands tdes-*/tdes2-* for every mode.

Scope and Risk

New crate only; the core/modes/cli changes it depends on (ENCRYPTION_APPROVED, KeyMaterialError::WeakKey, generic block length in the CLI plumbing) 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.
  • cargo test -p bouncycastle-tdes: all passing.
  • cargo mutants -p bouncycastle-tdes: 467 mutants, 407 caught, 21 unviable, 33 missed (bit-permutation XOR/OR equivalences), 6 timeouts (compile-time ANF constant derivation).

AI Usage Statement

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

Assisted-by: Claude:claude-sonnet-5

🤖 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).

dghgit and others added 9 commits September 28, 2026 20:28
…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>
@hubot
hubot deleted the feature/tdes branch September 28, 2026 15:21
@hubot
hubot deleted the branch feature/simple-ciphers 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
dghgit and others added 3 commits September 29, 2026 10:25
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>
# Conflicts:
#	cli/src/aes_cbc_cmd.rs
#	cli/src/aes_ccm_cmd.rs
#	cli/src/aes_cfb8_cmd.rs
#	cli/src/aes_cfb_cmd.rs
#	cli/src/aes_ctr_cmd.rs
#	cli/src/aes_ecb_cmd.rs
#	cli/src/helpers/block_mode_helpers.rs
#	cli/src/main.rs
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 3 commits October 1, 2026 15:26
# Conflicts:
#	cli/src/aes_cbc_cmd.rs
#	cli/src/aes_ccm_cmd.rs
#	cli/src/aes_cfb8_cmd.rs
#	cli/src/aes_cfb_cmd.rs
#	cli/src/aes_ctr_cmd.rs
#	cli/src/aes_ecb_cmd.rs
#	cli/src/main.rs
#	crypto/core/src/traits.rs
#	crypto/modes/src/ctr.rs
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>
dghgit and others added 2 commits October 1, 2026 16:20
…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>
Brings in 8dff255 (bouncycastle-modes and bouncycastle-padding folded into the new
bouncycastle-cipher crate) and a27d9e6 (StreamCipher and the direction markers moved out of
core), so the TDES crate, its CLI commands and tests are rewritten onto the new paths:
bouncycastle_cipher::modes::*, bouncycastle_cipher::padding::*, and
bouncycastle_cipher::{Direction, Encrypting, Decrypting}. Import rewrite only, no behaviour
change.
The ENCRYPTION_APPROVED gates followed the mode files into the new crate under git's rename
detection; only the core KeyStream doc's link to StreamCipher, now in another crate, is
rewritten as plain text.

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

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