tc_block_modes 0.1.0

ECB, CBC, CFB, OFB and CTR block cipher modes of operation for engines implementing the tc_block_cipher traits.
Documentation

tc_block_modes

crates.io docs.rs CI license rustc

ECB, CBC, CFB, OFB and CTR modes of operation for block ciphers. Every mode wraps an engine that implements the tc_block_cipher traits, such as tc_aes, and is itself a block cipher: it is initialized with a key and an IV and transforms one segment per call. Ported from Bouncy Castle C#.

The crate is no_std, needs no allocator by default and contains no unsafe code. It depends on tc_block_cipher and tc_zeroize.

Requires Rust 1.85 or later (edition 2024), with or without the alloc feature.

Types

  • EcbBlockCipher — ECB, every block on its own.
  • FixedCbcBlockCipher, CbcBlockCipher (alloc) — CBC over whole blocks.
  • FixedCfbBlockCipher, CfbBlockCipher (alloc) — CFB with a segment from one byte up to one block.
  • FixedOfbBlockCipher, OfbBlockCipher (alloc) — OFB with a segment from one byte up to one block.
  • FixedCtrBlockCipher, CtrBlockCipher (alloc) — CTR, which Bouncy Castle calls SIC (SicBlockCipher).
  • KeyWithIvRef, KeyWithIvFixed, KeyWithIvOwned (alloc) — key and IV parameters that borrow, or own and wipe, their bytes.
  • BlockModeError, BlockModeInitError — processing and initialization errors that wrap the engine's.

The Fixed* forms take the block size, and the CFB or OFB segment size in bytes, as const generics and keep their state inline; initialization rejects an engine whose block size differs. The runtime-sized forms size their state from the engine and take the CFB or OFB feedback size in bits.

CBC takes an IV of exactly one block. CFB and OFB take up to one block and right-align a shorter IV over zeros, as in FIPS 81, so an empty IV is all zeros. CTR fills the leading bytes of its counter block with the IV and may leave at most min(8, block / 2) bytes of counter, so AES takes an IV of 8 to 16 bytes. A rejected init returns BlockModeInitError and keeps the previous IV and chaining state.

process_block transforms the first segment of the input, returns its length and leaves any longer tail of the output untouched; it returns BlockModeError::NotInitialised before init and BufferTooShort for a buffer shorter than a segment, without changing the mode. There is no padding: ECB and CBC take whole blocks, while CFB, OFB and CTR finish a partial segment through a segment-sized buffer. Display writes the engine's name and the mode, such as "AES/CBC", "AES/CFB8" or "AES/CTR".

OpenPGP CFB, GOFB, KCTR and a byte-oriented stream interface for CTR are not ported yet.

Traits

  • BlockCipherMode — restarts a mode from its IV and exposes the wrapped engine; implemented by every mode.
  • IvParams — the IV a parameter type provides alongside the engine's KeyParams.

Features

  • alloc (off by default) — adds the runtime-sized modes and KeyWithIvOwned; does not require the standard library.

Usage

[dependencies]
tc_block_modes = "0.1.0"
tc_block_cipher = "0.1.0"
tc_aes = "0.1.0"
use tc_aes::AesEngine;
use tc_block_cipher::{BlockCipher, BlockCipherInit, CipherDirection};
use tc_block_modes::{FixedCbcBlockCipher, KeyWithIvRef};

let (key, iv) = ([0x42; 16], [0x24; 16]); // a fresh, unpredictable IV per message
let mut cbc = FixedCbcBlockCipher::<_, 16>::new(AesEngine::new());
cbc.init(CipherDirection::Encrypt, &KeyWithIvRef::new(&key, &iv)).expect("valid key and IV");
let mut ciphertext = [0; 16];
cbc.process_block(b"one single block", &mut ciphertext).expect("initialized, whole block");

The crate and type documentation carry executable examples for every mode.

Security

No mode authenticates, so altered ciphertext goes undetected. Protect messages with an authenticated-encryption construction, or add a MAC over the ciphertext.

The IV decides the confidentiality of every mode here. CBC and CFB need a fresh, unpredictable IV for every message; OFB and CTR need one that never repeats under a key, and a CTR counter block must never repeat either. The CTR counter spans the whole block and carries into the IV bytes, so keep each message below 2^(8 * counter bytes) blocks: 64 GiB for AES with a 12-byte IV.

The modes add only data-independent XORs, copies and a branch-free counter increment, so each call is constant time exactly when the engine is. tc_aes::AesEngine, for example, is constant time with AES-NI or its rustcrypto feature and variable time otherwise.

Every mode wipes its IV, its feedback register or counter, and its last chaining or keystream block on drop; the engine wipes its own key schedule. Wiping does not reach the caller's buffers, copies left in registers and on the stack, or a value that is leaked or forgotten.

Validation

Every mode is tested against the NIST SP 800-38A AES-128 vectors, including CFB8, in both directions and in both its fixed-size and runtime-sized forms. Contract tests cover the IV rules, the CTR counter carry, feedback-size validation, reset, direction handling, state kept across a rejected init, errors on short buffers and preserved output tails. A test requires every public API to document whether it is constant or variable time.

Missing public documentation and unsafe code are rejected by crate-level lints.

Run these commands from the workspace root:

cargo test -p tc_block_modes --locked
cargo test -p tc_block_modes --locked --features alloc
cargo clippy -p tc_block_modes --all-targets --all-features --locked -- -D warnings
cargo fmt -p tc_block_modes --check
cargo doc -p tc_block_modes --no-deps --all-features --locked

Before a release, check the archive contents and run publication validation from a committed checkout:

cargo package -p tc_block_modes --list --locked
cargo publish -p tc_block_modes --dry-run --locked

The archive includes both license texts, this README, the changelog, the source and the integration tests. It must not include target/ or other build artifacts.

License

Licensed under either the MIT license or the Apache License, Version 2.0, at your option.