Table of Contents

Using Serpent

Serpent is the AES finalist by Anderson, Biham, and Knudsen: a 32-round substitution-permutation network over a 128-bit block with 128-, 192-, or 256-bit keys. Bodu.Security.Cryptography ships it in two very different forms. Serpent128 is the standard cipher, verified against the NESSIE submission vectors. Serpent256, Serpent512, and Serpent1024 are Bodu-defined, non-standard, tweakable wide-block constructions that reuse Serpent's S-boxes over a wider state - they interoperate with nothing but themselves.

Note

Like the rest of the library, these implementations are not independently audited and offer best-effort, not guaranteed, side-channel resistance. Each S-box is computed as a Boolean circuit (Osvik's) and the linear transform with rotations, shifts and XOR, so no table is read and no branch depends on the key or the data.

The family at a glance

Type Role Block Key Tweak Rounds Standard
Serpent128 SymmetricAlgorithm wrapper (an ExtendedSymmetricAlgorithm) 128 bits 128 (default) / 192 / 256 bits - 32 Serpent (NESSIE vectors)
Serpent128Cipher raw IBlockCipher engine 128 bits 16 / 24 / 32 bytes - 32 Serpent
Serpent256 / Serpent256Cipher tweakable wrapper (Serpent) / engine 256 bits 256 bits 128 bits 48 Bodu-only
Serpent512 / Serpent512Cipher tweakable wrapper / engine 512 bits 512 bits 128 bits 64 Bodu-only
Serpent1024 / Serpent1024Cipher tweakable wrapper / engine 1024 bits 1024 bits 128 bits 80 Bodu-only

The wide variants take a key exactly as long as their block and a fixed 16-byte tweak; the engines expose KeySize constants (256 / 512 / 1024) and the wrappers report LegalTweakSizes of exactly 128 bits. SerpentBlockCipherBase is the shared abstract base (S-boxes, linear transform, disposal) under both engine families, and SerpentBlockCipher is the abstract wide-block engine that Serpent256Cipher and siblings specialize.

Pattern 1 - a known-answer check on the raw engine

Serpent128Cipher is the primitive: one block in, one block out. The Serpent NESSIE set 1 vector 0 (all-zero key and plaintext) is the quickest way to confirm you have standard Serpent:

using Bodu.Security.Cryptography;

using IBlockCipher cipher = new Serpent128Cipher(new byte[16]);      // 16-, 24-, or 32-byte key
byte[] block = new byte[16];
byte[] ciphertext = new byte[16];
cipher.Encrypt(block, ciphertext);
// 3620B17AE6A993D09618B8768266BAE9

byte[] recovered = new byte[16];
cipher.Decrypt(ciphertext, recovered);                                // back to the zero block

using IBlockCipher keyed = new Serpent128Cipher(Convert.FromHexString("000102030405060708090A0B0C0D0E0F"));
keyed.Encrypt(Convert.FromHexString("33B3DC87EDDD9B0F6A1F407D14919365"), ciphertext);
// 00112233445566778899AABBCCDDEEFF  (NESSIE set 1 vector 9)

An engine validates lengths strictly: a key that is not 16, 24, or 32 bytes throws ArgumentException (key), and Encrypt / Decrypt throw ArgumentException unless both spans are exactly 16 bytes.

Runs of independent blocks go faster through IBlockCipher.EncryptBlocks / DecryptBlocks, which the ECB, CTR, XTS and OCB modes already use: Serpent128Cipher processes eight or four blocks at once there on processors with vector instructions (see Hardware acceleration), and one at a time elsewhere, with identical output.

Pattern 2 - Serpent-128 through the wrapper

Serpent128 behaves like every other ExtendedSymmetricAlgorithm: set Key, IV, BlockMode, and Padding, then use CreateEncryptor / CryptoStream or the one-shot extensions. Serpent128.Create() is the factory-style equivalent of new Serpent128(). The default key size is 128 bits; set KeySize before assigning or generating a longer key.

using System.Security.Cryptography;
using Bodu.Security.Cryptography;
using Bodu.Security.Cryptography.Extensions;

byte[] plaintext = "Serpent-128 through the SymmetricAlgorithm wrapper"u8.ToArray();

using var alg = Serpent128.Create();
alg.KeySize = 256;                                                   // 128 (default), 192, or 256
alg.Key = Convert.FromHexString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
alg.IV  = Convert.FromHexString("101112131415161718191a1b1c1d1e1f");
alg.BlockMode = CipherModeKind.CBC;
alg.Padding = PaddingMode.PKCS7;

byte[] ciphertext = alg.Encrypt(plaintext);                          // 64 bytes: 040E605C…848BCFAC
byte[] recovered  = alg.Decrypt(ciphertext);

LegalKeySizes reports 128-256 in 64-bit steps. Because it is a 128-bit-block cipher, Serpent128Cipher is not accepted by the AEAD transforms - those are written for AesBlockCipher only - but every classic mode and padding scheme in Modes, transforms, and factories applies.

Pattern 3 - the wide-block, tweakable variants

The three wide variants derive from Serpent, itself a TweakableSymmetricAlgorithm. They add Tweak / GenerateTweak() and the three-argument CreateEncryptor(key, iv, tweak), and carry their own BlockMode property (the inherited Mode is not synchronized). Each variant has a static Create().

using System.Security.Cryptography;
using Bodu.Security.Cryptography;
using Bodu.Security.Cryptography.Extensions;

byte[] plaintext = "wide-block, tweakable, Bodu-only"u8.ToArray();   // 32 bytes = exactly one block

using var alg = Serpent256.Create();
alg.Key   = Convert.FromHexString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");   // 32 bytes = block size
alg.IV    = Convert.FromHexString("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f");   // 32 bytes
alg.Tweak = Convert.FromHexString("404142434445464748494a4b4c4d4e4f");                                   // 16 bytes, always
alg.BlockMode = CipherModeKind.CTR;
alg.Padding = PaddingMode.None;

byte[] ciphertext = alg.Encrypt(plaintext);      // A63B7A5F4029BFA7E86CEF5834AB9097A83E57EF51C100D502945EC189B957C3
byte[] recovered  = alg.Decrypt(ciphertext);

alg.Tweak = Convert.FromHexString("404142434445464748494a4b4c4d4e50");   // one bit of tweak → unrelated ciphertext
byte[] other = alg.Encrypt(plaintext);           // B8B27397…351E678C

The tweak is injected into the state after every fourth round together with a round counter, so - as with Threefish - it separates domains under one key rather than replacing the IV. Using Threefish-256 explains the IV-versus-tweak distinction; it applies verbatim here.

The raw engines take the key and tweak together and drop into BlockCipherModeFactory like any other IBlockCipher:

using Bodu.Security.Cryptography;

byte[] key = new byte[32], tweak = new byte[16], iv = new byte[32];
using IBlockCipher cipher = new Serpent256Cipher(key, tweak);        // Serpent512Cipher: 64-byte key; Serpent1024Cipher: 128-byte key
using IBlockCipherModeTransform ctr = BlockCipherModeFactory.Create(CipherModeKind.CTR, cipher, iv);

byte[] plaintext = "raw engine + CTR"u8.ToArray();
byte[] ciphertext = new byte[plaintext.Length];
ctr.Transform(plaintext, ciphertext, encrypt: true);                 // 0BC34B78EC9E17A0F388E6E7B845F6C8

A wrong key length throws ArgumentException (key); a tweak that is not 16 bytes throws ArgumentException (tweak). All-zero key, tweak, and plaintext encrypt under Serpent-256 to 79A23C5889F070C99DEDC6CC9806A29A98A3F2B854B61D719C1FA832ADF900D0 - a value you can pin in your own tests, but not one any other implementation will reproduce.

When not to use the wide variants

Warning

Serpent256, Serpent512, and Serpent1024 are experimental constructions defined by this library. There is no specification, no third-party implementation, no published cryptanalysis, and no test vector outside this repository. Do not use them where interoperability, external review, or a compliance regime matters. For a standard cipher use Serpent128 or, preferably, AES; for a reviewed tweakable wide-block cipher use Threefish, which comes from the Skein SHA-3 submission and carries published vectors.

They exist for experiments that want Serpent's S-box structure over a wider state - for example comparing wide-block behaviour against Threefish under identical modes. Treat any ciphertext they produce as tied to this library's version.

API summary

Member Serpent-128 Serpent-256 / 512 / 1024
Wrapper type Serpent128 : ExtendedSymmetricAlgorithm Serpent256 … : Serpent : TweakableSymmetricAlgorithm
Engine type Serpent128Cipher(ReadOnlySpan<byte> key) Serpent256Cipher(key, tweak) …
Create() Serpent128.Create() Serpent256.Create(), Serpent512.Create(), Serpent1024.Create() (none on the abstract Serpent)
Block / key sizes 128 / 128-256 step 64 block = key = 256 / 512 / 1024
Tweak - Tweak, TweakSize (128), LegalTweakSizes, GenerateTweak()
Mode selection BlockMode (synced with Mode) + BlockPadding / Padding own BlockMode; BCL Padding only
Transforms CreateEncryptor(key, iv) CreateEncryptor(key, iv, tweak) and the parameterless overload

Where to go next