Skip to content

Add Padding trait and bouncycastle-padding crate (PKCS7, Padded{En,De}cryptor) - #97

Open
dghgit wants to merge 1 commit into
feature/block-cipher-to-multifrom
feature/block-cipher-padding
Open

Add Padding trait and bouncycastle-padding crate (PKCS7, Padded{En,De}cryptor)#97
dghgit wants to merge 1 commit into
feature/block-cipher-to-multifrom
feature/block-cipher-padding

Conversation

@dghgit

@dghgitdghgit commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Note this also includes a mock cipher to test with. We should replace that with AES when the actual implementation arrives.

Claude Summary follows:

Stacked on #96 (feature/block-cipher-to-multi); will retarget to release/0.1.3alpha once that merges. Adds the padding layer that the block-aligned BlockCipherEncryptor / BlockCipherDecryptor traits deliberately leave out.

core

  • Padding<const BLOCK_LEN> trait: in-place pad(block, data_len) and constant-time unpad(block) -> data_len.
  • PaddingError { DataLengthTooLong, InvalidPadding }, plus a PaddingError variant (with From) on SymmetricCipherError.

crypto/padding (new crate bouncycastle-padding, no_std, no unsafe)

  • PKCS7 — RFC 5652 §6.3 padding for any block length 1..=255, enforced at compile time via an inline const assert. unpad visits every byte with Condition<i64> mask arithmetic from bouncycastle-utils and has a single public decision point, so neither timing nor error detail leaks a padding oracle. Spec text was taken from the downloaded RFC, not recall.
  • PaddedEncryptor<E, P> / PaddedDecryptor<D, P> — adapt any BlockCipherEncryptor / BlockCipherDecryptor to arbitrary-length data. Streaming do_update_out / do_final(self) with exact update_out_len helpers, and one-shot encrypt_out / decrypt_out. Whole blocks go to the inner cipher in groups via as_chunks (no unwraps, no copies); the buffered partial plaintext block lives in a Secret. The decryptor withholds one complete block until do_final, since only the last block carries padding.
  • Tests: PKCS7 round-trips for every length at block sizes 1/8/16/255 checked literally against the RFC rule, the RFC's worked strings, and malformed-padding rejection at every byte position. Adapter tests drive the code with a toy XOR-CBC cipher (the workspace's first implementor of the new traits) — every length, ten chunkings in both directions, the one-block lag, tampering, malformed lengths, buffer sizing, wrong KeyType — and run it through TestFrameworkBlockCipher.
  • Criterion bench; registered in the workspace and re-exported as bouncycastle::padding.

core-test-framework fix

Running the framework against the toy cipher exposed a latent bug: the security-strength loops in the symmetric/block/AEAD suites unwrapped set_security_strength, whose comment claimed do_hazardous_operations bypasses the key-length guard. It doesn't (key_material.rs), so any key under 32 bytes panicked the suite. The loops now skip strengths the key cannot carry and the comment is corrected.

Not included

No CLI subcommand — there is no real block cipher to wire it to yet; it belongs with the first AES/CBC crate.

Verification

cargo test --workspace, cargo doc (no broken links), cargo fmt --check, cargo bench -p bouncycastle-padding --no-run all clean. quality_stats.sh ./crypto/padding: 322 code / 122 doc / 347 test lines.

…}cryptor)
core:
- Add `Padding<const BLOCK_LEN>` trait with in-place `pad(block, data_len)`
and constant-time `unpad(block) -> data_len`.
- Add `PaddingError { DataLengthTooLong, InvalidPadding }` and a
`PaddingError` variant (with From) on `SymmetricCipherError`.
crypto/padding (new crate, no_std, no unsafe):
- `PKCS7`: RFC 5652 §6.3 padding for any block length 1..=255, enforced
at compile time. `unpad` examines every byte with `Condition<i64>`
mask arithmetic and has a single public decision point, so it does
not leak a padding oracle through timing or error detail.
- `PaddedEncryptor<E, P>` / `PaddedDecryptor<D, P>`: adapt a
block-aligned BlockCipherEncryptor / BlockCipherDecryptor to
arbitrary-length data. Streaming `do_update_out` / `do_final(self)`
plus one-shot `encrypt_out` / `decrypt_out`, with exact output-length
helpers. The buffered partial plaintext block is held in a `Secret`.
The decryptor withholds one complete block until `do_final`, since
only the last block carries padding.
- Tests derived from the RFC 5652 rule for PKCS7; adapter tests drive
the code with a toy XOR-CBC cipher implementing the new block cipher
traits, covering every length, ten chunkings in both directions,
tampering, malformed lengths, and buffer sizing. Criterion bench.
- Registered in the workspace and re-exported as `bouncycastle::padding`.
core-test-framework:
- Fix the security-strength loops in the symmetric/block/AEAD suites:
`set_security_strength` refuses strengths the key length cannot
support even inside `do_hazardous_operations`, so the previous
unwrap panicked for any key shorter than 32 bytes. Skip those
strengths instead and correct the misleading comment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto 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.

1 participant

@dghgit