Introduction
Cryptography is everywhere — and yet most of us treat it like magic.
From encrypted chats and online banking to firmware updates on satellites, the systems we rely on daily are secured (or compromised) by cryptography. And too often, it fails — not because the math was wrong, but because the implementation was.
This book exists to change that.
You’re not here to memorize equations. You’re here to understand, implement, and apply cryptographic primitives to real-world systems using a language designed to prevent mistakes before they happen: Rust.
Whether you’re building infrastructure, smart contracts, embedded firmware, or secure APIs, this book gives you the tools to use cryptography safely, idiomatically, and fearlessly — one primitive, one project, one domain at a time.
⚠️ Important Note on Scope
This book takes a two-layered approach:
Learning the Primitives
- We begin with fundamental building blocks (XOR, AES, ChaCha20, Feistel, SPN) to teach how cryptography works under the hood.
- These examples are simplified, sometimes insecure by design, and are provided for educational clarity.
Applying Secure Constructions
- For every primitive introduced, we also demonstrate how it is used safely in practice — with production-ready Rust code and well-reviewed crates.
- In modern systems, raw primitives are never used alone. Instead, we rely on AEAD (Authenticated Encryption with Associated Data) modes such as AES-GCM or ChaCha20-Poly1305, which provide both confidentiality and integrity/authentication.
- Each chapter connects the primitive to its real-world domains (e.g., AES-GCM in TLS, ChaCha20-Poly1305 in VPNs and mobile messaging, AES-XTS in disk encryption).
💡 Bottom line: You’ll gain an understanding of the core mechanics of symmetric ciphers, and also learn how to apply them correctly with Rust in production scenarios. By the end, you’ll not only know what’s inside the black box, but also how to choose and use the right construction for your specific domain.
Who This Book Is For
- Rust developers who want to understand and apply cryptography
- Security engineers transitioning to Rust
- Curious hackers tired of black-box crypto
- Systems developers who care about safety, correctness, and resilience
What You’ll Learn
- The foundations and mental models behind symmetric and asymmetric cryptography
- How to use modern cryptographic crates in Rust safely and idiomatically
- Where cryptographic primitives show up in real-world domains (blockchain, embedded, medical, etc.)
- How to design, test, and publish your own secure Rust crypto crate
What This Book Is Not
- ❌ A math-heavy cryptography textbook
- ❌ A copy-paste cookbook
- ❌ A blockchain hype manual
We won’t drown you in proofs, but we’ll explain just enough math to build intuition. We’ll write real code — not just use libraries. And we’ll focus on systems-level crypto, not speculative tokens.
What You’ll Need
- ✅ Basic experience with Rust (enough to build a CLI or follow
cargo run) - ✅ Comfort with reading code, refactoring, and using crates
- ✅ Curiosity, and a bias toward safe, practical, applied learning
Let’s begin
This book won’t make you a cryptographer in the academic sense — but it will make you something just as rare and valuable: A Rust engineer who understands, wields, and applies cryptography with precision, context, and confidence.
You won’t just read — you’ll build.
Focused implementations and applied examples are available here and updated weekly.
How to Use This Book
This book is designed to be practical, modular, and domain-focused.
You can read it front to back, or jump directly to the domains and primitives most relevant to your work.
Structure
The book is organized into four major parts:
Foundations — Why Rust is uniquely suited for secure cryptographic engineering
Primitives — The core building blocks of cryptography, with Rust-focused usage and implementation
Applied Domains — Real-world systems and how they use cryptography in practice (blockchain, defense, aerospace, medical, infrastructure, etc.)
Crate Building — How to architect, test, audit, and publish your own cryptographic crate in Rust
Each chapter includes:
- Plain-language explanations
- Rust code examples using community crates
- Security insights and common pitfalls
- Domain-specific applications
Code & Examples
This book includes real, runnable Rust code for nearly every chapter.
- All examples are written for Rust 2024
- You’ll need a working Rust toolchain (
rustup,cargo) - Each chapter’s code is provided as a separate Cargo example.
- Examples live inside the companion repository: sealed-in-rust
You can run them locally without touching production crates or complex setups:
git clone https://github.com/VinEckSie/sealed-in-rust.git
cd sealed-in-rust/rust_crypto_book_code
cargo run --example aes_cbc //Replace aes_cbc with any chapter example you want to try
⚠️ Note on Playground Limitations:
Some examples in this book use no_run or ignore markers when rendered online, because external crates like aes, cbc, or rsa cannot be compiled in-browser. All examples work locally, and full source code is always included.
💡 These examples are didactic and minimal by design. They illustrate cryptographic concepts — not replace mature, production-ready libraries.
Non-linear Reading
This book doesn’t assume linear progress.
- Want to build a secure file encryption tool? Symmetric Ciphers + Secure Infrastructure
- Curious about smart contracts? Go directly to ECC and Blockchain & Web3
- Working on embedded firmware? Check out Ascon, PRESENT, and Defense & Aerospace
Each domain chapter reminds you of the necessary primitives — like a map, not a locked path.
Contributing, Feedback, & Issues
This book is a living project.
- Errors? Open an issue or PR on the GitHub repo
- Suggestions? You’re welcome to share ideas for domains, examples, or improvements
- Contributions to the examples repo are also welcome
Final Note
This is not a cryptography textbook — it’s a cryptographic engineering manual.
By the end, you’ll not only understand the primitives, you’ll know how to use them to secure real systems — in Rust, by design, not by accident.
Let’s begin.
Cryptography is a Systems Problem
Cryptography is a Systems Problem
Cryptography isn’t just about math.
Yes, it starts with elegant algebra and deep number theory — but where it breaks is almost always in the system. Real-world failures come from poor implementations, leaky abstractions, memory bugs, side channels, or simply misunderstanding what problem crypto is supposed to solve.
It’s easy to misuse even “secure” primitives. AES1 in ECB mode2 is fast — and useless. RSA3 without padding4 is a gift to attackers. And a perfectly strong key means nothing if it’s printed to your logs.
This is why cryptography is a systems engineering problem first.
And it’s why Rust matters.
Rust doesn’t make crypto correct by default — nothing does — but it gives you tools to avoid entire classes of catastrophic bugs.
Memory safety, explicit ownership, fearless concurrency, and tight control over the machine — these aren’t “nice to have.” They’re security features.
In this book, we’ll treat crypto not as a black box, but as a series of concrete systems problems — and show how Rust lets us solve them with clarity and precision.
-
ECB Mode. Simple block mode, insecure due to patterns. More ↩
-
Padding. Adds randomness/structure to secure RSA encryption. More ↩
Safety, Performance, Predictability
Safety, Performance, Predictability
Rust is often praised for its speed and memory safety, but in the world of cryptographic engineering, these traits aren’t just nice-to-haves — they’re critical.
Safety
Bugs in cryptographic code can be catastrophic. Memory corruption, undefined behavior, or uninitialized values can leak secrets or open attack vectors. Rust eliminates entire classes of these bugs at compile time:
- No nulls
- No uninitialized memory
- No data races
- No buffer overflows
This safety isn’t enforced by a runtime, but by the borrow checker at compile time. That makes Rust extremely attractive for writing low-level cryptographic code without sacrificing control.
️Performance
Rust compiles to fast native code, comparable to C and C++. There’s no garbage collector, and you pay only for what you use. This matters because cryptography is often used in:
- Performance-critical code paths (e.g. TLS handshakes, file encryption)
- Embedded systems where CPU cycles and memory are limited
Rust lets you stay close to the metal while writing high-level abstractions — it’s a rare balance.
Predictability
In cryptography, predictable behavior is essential. You need fine-grained control over:
- Timing — Avoid accidental leaks via early-exit comparisons or branching on secrets
- Memory — Prevent unexpected reallocations or optimization side effects
- Execution — Ensure constant-time logic without interference from JITs or hidden runtime behavior
Rust gives you this control by default, making it a strong ally in defending against side-channel attacks.
In short: Rust brings the low-level control of C, the safety of functional languages, and the clarity of modern syntax — all in a single toolchain. That’s why cryptographers and security engineers are increasingly turning to it.
Cost of Unsafety: Famous Failures
The Cost of Unsafety in Crypto: Famous Failures
Cryptography doesn’t fail because math is broken — it fails because systems leak, code panics, or side-channels whisper secrets.
And most of these failures? They stem from unsafety.
Here are just a few real-world examples of cryptographic disasters caused by unsafe programming, undefined behavior, or lack of control:
Heartbleed (2014)
- Cause: Buffer over-read in OpenSSL (written in C)
- Impact: Leaked private keys, passwords, and session data from millions of servers
- Lesson: Unsafe memory access can silently expose secrets
Debian RNG Bug (2006–2008)
- Cause: A developer commented out entropy-gathering code in OpenSSL
- Impact: Generated only 32,768 possible SSH keys across all Debian systems
- Lesson: Cryptographic quality often hinges on deterministic, auditable behavior
Lucky13 Attack (2013)
- Cause: Tiny timing differences in CBC mode padding checks (TLS)
- Impact: Allowed attackers to decrypt data by measuring how long responses took
- Lesson: Timing leaks can invalidate encryption, even with perfect math
JavaScript Crypto Fails
- Cause: Misuse of
Math.random()or insecure key handling in frontend apps - Impact: Predictable keys, insecure password storage, and non-constant-time comparisons
- Lesson: Languages with hidden optimizations make constant-time logic fragile
Why Rust Helps
Rust’s safety model eliminates whole classes of vulnerabilities:
- No null/dangling pointers
- No uninitialized memory
- Memory-safe concurrency
- Deterministic behavior at runtime (no GC pauses, no JIT surprise)
You still have to design crypto carefully, but with Rust, you’re not building it on quicksand.
Writing secure cryptography in unsafe languages is like writing legal contracts with disappearing ink.
First Code: A Naive XOR Encryptor
First Code: A Naive XOR Encryptor
Let’s write our first cryptographic algorithm — or at least something that looks like one.
We’ll implement a simple XOR cipher. This method is insecure and should never be used in real applications — but it’s the perfect teaching tool.
What’s a Cipher?
A cipher is just a method to transform readable data (plaintext) into unreadable data (ciphertext) using a key — and vice versa.
🧭 Word Origin — “Cipher” The word comes from the Arabic “ṣifr” (صفر), meaning “zero” or “empty”. It passed through Latin (cifra), then into French and English as cipher.
What started as a symbol for “nothing” evolved into a word for secret writing — and eventually, encryption algorithms.
What is XOR?
XOR stands for “exclusive or”, a bitwise operation:
| A | B | A XOR B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
In short: XOR returns 1 if the bits differ, 0 if they’re the same.
The XOR operation flips bits when they differ:
1 ^ 0 = 1
1 ^ 1 = 0
0 ^ 0 = 0
When used for encryption:
cipher = plaintext ^ key
plaintext = cipher ^ key
That’s why XOR can be used to encrypt and decrypt data — if you XOR something twice with the same key, you get the original back.
✅ Simple, reversible, fast — but also dangerously weak when misused.
XOR, Bit by Bit
To truly understand XOR in cryptography, it helps to look at bit-level behavior.
Let’s say you compute:
100 ^ 1
This doesn’t mean 100 to the power of 1. In Rust, ^ is the bitwise XOR operator.
Step-by-step:
100 = 0110 0100
1 = 0000 0001
---------------
XOR = 0110 0101 = 101
✅ Each bit is compared: If they’re different → 1 If they’re the same → 0
100 ^ 1 = 101
This is what makes XOR useful: you can toggle bits with a key, and reverse it by applying the same key again.
Why This?
This example teaches you:
- The reversible nature of XOR (
a ^ b ^ b == a) - Handling bytes and slices in Rust
- Thinking about encryption as a transformation
- Why key reuse and simplicity are dangerous
Naive XOR in Rust
Here’s how to implement a basic XOR encryptor in Rust:
Filename: src/main.rs
fn main() {
let message = b"hello world";
let key = b"key";
let encrypted = xor_encrypt(message, key);
let decrypted = xor_encrypt(&encrypted, key);
println!("Encrypted: {:x?}", encrypted);
println!("Decrypted: {}", String::from_utf8_lossy(&decrypted));
}
pub fn xor_encrypt(input: &[u8], key: &[u8]) -> Vec<u8> {
input
.iter()
.enumerate()
.map(|(i, &byte)| byte ^ key[i % key.len()])
.collect()
}
Output
The output will show the encrypted bytes (in hex) and the original decrypted message.
What’s Wrong With This Cipher?
- Key reuse makes patterns obvious
- No randomness or initialization vector (IV)
- Susceptible to frequency analysis attacks
This cipher is insecure — but it demonstrates important cryptographic concepts:
- Reversibility
- Byte-wise transformations
- Why randomness and key handling matter
You’ll build on this when implementing real-world ciphers like ChaCha20 or AES.
Tooling Up
Tooling Up
Before we dive into cryptographic primitives, here’s a quick look at tools that can support your Rust development journey — especially if you plan to write your own crypto libraries.
None of these tools are required to read or complete the following chapters.
If you already havecargoinstalled, you’re good to go!
That said, for readers who want to build clean, secure, and testable codebases, these tools are worth bookmarking:
Code Quality
- rustfmt — auto-formats code to keep it idiomatic
- clippy — catches common pitfalls and non-idiomatic patterns
Testing & Fuzzing
- proptest — property-based testing for edge case discovery
- cargo-fuzz — fuzz testing to uncover panics and vulnerabilities
Security & Auditing
- cargo-audit — alerts you to vulnerable crates
- cargo-geiger — scans for
unsafecode
Benchmarking & Debugging
- Criterion.rs — precise performance benchmarks
- cargo-expand — view macro-expanded code (useful when using
#[derive(...)])
As you gain experience, integrating these tools will help ensure your cryptographic code is not only correct — but robust, maintainable, and audit-friendly.
Try It Yourself
Want to skip the setup and jump right into coding?
👉 Use the Sealed in Rust Starter Template — a minimal Rust project preconfigured with the tools mentioned above.
git clone https://github.com/vinecksie/sealed-starter.git
cd sealed-starter
cargo test
Symmetric Ciphers — XOR, AES, ChaCha20
Symmetric Ciphers: XOR, AES, ChaCha20 & Beyond
🔐 Used in: VPNs, TLS (post-handshake), disk encryption, messaging apps
✅ Still foundational in modern cryptography.
What Are Symmetric Ciphers?
Symmetric ciphers use the same key for both encryption and decryption. Unlike public-key cryptography, they don’t offer key exchange—but they are much faster, making them ideal for bulk data encryption.
They are used everywhere: encrypted file systems, secure communications, and even inside protocols like TLS (after the handshake).
XOR Cipher — Simplicity That Teaches
⚠️ Insecure. Demonstration-only (used in educational demos, malware obfuscation )
Watch it on my Fearless in Rust channel: XOR Cipher in Rust - Step by Step
We first explored XOR encryption in Section 1.4: First Code — A Naive XOR Encryptor, where we built a full working example from scratch.
XOR is the simplest symmetric cipher: each byte of the message is XORed with a repeating key. Reversibility is built-in — XORing twice with the same key restores the original.
fn main() {
let message = b"Hi, Rust!";
let key = b"key";
let encrypted = xor_encrypt(message, key);
let decrypted = xor_encrypt(&encrypted, key);
println!("Encrypted: {:x?}", encrypted);
println!("Decrypted: {}", String::from_utf8_lossy(&decrypted));
}
pub fn xor_encrypt(input: &[u8], key: &[u8]) -> Vec<u8> {
input
.iter()
.enumerate()
.map(|(i, &byte)| byte ^ key[i % key.len()])
.collect()
}
🟢 Conclusion XOR encryption is reversible and stateless, which makes it simple and fast. But it lacks confusion and diffusion, so patterns in the input remain visible — offering no real resistance to cryptanalysis.
Feistel Networks — Foundation of Classic Block Ciphers
⚠️ Cryptographically obsolete, but conceptually important (used in DES1, 3DES2)
Feistel networks are a clever way to build reversible encryption using any basic function—even if that function itself can’t be reversed. That’s the key idea.
Each round applies a transformation to the data. Multiple rounds are chained to strengthen security.
Each round does the following:
- Takes two halves: Left (L) and Right (R)
- Computes a function f(R, key)
- Updates the pair as:
L₂ = R₁
R₂ = L₁ ⊕ f(R₁, key)
To encrypt, let’s see it in Rust:
fn feistel_round(l: u8, r: u8, k: u8) -> (u8, u8) {
let f = r ^ k;
(r, l ^ f)
}
fn main() {
let left1: u8 = 0b1010_1010; // 170
let right1: u8 = 0b0101_0101; // 85
let key: u8 = 0b1111_0000; // 240
let (left2, right2) = feistel_round(left1, right1, key);
println!("Encrypted: ({}, {})", left2, right2);
}
Decryption reuses the same function f, simply reversing the round transformation:
fn feistel_round(l1: u8, r1: u8, k: u8) -> (u8, u8) {
let f = r1 ^ k;
(r1, l1 ^ f)
}
fn feistel_decrypt(l2: u8, r2: u8, k: u8) -> (u8, u8) {
let f = l2 ^ k;
let l1 = r2 ^ f;
(l1, l2)
}
fn main() {
let left1: u8 = 0b1010_1010; // 170
let right1: u8 = 0b0101_0101; // 85
let key: u8 = 0b1111_0000; // 240
let (left2, right2) = feistel_round(left1, right1, key);
println!("Encrypted: ({}, {})", left2, right2);
let (left_orig, right_orig) = feistel_decrypt(left2, right2, key);
println!("Decrypted: ({}, {})", left_orig, right_orig);
}
Because encryption produces :
Encrypted → (R, L ⊕ f(R, k))
Let’s define:
- L₁ and R₁ = original input
- L₂ = R₁ and R₂ = L₁ ⊕ f(R₁, k)
We receive (L₂, R₂) and want to recover (L₁, R₁):
-
From encryption, we know L₂ = R₁
- So: R₁ = L₂
-
And: R₂ = L₁ ⊕ f(R₁, k)
- Replace R₁ with L₂
- R₂ = L₁ ⊕ f(L₂, k)
-
Rearranging to get L₁:
- L₁ = R₂ ⊕ f(L₂, k)
So, decryption is
L₁ = R₂ ⊕ f(L₂, k)
R₁ = L₂
🟢 Conclusion
Reversibility comes from XOR being reversible and swapping the halves. Feistel networks let you build reversible encryption even with non-invertible functions. This idea shaped DES and similar ciphers.Not used today due to known vulnerabilities, but conceptually essential.
Substitution–Permutation Networks (SPN)
⚠️ Software-only S-box implementations can leak secrets through cache timing. Modern AES implementations use hardware instructions (AES-NI) or constant-time software libraries.
⚠️ Used in AES3, Camellia4, and modern block ciphers. Still dominant in current cipher architectures
Substitution-Permutation Networks (SPNs) are a powerful way to build secure block ciphers by layering simple operations repeated across multiple rounds to build a secure cipher.
Each round does the following:
- Substitution – replace each byte using an S-box (non-linear mapping)
- Permutation – reorder bits or bytes to spread influence
- Key mixing – XOR the block with a round key
Decryption reverses these steps in reverse order.
💡 An S-box (substitution box) is a predefined table that maps each input byte to a new output byte. Its goal is to introduce non-linearity — meaning the output doesn’t follow any simple, predictable rule based on the input.
This non-linear mapping ensures that small changes in the input produce unpredictable changes in the output, making it impossible to reverse or model with linear equations — a key requirement for secure encryption.
Let’s walk through a simple encryption of a 4-byte block.
#![allow(unused)]
fn main() {
use std::convert::TryInto;
// Manually defined "shuffled" S-box (shortened for demo)
let s_box: [u8; 16] = [
0x63, 0x7C, 0x77, 0x7B,
0xF2, 0x6B, 0x6F, 0xC5,
0x30, 0x01, 0x67, 0x2B,
0xFE, 0xD7, 0xAB, 0x76,
];
// Step 1: Substitution with S-box
// ⚠️ input and output (substituted) must have the same size
// Otherwise, map() or indexing will panic at runtime
let input: [u8; 4] = [0x00, 0x03, 0x07, 0x0F];
let substituted: [u8; 4] = input.map(|b| s_box[b as usize]);
// Step 2: Permutation (custom byte reordering)
let permuted: [u8; 4] = [
substituted[2], // byte 2 moves to pos 0
substituted[0], // byte 0 → pos 1
substituted[3], // byte 3 → pos 2
substituted[1], // byte 1 → pos 3
];
// Step 3: XOR with round key
let round_key: [u8; 4] = [0xF0, 0x0F, 0xAA, 0x55];
let encrypted: [u8; 4] = permuted
.iter()
.zip(round_key.iter())
.map(|(a, b)| a ^ b)
.collect::<Vec<u8>>()
.try_into()
.unwrap();
println!("Step | Byte 0 | Byte 1 | Byte 2 | Byte 3");
println!("------------|--------|--------|--------|--------");
println!("Input | {:02X} | {:02X} | {:02X} | {:02X}", input[0], input[1], input[2], input[3]);
println!("Substituted | {:02X} | {:02X} | {:02X} | {:02X}", substituted[0], substituted[1], substituted[2], substituted[3]);
println!("Permuted | {:02X} | {:02X} | {:02X} | {:02X}", permuted[0], permuted[1], permuted[2], permuted[3]);
println!("Encrypted | {:02X} | {:02X} | {:02X} | {:02X}", encrypted[0], encrypted[1], encrypted[2], encrypted[3]);
}
To decrypt, reverse the steps in reverse order:
#![allow(unused)]
fn main() {
use std::convert::TryInto;
// Same S-box used for encryption
let s_box: [u8; 16] = [
0x63, 0x7C, 0x77, 0x7B,
0xF2, 0x6B, 0x6F, 0xC5,
0x30, 0x01, 0x67, 0x2B,
0xFE, 0xD7, 0xAB, 0x76,
];
// Generate inverse S-box
let mut inverse_s_box = [0u8; 256];
for (i, &val) in s_box.iter().enumerate() {
inverse_s_box[val as usize] = i as u8;
}
// Encrypted block from the previous encryption output
let encrypted: [u8; 4] = [0x35, 0x6C, 0xDC, 0x2E];
let round_key: [u8; 4] = [0xF0, 0x0F, 0xAA, 0x55];
// Step 1: Undo XOR with round key
let xor_reversed: [u8; 4] = encrypted
.iter()
.zip(round_key.iter())
.map(|(a, b)| a ^ b)
.collect::<Vec<u8>>()
.try_into()
.unwrap();
// Step 2: Reverse permutation
// Remember: original permutation was [2, 0, 3, 1]
// So now we must do: [1, 3, 0, 2]
let permuted_reversed: [u8; 4] = [
xor_reversed[1], // was originally at index 0
xor_reversed[3], // was at index 1
xor_reversed[0], // was at index 2
xor_reversed[2], // was at index 3
];
// Step 3: Inverse substitution using inverse_s_box
let decrypted: [u8; 4] = permuted_reversed.map(|b| inverse_s_box[b as usize]);
println!("Step | Byte 0 | Byte 1 | Byte 2 | Byte 3");
println!("------------|--------|--------|--------|--------");
println!("Encrypted | {:02X} | {:02X} | {:02X} | {:02X}", encrypted[0], encrypted[1], encrypted[2], encrypted[3]);
println!("XOR Rev | {:02X} | {:02X} | {:02X} | {:02X}", xor_reversed[0], xor_reversed[1], xor_reversed[2], xor_reversed[3]);
println!("Perm Rev | {:02X} | {:02X} | {:02X} | {:02X}", permuted_reversed[0], permuted_reversed[1], permuted_reversed[2], permuted_reversed[3]);
println!("Decrypted | {:02X} | {:02X} | {:02X} | {:02X}", decrypted[0], decrypted[1], decrypted[2], decrypted[3]);
}
Why it works
- Substitution = confusion → Hide relationships between plaintext and ciphertext
- Permutation = diffusion → Spread input influence across the block
These are Shannon’s two pillars of secure ciphers.
💡 Claude Shannon, widely considered the father of modern cryptography, introduced the concepts of confusion and diffusion in 1949 as the foundation of secure cipher design.
🟢 Conclusion
Substitution-Permutation Networks provide a simple yet powerful structure for building symmetric ciphers. They deliver the critical properties of confusion and diffusion, as first formalized by Claude Shannon in his foundational work on cryptographic security.
AES (Advanced Encryption Standard) — The Global Symmetric Standard
💡 Used in TLS5, LUKS6, SSH7, mobile apps, and FIPS-certified systems8.
Secure, fast, and hardware-accelerated
Crates used: aes, block_modes
AES is a symmetric-key block cipher developed by Belgian cryptographers Vincent Rijmen and Joan Daemen. It was selected by NIST in 2001 as the successor to DES and 3DES.
AES operates on 128-bit blocks and supports key sizes of 128, 192, or 256 bits. It is based on a Substitution–Permutation Network (SPN) and runs 10, 12, or 14 rounds depending on the key length.
It is standardized by FIPS-197, ISO/IEC9, and widely adopted in security protocols such as TLS, SSH, and IPsec10. AES is available in hardware on most modern CPUs, making it both fast and energy-efficient.
🧪 Code Example: AES-128-CBC Encryption & Decryption in Rust (source code)
We’ll use the aes and block-modes crates to encrypt and decrypt a message using AES-128 in CBC mode11 with PKCS712 padding.
#![allow(unused)]
fn main() {
use aes::Aes128;
use block_padding::Pkcs7;
use cbc::{Encryptor, Decryptor};
use cipher::{BlockEncryptMut, BlockDecryptMut, KeyIvInit};
pub fn run_aes_example() {
let key = b"verysecretkey123";
let iv = b"uniqueinitvector";
let plaintext = b"Attack at dawn!";
let mut buffer = plaintext.to_vec();
let pos = buffer.len();
buffer.resize(pos + 16, 0u8);
let encryptor = Encryptor::<Aes128>::new(key.into(), iv.into());
let ciphertext = encryptor
.encrypt_padded_mut::<Pkcs7>(&mut buffer, pos)
.expect("encryption failure");
println!("Ciphertext (hex): {}", hex::encode(ciphertext));
let decryptor = Decryptor::<Aes128>::new(key.into(), iv.into());
let mut ciphertext_buffer = ciphertext.to_vec();
let decrypted = decryptor
.decrypt_padded_mut::<Pkcs7>(&mut ciphertext_buffer)
.expect("decryption failure");
println!("Decrypted text: {}", String::from_utf8_lossy(decrypted));
assert_eq!(plaintext.to_vec(), decrypted);
}
}
Output:
#![allow(unused)]
fn main() {
Ciphertext (hex): a2ae0699ff0bc71e6ff43a32531d88
Decrypted text: Attack at dawn!
}
✅ Use a unique IV (Initialization Vector) for every encryption, and never reuse a key/IV pair. Avoid ECB mode entirely, and prefer AEAD modes (e.g., AES-GCM) when available.
🟢 Conclusion
AES is the modern standard for symmetric encryption.
It is fast, secure, and hardware-accelerated — making it ideal for both embedded systems and high-throughput servers.
When used correctly with a secure mode like CBC or GCM and proper key/IV management, AES provides strong resistance against all known practical attacks.
ChaCha20 — Modern Stream Cipher
💡 A stream cipher encrypts data one bit or byte at a time by XORing it with a pseudorandom keystream, instead of encrypting fixed-size blocks like a block cipher.
💡 Used in WireGuard13, TLS (on non-AES hardware14), mobile apps, messaging protocols, and security libraries.
Fast, simple, and naturally resistant to timing attacks.
Crate used: chacha20
ChaCha20 is a modern stream cipher designed by Daniel J. Bernstein.
It is the streamlined successor to Salsa2015, offering improved diffusion and exceptional performance on all platforms — especially devices without AES hardware.
Unlike block ciphers, ChaCha20 does not process data in fixed-size blocks. Instead, it transforms a key + nonce + counter into a pseudorandom keystream16.
Encryption is simply: ciphertext = plaintext XOR keystream
No padding. No block alignment. Just pure stream encryption.
ChaCha20 is now a fundamental primitive across modern cryptography:
WireGuard, OpenSSH (for session keys), TLS 1.3 fallback ciphers, mobile operating systems, and many authenticated encryption schemes like ChaCha20-Poly130517.
🧪 Code Example: ChaCha20 Encryption (source code)
We’ll generate a ChaCha20 keystream and XOR it with a plaintext message.
The API is extremely simple — you create a cipher and stream through it.
#![allow(unused)]
fn main() {
pub fn run_chacha20_example() {
use chacha20::cipher::{KeyIvInit, StreamCipher};
use chacha20::ChaCha20;
let key = *b"an example very very secret key!";
let nonce = *b"unique nonce";
let plaintext = b"Secret meeting at midnight".to_vec();
let mut ciphertext = plaintext.clone();
let mut encryptor = ChaCha20::new(&key.into(), &nonce.into());
encryptor.apply_keystream(&mut ciphertext);
println!("Ciphertext (hex): {}", hex::encode(&ciphertext));
let mut decrypted = ciphertext.clone();
let mut decryptor = ChaCha20::new(&key.into(), &nonce.into());
decryptor.apply_keystream(&mut decrypted);
println!("Decrypted text: {}", String::from_utf8_lossy(&decrypted));
assert_eq!(plaintext, decrypted);
}
}
Output:
#![allow(unused)]
fn main() {
Ciphertext (hex): b0bf118af914c7127eb12a3a4a1489c262dcdd53e9563bfaf652
Decrypted text: Secret meeting at midnight
}
🚨 Security rule 🚨
Never reuse the same (key, nonce) pair. Doing so reveals keystream reuse → instant compromise. ChaCha20 itself is secure, but it becomes unsafe if you repeat a nonce.
🟢 Conclusion
ChaCha20 is the modern workhorse of stream ciphers: fast, extremely simple to implement correctly, and designed to avoid timing leaks by construction.
It performs brilliantly on laptops, phones, microcontrollers, and any platform lacking AES acceleration.
Use a fresh nonce for every encryption, treat keystreams as one-time pads, and avoid reuse. When authenticated encryption is needed, pair ChaCha20 with Poly1305 (ChaCha20-Poly1305).
ChaCha20 combines security, clarity, and performance — a perfect fit for modern Rust systems, network protocols, and embedded environments.
Modes of Operation
Modern cryptography doesn’t encrypt “messages.” It encrypts blocks.
AES, for example, works on 128-bit chunks and nothing else.
So to handle real-world data—files, network packets, logs, telemetry—we need a strategy to link those fixed-size blocks together safely.
That strategy is a mode of operation.
Modes are not decorative. They don’t sit on top of AES; they define its security. Pick the wrong one and your encryption leaks patterns. Pick the right one and you get confidentiality at scale.
Why Modes Exist
A block cipher is deterministic: same key + same block = same output.
That predictability is deadly when encrypting long messages.
Modes solve four problems:
- Randomness — injecting unpredictability so repeated blocks don’t look the same.
- Chaining — connecting blocks so tampering affects more than one piece.
- Streaming — letting you encrypt arbitrary sizes efficiently.
- State — defining how to start, continue, and finish encryption safely.
Overview of Common Modes
| Mode | Secure? | Real Use Case | Notes |
|---|---|---|---|
| ECB | ❌ No | None (except teaching) | Leaks structure. Never use in production. |
| CBC | ⚠️Risky | Legacy protocols | Requires a random IV. Padding mistakes break it. |
| CTR | ✅ Yes | High-speed streaming, networking, I/O | Turns AES into a fast stream cipher. Very robust when nonce-unique. |
| XTS | ✅ Yes | Disk and sector encryption | Designed for storage only, not general messages. |
ECB — The Anti-Example
ECB encrypts each block independently.
Patterns in the plaintext appear in the ciphertext.
Famous example: encrypting the Tux penguin still looks like a penguin.
If you see ECB in a system, assume the designer didn’t understand cryptography.
CBC — The Old Workhorse
CBC chains each block with the previous one using XOR.
If the IV is truly random and padding is handled correctly, it’s fine.
But historically, padding-oracle attacks destroyed its safety in many protocols.
Today, CBC mostly survives in legacy stacks and old file formats.
New designs avoid it.
CTR — The Modern Default
CTR mode transforms AES into a stream cipher.
Instead of encrypting the plaintext blocks directly, it encrypts a counter and XORs the result with the message.
This gives:
- high performance
- random access
- no padding
- clean parallelism
🚨 The only hard rule: never reuse the same (key, nonce) pair.
Break that rule and attackers recover the XOR of two plaintexts.
CTR Example
#![allow(unused)]
fn main() {
use aes::Aes128;
use ctr::cipher::{KeyIvInit, StreamCipher};
let mut cipher = ctr::Ctr128BE::<Aes128>::new(key.into(), nonce.into());
cipher.apply_keystream(&mut buffer); // encrypt or decrypt
}
CTR is simple, fast, and well-suited to network protocols, telemetry pipelines, and embedded systems.
XTS — Built for Storage
XTS is AES-CTR + a “tweak” system tailored to disk sectors. It prevents block relocation attacks and keeps each sector isolated.
XTS shines for full-disk encryption because it resists sector-copy tampering and is purpose-built for storage. It isn’t suitable for general messages or network data, and it requires two independent AES keys.
Use XTS only when you’re encrypting storage blocks.
Key Takeaway
Modes of operation are not optional add-ons. They decide whether your encryption is safe or broken.
-
ECB teaches you what not to do.
-
CBC reminds you legacy systems carry hidden risks.
-
CTR gives you modern, scalable encryption for streaming workloads.
-
XTS protects disks—nothing else.
If you understand modes, you understand how real-world encryption actually works.
-
3DES — DES applied three times, better than DES but now deprecated. More ↩
-
AES — The modern global standard, fast, secure, and hardware-accelerated. More ↩
-
Camellia — Japanese block cipher, secure & AES-comparable. More ↩
-
TLS — protocol securing data in transit (HTTPS, etc.). More ↩
-
FIPS — U.S. cryptographic standards for government/finance. More ↩
-
IPSec — protocol suite for securing IP communications. More ↩
-
WireGuard — modern VPN protocol using ChaCha20-Poly1305 to secure IP traffic. More ↩
-
Non-AES hardware — CPUs without AES instructions, where ChaCha20 is often faster than AES. More ↩
-
Salsa20 — stream cipher by Daniel J. Bernstein; predecessor of ChaCha20, fast and well-studied. More ↩
-
Pseudorandom keystream - sequence of bits/bytes that looks random but is deterministically generated from a secret key (and usually a nonce). More ↩
-
ChaCha20-Poly1305 - AEAD scheme that combines the ChaCha20 stream cipher with the Poly1305 MAC to provide authenticated encryption (confidentiality + integrity). More ↩
Glossary of Terms
AES (Advanced Encryption Standard)
AES is a symmetric-key block cipher standardized by NIST in 2001, designed by Vincent Rijmen and Joan Daemen.
It operates on 128-bit blocks with keys of 128, 192, or 256 bits, and is based on a Substitution–Permutation Network.
AES is fast, secure, and widely implemented in both software and hardware (including CPU instructions).
It remains the global standard for symmetric encryption, used in TLS, SSH, disk encryption, and countless applications.
ECB (Electronic Code Book)
ECB is the simplest block cipher mode of operation: it encrypts each block independently with the same key.
While easy to implement, it is insecure because identical plaintext blocks produce identical ciphertext blocks.
This leaks patterns in the input (e.g., images encrypted with ECB visibly preserve outlines).
Because of this, ECB is almost never used in practice and is considered unsafe.
RSA (Rivest–Shamir–Adleman)
RSA is one of the first public-key cryptosystems, invented in 1977 by Ron Rivest, Adi Shamir, and Leonard Adleman.
It allows secure key exchange, encryption, and digital signatures by relying on the mathematical difficulty of factoring large integers.
RSA keys are typically 2048 or 3072 bits today. While still widely used, modern protocols increasingly migrate to elliptic-curve cryptography (ECC) for better performance and smaller key sizes.
Padding in RSA
RSA encryption without padding is deterministic: encrypting the same message always produces the same ciphertext.
This makes it insecure, as attackers can guess messages or exploit mathematical properties of RSA.
Padding schemes (e.g., PKCS#1 v1.5 or OAEP) add randomness or structured bytes before encryption.
This ensures different ciphertexts for the same plaintext and provides protection against chosen-plaintext or chosen-ciphertext attacks.
Proper padding is essential for RSA’s security in practice.
DES (Data Encryption Standard)
DES is a symmetric-key block cipher standardized in the 1970s by NIST.
It uses a 56-bit key and operates on 64-bit blocks.
Once a global standard, DES is now considered insecure due to its short key length, which makes it vulnerable to brute-force attacks.
3DES (Triple DES)
Triple DES (3DES) is an extension of DES that applies the DES algorithm three times with either two or three different keys.
This increases the effective key length to 112 or 168 bits.
It offered better security than DES but is now deprecated due to its slow performance and known cryptographic weaknesses.
Camellia
Camellia is a modern symmetric-key block cipher developed in Japan by Mitsubishi Electric and NTT.
It offers security and performance comparable to AES, supporting key sizes of 128, 192, and 256 bits.
Camellia is standardized by ISO/IEC and the NESSIE project.
Though less widely used than AES, it is considered secure and suitable for both software and hardware implementations.
TLS (Transport Layer Security)
TLS is a protocol defined by the IETF to secure data during transmission, most notably in HTTPS.
It uses asymmetric encryption for the handshake and key exchange, then switches to symmetric encryption for efficient bulk data encryption.
TLS provides confidentiality, integrity, and authentication for internet communications.
LUKS (Linux Unified Key Setup)
LUKS is the standard format for full disk encryption on Linux, designed by Clemens Fruhwirth.
It provides strong symmetric encryption for protecting data at rest and supports multiple key slots, allowing different passphrases or keys to unlock the same volume.
SSH (Secure Shell)
SSH is a secure remote access protocol invented by Tatu Ylönen in 1995.
It uses asymmetric encryption to authenticate users and symmetric encryption to secure the session once established.
SSH replaced insecure protocols such as Telnet and rlogin and is now the standard for secure remote administration.
FIPS (Federal Information Processing Standards)
FIPS are cryptographic standards defined by NIST for use in U.S. government systems.
FIPS certification ensures that cryptographic algorithms and implementations meet strict security requirements.
It is often required in government, healthcare, and financial sectors to guarantee compliance and trust.
ISO/IEC
ISO (International Organization for Standardization) and IEC (International Electrotechnical Commission) jointly publish international standards for information technology, including cryptography.
These standards ensure interoperability and security across implementations worldwide.
IPSec
IPSec (Internet Protocol Security) is a suite of protocols designed to secure IP communications.
It operates at the network layer, providing encryption, integrity, and authentication for IP packets.
IPSec is widely used for VPNs and site-to-site secure tunnels.
CBC (Cipher Block Chaining)
CBC is a block cipher mode of operation that chains blocks together for added security.
Each plaintext block is XORed with the previous ciphertext block before encryption, preventing identical plaintexts from producing identical ciphertexts.
An Initialization Vector (IV) is required for the first block to ensure uniqueness.
While stronger than ECB, CBC has weaknesses and should be used with caution.
PKCS7
PKCS7 is a padding scheme used in block cipher encryption.
It fills up the last block of plaintext with bytes all set to the value of the number of padding bytes added.
For example, if 4 bytes of padding are needed, the block is filled with 04 04 04 04.
PKCS7 ensures that plaintext lengths align with the cipher’s block size, but improper validation of padding can lead to padding oracle attacks.
Wireguard
WireGuard is a modern VPN protocol and implementation designed by Jason A. Donenfeld.
It focuses on simplicity, performance, and a small codebase, in contrast to traditional VPN stacks like IPsec and OpenVPN.
WireGuard uses a fixed, opinionated set of strong primitives (e.g., Curve25519, ChaCha20-Poly1305, BLAKE2s, and HKDF) arranged in a Noise-based handshake pattern.
It is now integrated into the Linux kernel and widely deployed for site-to-site VPNs, remote access, and privacy-focused applications.
Non-AES Hardware
“Non-AES hardware” refers to CPUs and devices that do not provide hardware acceleration for AES (such as Intel’s AES-NI instructions).
On these platforms, software-only AES can be relatively slow and sometimes harder to implement in a constant-time way.
Stream ciphers like ChaCha20, which use simple add-rotate-xor operations, tend to be faster and easier to harden against timing attacks on such hardware.
Modern protocols therefore often select ChaCha20-Poly1305 as a preferred or fallback cipher suite for clients without efficient AES support.
Salsa20
Salsa20 is a stream cipher designed by Daniel J. Bernstein as part of the eSTREAM project.
It uses simple add-rotate-xor (ARX) operations to generate a pseudorandom keystream from a key, nonce, and counter, making it fast and easy to implement on a wide range of hardware.
Salsa20 was extensively analyzed and gained a strong security reputation, and its design directly inspired ChaCha20, which refines the permutation for better diffusion and performance on modern CPUs.
Pseudorandom Keystream
A pseudorandom keystream is a sequence of bits or bytes that appears random but is generated deterministically from a secret key (and often a nonce/counter).
In a secure stream cipher, this keystream should be computationally indistinguishable from true randomness to anyone who does not know the key.
When the keystream is XORed with plaintext (and never reused with the same key/nonce), it hides the original data while still allowing the receiver, who can regenerate the same keystream, to decrypt it.
ChaCha20-Poly1305
ChaCha20-Poly1305 is an AEAD (Authenticated Encryption with Associated Data) construction that combines the ChaCha20 stream cipher with the Poly1305 message authentication code.
ChaCha20 provides fast, software-friendly encryption using add-rotate-xor operations, while Poly1305 computes a strong one-time MAC over the ciphertext and associated data.
Together, they provide confidentiality, integrity, and authenticity in a single primitive, widely deployed in TLS 1.3, WireGuard, and many modern protocols, especially on devices without AES hardware acceleration.
Cryptographic Concepts Cheatsheet
This cheatsheet is designed as a quick mental map. For detailed explanations and Rust code, see Part 2 and Part 3 of this book.
🔐 Core Principles
| Concept | Summary |
|---|---|
| Kerckhoffs’ Principle | A cryptosystem must be secure even if everything except the key is known |
| Shannon’s Maxim | The enemy knows the system — don’t rely on obscurity |
| Perfect Secrecy | Ciphertext reveals no information without the key (e.g., OTP) |
| Semantic Security | An attacker can’t learn anything new from ciphertext |
🔢 Mathematical Foundations
| Concept | Summary |
|---|---|
| Entropy | A measure of randomness; critical for secure key generation |
| Modular Arithmetic | Math used in most crypto (e.g., a mod n) |
| Finite Fields | Algebraic structures where crypto operations like ECC take place |
| Primes & Factorization | Basis for RSA’s difficulty: factoring large numbers is hard |
| Discrete Log Problem | Hardness assumption behind Diffie-Hellman and ECC |
🔄 Encryption Concepts
| Concept | Summary |
|---|---|
| Symmetric Encryption | Same key used for encryption and decryption (e.g., AES) |
| Asymmetric Encryption | Public-key systems like RSA and ECC |
| Block Cipher | Encrypts fixed-size blocks (e.g., AES-128) |
| Stream Cipher | Encrypts bit-by-bit or byte-by-byte (e.g., ChaCha20) |
| Modes of Operation | Techniques to apply block ciphers to arbitrary-length data (e.g., CBC, GCM) |
| Padding | Fills the last block in block cipher (e.g., PKCS#7) |
🔁 Cryptographic Properties
| Concept | Summary |
|---|---|
| Confusion | Makes the relationship between key and ciphertext complex |
| Diffusion | Spreads the influence of each input bit across the ciphertext |
| Avalanche Effect | Small change in input → large change in output |
| Deterministic Encryption | Same ciphertext for same input+key — not semantically secure |
| Non-deterministic Encryption | Includes randomness (like IVs) to ensure unique ciphertexts |
🔐 Hash Functions
| Concept | Summary |
|---|---|
| Collision Resistance | It’s hard to find two different inputs with the same hash |
| Preimage Resistance | Given a hash, it’s hard to find an input that produces it |
| Second Preimage Resistance | Given an input, hard to find another that hashes to the same value |
| Birthday Paradox | Hash collisions can happen surprisingly early (~2^n/2 complexity) |
| Merkle–Damgård Construction | A method used in many hash functions like SHA-1, SHA-256 |
🔏 Digital Signatures
| Concept | Summary |
|---|---|
| Authenticity | Verifies that the message comes from the claimed sender |
| Non-repudiation | Signer cannot deny having signed |
| ECDSA / RSA Signatures | Algorithms for digital signatures using asymmetric keys |
📡 Protocol Concepts
| Concept | Summary |
|---|---|
| Key Exchange | Securely establishing a shared key over an insecure channel (e.g., Diffie-Hellman) |
| Forward Secrecy | Compromise of one key doesn’t expose past sessions |
| Replay Attack | Re-sending valid data to trick the system again |
| Man-in-the-Middle | Attacker intercepts communication between two parties |
| Nonce | A number used once — prevents replay attacks and ensures uniqueness |
| Initialization Vector (IV) | Random value to ensure unique ciphertexts in block cipher modes |
🛡️ Attack Models
| Concept | Summary |
|---|---|
| Ciphertext-only attack | Attacker only has access to encrypted messages |
| Known-plaintext attack | Attacker knows some plaintext–ciphertext pairs |
| Chosen-plaintext attack | Attacker can choose plaintexts and get their ciphertexts |
| Chosen-ciphertext attack | Attacker can decrypt chosen ciphertexts |
| Side-channel attack | Exploits physical leaks (timing, power, EM radiation) |