Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -11,6 +11,7 @@ version = "0.1.3"
bouncycastle = { path = "./" }
bouncycastle-aes-lowmemory = { path = "./crypto/aes-lowmemory" }
bouncycastle-base64 = { path = "./crypto/base64" }
bouncycastle-modes = { path = "./crypto/modes" }
bouncycastle-core = { path = "crypto/core" }
bouncycastle-core-test-framework = { path = "./crypto/core-test-framework" }
bouncycastle-factory = { path = "./crypto/factory" }
Expand Down Expand Up @@ -53,6 +54,7 @@ bouncycastle-mldsa.workspace = true
bouncycastle-mldsa-lowmemory.workspace = true
bouncycastle-mlkem.workspace = true
bouncycastle-mlkem-lowmemory.workspace = true
bouncycastle-modes.workspace = true
bouncycastle-rng.workspace = true
bouncycastle-sha2.workspace = true
bouncycastle-sha3.workspace = true
77 changes: 77 additions & 0 deletions alpha_0.1.3_release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,83 @@ permutation (NIST FIPS 197), re-exported from the umbrella crate.
only offer ECB, and those are mode-of-operation concerns. `Algorithm` is implemented (name and security
strength); per-mode OIDs and the `BlockCipherEncryptor` / `BlockCipherDecryptor` impls belong to the mode crates.

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<P, Dir, KEY_LEN, BLOCK_LEN>` over any `BlockPermutation`, 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.
* **The IV is generated, never accepted.** SP 800-38A Sec 5.3 requires the CBC IV to be
*unpredictable*, not merely unique, so `do_encrypt_init` draws one from the library's default
OS-backed DRBG (Appendix C's second recommended method) and returns it; there is no API for
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
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.
* Strictly block-aligned, as Sec 5.2 requires of CBC. Arbitrary-length data needs a padding layer,
which does not exist in this workspace yet; when it lands, CBC gets it by being wrapped.
* Verified against all six SP 800-38A Appendix F.2 vectors (CBC-AES128/192/256, Encrypt and
Decrypt), each checked in one call, one block at a time, in a `3 + 1` grouping that exercises the
pair remainder, and through the `_out` variant. Appendix D error propagation is tested
exhaustively for the IV (every one of the 128 bit positions flips exactly its own bit of P1) and
for a ciphertext bit error (affects exactly two blocks).
* Also verified against the **2150 NIST ACVP `ACVP-AES-CBC` AFT cases** from `bc-test-data` (all
three key lengths, both directions, 60 of them spanning 2-10 blocks). Each case is run twice --
block by block, and in pairs with a one-block remainder -- so the `decrypt_blocks2` path is
exercised against real vectors, not only against the toy permutation. Unlike the ECB response
file, the CBC one carries only the answer against a `tcId`, so the request and response files are
joined; the 6 MCT groups are skipped and the count reported. These vectors were already in
`bc-test-data` and previously unused.
* No CFB yet -- see the crate docs' "Not yet implemented".

`cli`: three new subcommands, `aes128-cbc`, `aes192-cbc` and `aes256-cbc`, each taking `encrypt` or
`decrypt` and streaming stdin to stdout in 1 KiB chunks.

* Key from `--key` (hex) or `--key-file` (binary or hex), with the usual note that secrets on the
command line end up in shell history. The key length must match the variant exactly.
* **The IV travels in the ciphertext**: since there is no API for supplying one, `encrypt` writes
the generated IV as the first 16 bytes of its output and `decrypt` reads it back from the first
16 bytes of its input, so `encrypt | decrypt` composes with no `--iv` flag anywhere. The IV need
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`.
* 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.
* `cli/tests/aes_cbc_cli_tests.rs` (16 tests) drives the built binary as a subprocess via
`CARGO_BIN_EXE_bc-rust`, so all of the above is asserted by `cargo test` rather than by hand:
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<KEY_LEN, BLOCK_LEN>` 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).

Testing:

* `core-test-framework` gains `TestFrameworkBlockPermutation`, 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.
* Fixed a latent bug in `TestFrameworkBlockCipher`: it unwrapped `set_security_strength` at all
five strengths, which a key shorter than 32 bytes cannot carry, so the framework panicked for
any 16- or 24-byte key. It now skips the strengths the key length cannot hold. The bug was
invisible until now because nothing in the workspace implemented the block cipher traits. The
identical loop in `TestFrameworkSymmetricCipher` and `TestFrameworkAEADCipher` is still unfixed;
both still have no implementors, so it stays latent. (`TestFrameworkStreamCipher` has no
security-strength handling at all and is unaffected.)

## Minor features / bug fixes

* bug fixes to the way SHA3/SHAKE handled absorbing and squeezing a partial final byte.
Expand Down
Loading
Loading