Encryption basics
This page introduces the mental model that every cipher in the library follows. If you know System.Security.Cryptography.SymmetricAlgorithm from the BCL, most of this will feel familiar - with three twists:
BlockModereplacesMode. The inheritedModeproperty (type CipherMode) only knows about the modes the BCL defined. Bodu ciphers expose a newBlockModeproperty of type CipherModeKind, which addsCTR,XTS, and friends. SetBlockMode, notMode.- Tweak is a first-class input for Threefish. Threefish is a tweakable block cipher; each call is parameterized by a key, an IV, and a 128-bit tweak that acts as a domain-separation label.
- Key / IV / Tweak are lazily generated. If you never set them, they are materialized on first read from a cryptographically secure RNG. Read the property, or call
GenerateKey()/GenerateIV()/GenerateTweak()explicitly.
The CipherModeKind enum is a superset of the BCL CipherMode: its CBC, ECB, OFB, CFB, and CTS members share the framework numeric values (so they cast directly), and CTR, XTS, plus the AEAD modes OCB / EAX / SIV start at 1 << 10 so they never collide with framework values. That is how a Bodu cipher can offer counter mode while still deriving from SymmetricAlgorithm.
Two padding properties, kept in sync
Every block-cipher wrapper carries two padding surfaces that always agree:
- the inherited PaddingMode-typed
Padding(so the cipher plugs intoCryptoStreamand any BCL-shaped code); and - a
BlockPaddingof type PaddingModeKind, the extended enum that adds ISO/IEC 7816-4 bit padding on top of the framework values.
Assigning either one updates the other whenever the value has a counterpart: setting Padding = PaddingMode.PKCS7 sets BlockPadding to PaddingModeKind.PKCS7, and the reverse holds. The single asymmetry is the Bodu-only PaddingModeKind.ISO7816_4, which has no PaddingMode equivalent - assigning it leaves the inherited Padding untouched. Use whichever property reads more naturally at the call site; the samples in these guides use the BCL Padding because it is the name most readers already know.
Anatomy of an encryption
using System.Security.Cryptography;
using Bodu.Security.Cryptography;
// 1. Choose and configure the algorithm.
using var alg = new Threefish256();
alg.BlockMode = CipherModeKind.CBC; // how blocks chain
alg.Padding = PaddingMode.PKCS7; // how the last (partial) block is filled
// 2. Produce key material. This is cryptographically random.
alg.GenerateKey(); // 32 bytes for Threefish-256
alg.GenerateIV(); // 32 bytes, matches the block size
alg.GenerateTweak(); // 16 bytes, Threefish-specific
// 3. Encrypt.
byte[] plaintext = System.Text.Encoding.UTF8.GetBytes("hello, world");
byte[] ciphertext;
using (ICryptoTransform enc = alg.CreateEncryptor())
ciphertext = enc.TransformFinalBlock(plaintext, 0, plaintext.Length);
// 4. Decrypt using the same Key, IV, and Tweak.
byte[] recovered;
using (ICryptoTransform dec = alg.CreateDecryptor())
recovered = dec.TransformFinalBlock(ciphertext, 0, ciphertext.Length);
Debug.Assert(plaintext.SequenceEqual(recovered));
The four numbered steps above are the shape of every encryption in the library.
Key, IV, Tweak - what each is for
| Input | Role | Secret? | Reuse across messages? |
|---|---|---|---|
| Key | Selects which permutation family to use. | Yes. Never transmit or log in the clear. | Yes, within a rotation policy (e.g. rotate per month / per volume). |
| IV (initialization vector) | Randomizes the ciphertext so two messages with the same key and plaintext encrypt to different ciphertexts. | No - the IV travels with the ciphertext. | No. Must be unique per message under a given key. For CBC it must also be unpredictable. For CTR / OFB reuse is catastrophic. |
| Tweak (Threefish only) | Domain separator. Encrypting the same plaintext under the same key but a different tweak yields unrelated ciphertext. | No - treat like an IV. | Depends on use. For generic encryption, it behaves like an auxiliary IV; for disk encryption-style uses, it encodes the sector/record number. |
Important
IV is not a nonce. Block ciphers in this library take an IV whose length equals the block size; the requirements on that IV depend on the mode - CBC needs it unpredictable, CTR/OFB/CFB only need it unique, and ECB takes none at all. The stream ciphers (stream-ciphers) are different: they derive from SymmetricStreamAlgorithm and take a Nonce (generated with GenerateNonce()), not a block IV. The word "nonce" - number used once - captures the one rule they share: under a fixed key, the value must never repeat.
Tweak sizing
The tweak is a property of TweakableSymmetricAlgorithm, the base the Threefish wrappers extend. It is fixed at 128 bits (16 bytes) across the whole Threefish family - LegalTweakSizes advertises the permitted sizes, and GenerateTweak() fills 16 random bytes. Unlike the key it is not secret; unlike the IV it does not have to change per message. Its job is domain separation: two encryptions under the same key but different tweaks are cryptographically unrelated. See Threefish-256 for worked tweak patterns.
Using the extension methods
The repetitive CreateEncryptor() / TransformFinalBlock() dance can be collapsed to one call with the Encrypt / Decrypt extension methods in Bodu.Security.Cryptography.Extensions:
using Bodu.Security.Cryptography.Extensions;
using var alg = new Threefish256 { BlockMode = CipherModeKind.CBC, Padding = PaddingMode.PKCS7 };
alg.GenerateKey();
alg.GenerateIV();
alg.GenerateTweak();
byte[] ciphertext = alg.Encrypt(plaintext);
byte[] recovered = alg.Decrypt(ciphertext);
Both extension methods also have overloads for Stream sources and destinations, so you can encrypt a file in one call:
using var src = File.OpenRead("plaintext.bin");
using var dst = File.Create("cipher.bin");
int bytesRead = alg.Encrypt(src, dst, bufferSize: 4096);
Lazy material generation
Reading the Key, IV, or Tweak property on an un-initialized algorithm allocates random bytes. That is usually what you want for a fresh encryption, but it means that:
- Reading
alg.Keyonce and then encrypting is safe - the same bytes are cached inKeyValueand used on the next access. - Reading
alg.Keybefore decrypting a previously-encrypted message will silently generate a different key. Decryption will then fail with garbled output or a padding error. Always re-assign the exact bytes used at encryption time.
// ✗ Wrong - this generates a NEW key for decryption.
byte[] cipher = alg.Encrypt(plaintext);
using var fresh = new Threefish256();
byte[] recovered = fresh.Decrypt(cipher); // fails: wrong key
// ✓ Right - carry Key/IV/Tweak across the boundary.
byte[] key = alg.Key, iv = alg.IV, tweak = alg.Tweak;
byte[] cipher = alg.Encrypt(plaintext);
// …store (cipher, iv, tweak) next to the message; keep key in a vault.
using var fresh = new Threefish256 { Key = key, IV = iv, Tweak = tweak };
byte[] recovered = fresh.Decrypt(cipher);
Storage layout
A common convention, used by many protocols, is to prepend the IV (and, for Threefish, the tweak) to the ciphertext:
┌────────┬──────────┬──────────────────┐
│ IV │ tweak │ ciphertext │
│ B bytes│ 16 bytes │ … │
└────────┴──────────┴──────────────────┘
The receiver knows the cipher's block size, so it can slice the fixed-width prefix off and then pass the remaining ciphertext through the decryptor with the recovered IV and tweak. The key is not in this envelope - it lives separately in a secrets store.
Disposal
Every SymmetricAlgorithm holds sensitive material (the expanded key schedule, the IV, intermediate buffers). Always wrap in using:
using var alg = new Threefish256();
// …
// alg is disposed here; its internal state is zeroed.
ICryptoTransform instances returned from CreateEncryptor() / CreateDecryptor() are also IDisposable - wrap them too, unless you're using the extension methods which already do.
Common pitfalls
- Reusing a
(Key, IV)pair in CTR / OFB / CFB completely breaks confidentiality: the XOR of two ciphertexts recovers the XOR of the plaintexts. - Reusing a
Keyin ECB makes identical plaintext blocks produce identical ciphertext blocks - structure leaks. - Using
PaddingMode.Nonewith an unpadded plaintext will throw if the plaintext length is not a multiple of the block size. See Padding for details. - Logging the key (e.g. via
Convert.ToHexString(alg.Key)) makes the key available to anyone with log access. Don't. - Holding the algorithm instance alive longer than needed keeps the expanded key schedule in memory. Dispose as soon as encryption finishes.
When to prefer the BCL over a Bodu cipher
These wrappers exist to cover algorithms the BCL does not ship - Threefish, Camellia, Twofish, Serpent, Skipjack, Blowfish - and to expose the modes (CTR, XTS) and padding (ISO7816_4) the framework enums omit. They do not re-implement AES: the only AES surface here is AesBlockCipher, a thin IBlockCipher adapter over the BCL Aes engine, provided so AES can drive the AEAD mode transforms.
| Want… | Reach for |
|---|---|
| AES in ECB / CBC / CTR / CFB | the BCL Aes directly - it is hardware-accelerated on AES-NI CPUs |
| AES-GCM / AES-CCM | the BCL AesGcm / AesCcm, or AesBlockCipher + the Bodu AEAD transforms for OCB / EAX / SIV / GCM-SIV |
| A non-AES block cipher | the Bodu SymmetricAlgorithm wrapper for that cipher |
| A counter-mode or tweakable cipher | a Bodu wrapper with BlockMode = CTR, or Threefish for tweak support |
The rule of thumb: if the BCL already ships the exact algorithm-and-mode you need, use it; reach for these wrappers when the algorithm, mode, or padding is the specific reason you came here.
Where to go next
- Cipher block modes - ECB, CBC, CFB, OFB, CTR side by side.
- Padding - which scheme for which situation.
- Per-algorithm: Threefish-256 · Threefish-512 · Threefish-1024 · Skipjack · Blowfish.
- Hashing & Cryptography guides - every guide in this topic, across Bodu.IO.Hashing and Bodu.Security.Cryptography.