Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.



  1. AES. Modern symmetric cipher, fast & secure. More

  2. ECB Mode. Simple block mode, insecure due to patterns. More

  3. RSA. Public-key system for encryption & signatures. More

  4. 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, but 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:

ABA XOR B
000
011
101
110

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 have cargo installed, 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

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

How Ciphers, Hashes, MACs, AEAD Compose

How Ciphers, Hashes, MACs, AEAD Compose

This book introduces several cryptographic primitives. They are not isolated tools. They are building blocks that compose into secure systems.

This section shows how they connect.

Each following chapter will explore one of these components in depth.

The Four Core Primitives

Symmetric Cipher

Provides confidentiality. Encrypts data using a shared secret key.

Hash Function

Provides integrity fingerprinting. Maps arbitrary data to a fixed-size digest. No key.

MAC (Message Authentication Code)

Provides integrity + authenticity. Built using a secret key (often from a hash construction such as HMAC).

AEAD (Authenticated Encryption with Associated Data)

Provides confidentiality + integrity + authenticity in a single construction.

Composition Hierarchy

Hash → Used to build MAC (HMAC)

Cipher + Authenticator → Used to build AEAD

AEAD → Used in modern secure communication protocols

Mental Model

GoalPrimitive
Hide dataSymmetric cipher
Detect ModificationHash
Prove authenticityMAC
Hide + authenticateAEAD

🚨 Security rule 🚨

Never invent your own composition.

❌ Bad idea: encrypt(data) + hash(data)

✅ Correct approach: Use AEAD.

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:

  1. Takes two halves: Left (L) and Right (R)
  2. Computes a function f(R, key)
  3. 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₁):

  1. From encryption, we know L₂ = R₁

    • So: R₁ = L₂
  2. And: R₂ = L₁ ⊕ f(R₁, k)

    • Replace R₁ with L₂
    • R₂ = L₁ ⊕ f(L₂, k)
  3. 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:

  1. Substitution – replace each byte using an S-box (non-linear mapping)
  2. Permutation – reorder bits or bytes to spread influence
  3. 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

My Crate Logo 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/IEC[^isoiec], and widely adopted in security protocols such as TLS, SSH, and IPsec9. AES is available in hardware on most modern CPUs, making it both fast and energy-efficient.


🧪 Code Example: AES-128-CBC Encryption & Decryption (source code)

We’ll use the aes and block-modes crates to encrypt and decrypt a message using AES-128 in CBC mode10 with PKCS711 padding.

#![allow(unused)]
fn main() {
pub fn run_aes_example() {
    use aes::Aes128;
    use cbc::cipher::block_padding::Pkcs7;
    use cbc::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit};
    use cbc::{Decryptor, Encryptor};

    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 WireGuard12, TLS (on non-AES hardware13), mobile apps, messaging protocols, and security libraries. Fast, simple, and naturally resistant to timing attacks.

My Crate Logo Crate used: chacha20

ChaCha20 is a modern stream cipher designed by Daniel J. Bernstein.

It is the streamlined successor to Salsa2014, 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 keystream15.

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-Poly130516.

🧪 Code Example: ChaCha20 Encryption & Decryption (source code)

#![allow(unused)]
fn main() {
pub fn run_chacha20_example() {
    use chacha20::ChaCha20;
    use chacha20::cipher::{KeyIvInit, StreamCipher};

    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:

  1. Randomness: injecting unpredictability so repeated blocks don’t look the same.
  2. Chaining: connecting blocks so tampering affects more than one piece.
  3. Streaming: letting you encrypt arbitrary sizes efficiently.
  4. State: defining how to start, continue, and finish encryption safely.

Overview of Common Modes

ModeSecure?Real Use CaseNotes
ECB❌ NoNone (except teaching)Leaks structure. Never use in production.
CBC⚠️RiskyLegacy protocolsRequires a random IV. Padding mistakes break it.
CTR✅ YesHigh-speed streaming, networking, I/OTurns AES into a fast stream cipher. Very robust when nonce-unique.
XTS✅ YesDisk and sector encryptionDesigned for storage only, not general messages.

ECB (Electronic Codebook): 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 (Cipher Bloc Chaining): 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 (Counter Mode): 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 (XEX-based (XOR-Encrypt-XOR-based) Tweaked CodeBook with Ciphertext Stealing): 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.

AES vs. ChaCha20: which one should you choose?

Both AES and ChaCha20 are modern, secure, and widely deployed symmetric encryption algorithms. You will encounter them everywhere: in HTTPS, VPNs, mobile apps, cloud storage, and embedded systems.

Yet, choosing between them is not a matter of “which is stronger”, both are cryptographically secure. The real difference lies in performance, implementation safety, and hardware availability.

AES (Advanced Encryption Standard)

  • Type: Block cipher
  • Strength: 128, 192, or 256-bit keys
  • Typical Modes Depends on the mode of operation used
  • Performance: Extremely fast when hardware acceleration is available
  • Used In TLS, disk encryption, databases, government systems

✅ Strengths:

  • Decades of analysis and trust
  • Hardware acceleration on most modern CPUs
  • Excellent for high-throughput systems

⚠️ Weaknesses:

  • More complex to implement correctly
  • Vulnerable to timing and cache side-channel attacks if implemented poorly

ChaCha20

  • Type: Stream cipher
  • Key Size: 256-bit
  • Performance: Fast on all CPUs, even without hardware support
  • Used In: Mobile apps, VPNs, embedded systems

✅ Strengths:

  • Very simple design
  • Timing-attack resistant by design
  • Outstanding performance on low-power devices
  • Fewer catastrophic implementation mistakes

⚠️ Weaknesses:

  • No native hardware acceleration (yet)
  • Less common in legacy enterprise systems

🟢 Conclusion

Use AES-GCM when you have hardware acceleration or when you need standardized enterprise compatibility

Use ChaCha20-Poly1305 when you target mobile, embedded, or low-power devices or you want maximum implementation safety or you care about side-channel resistance

Important truth: Both are safe when used correctly. ChaCha20 is often preferred today when simplicity and portability matter more than raw throughput.

A Glimpse at Key Derivation

What Goes Wrong Without a KDF (Real Consequence)

Let’s say a beginner does this:

  • Password = “mysecret123”
  • Key for AES = “mysecret123”

This is cryptographically broken, even if AES itself is mathematically perfect.

Because:

  • The password has low entropy
  • It exists in wordlists
  • It can be tested at billions of attempts per second
  • The attacker does not break AES, they simply break the password

This is the single biggest real-world cryptographic failure.

A Key Derivation Function (KDF) converts a weak human secret into a strong cryptographic key suitable for AES or ChaCha20. It is a conversion machine between two worlds:

Human Password → KDF → Cryptographic Key → AES / ChaCha20

A KDF protects symmetric encryption by:

  • Adding randomness (salt17)
  • Applying slow, repeated cryptographic operations
  • Making brute-force attacks economically unviable

Common KDF Families

  • PBKDF218 → Legacy compatibility
  • Argon219 → Modern password-based security standard
  • scrypt20 → Memory-hard defense against hardware attacks
  • HKDF21 → Safe key expansion from an already strong secret

Important Distinction

  • Human password → Argon2 / scrypt
  • Existing cryptographic secret → HKDF

🚨 They solve different security problems and must never be confused.

🧪 Code Example: HKDF (source code)

#![allow(unused)]
fn main() {
pub fn run_hkdf_example() {
    use hkdf::Hkdf;
    use sha2::Sha256;

    // salt: optional, non-secret, should be random per context/session
    let salt: [u8; 16] = *b"unique-salt-1234";

    // ikm: Input Keying Material (must be high-entropy)
    // In real systems this comes from a handshake secret, a shared key, etc.
    let ikm: [u8; 32] = *b"0123456789ABCDEF0123456789ABCDEF";

    let hk = Hkdf::<Sha256>::new(Some(&salt), &ikm);

    let mut okm = [0u8; 32];
    hk.expand(b"encryption key", &mut okm).unwrap();

    println!("OKM: {:02x?}", okm);
}
}

Output:

#![allow(unused)]
fn main() {
OKM: [0c, fd, 4c, 90, 3f, 4e, d8, 7e, 3f, d3, 28, e6, da, b7, f7, d6, aa, 9d, 76, df, 66, be, 5d, f1, a0, 17, 0b, 85, f4, 72, 39, c0]
}

HKDF is used to derive multiple secure keys from a single master secret.

This allows you to:

  • Derive separate keys for encryption, authentication, and signatures
  • Avoid key reuse
  • Maintain cryptographic isolation

🚨 Critical Security Rule

Never use user passwords directly as encryption keys. Ever.

Always apply:

  • a salt
  • a slow KDF
  • a modern hash function

Without this step, your entire encryption system is compromised before it even begins.


  1. DES: early symmetric cipher (56-bit), now insecure. More

  2. 3DES: DES applied three times, better than DES but now deprecated. More

  3. AES: The modern global standard, fast, secure, and hardware-accelerated. More

  4. Camellia: Japanese block cipher, secure & AES-comparable. More

  5. TLS: protocol securing data in transit (HTTPS, etc.). More

  6. LUKS: Linux standard for full disk encryption. More

  7. SSH: secure remote access protocol. More

  8. FIPS: U.S. cryptographic standards for government/finance. More

  9. IPSec: protocol suite for securing IP communications. More

  10. CBC: block cipher mode, chains blocks for security. More

  11. PKCS7: padding scheme for block ciphers. More

  12. WireGuard: modern VPN protocol using ChaCha20-Poly1305 to secure IP traffic. More

  13. Non-AES hardware: CPUs without AES instructions, where ChaCha20 is often faster than AES. More

  14. Salsa20: stream cipher by Daniel J. Bernstein; predecessor of ChaCha20, fast and well-studied. More

  15. Pseudorandom keystream: sequence of bits/bytes that looks random but is deterministically generated from a secret key (and usually a nonce). More

  16. ChaCha20-Poly1305: AEAD scheme that combines the ChaCha20 stream cipher with the Poly1305 MAC to provide authenticated encryption (confidentiality + integrity). More

  17. Salt: A random value added to a password before key derivation to prevent identical passwords from producing identical keys and to defeat precomputed attacks such as rainbow tables.. More

  18. PBKDF2 (Password-Based Key Derivation Function): The standardized successor to PBKDF1 for deriving cryptographic keys from passwords.. More

  19. Argon2 (named after the mythological ship Argo and its crew, the Argonauts): The modern standard for password hashing and key derivation, designed to resist GPU and ASIC attacks using memory-hard computation. More

  20. Scrypt: A memory-hard password-based key derivation function built to make large-scale hardware brute-force attacks expensive and inefficient. Older than Argon2 but still widely used. More

  21. HKDF (HMAC-based Key Derivation Function): A key derivation function for expanding an already strong secret into multiple independent cryptographic keys using hash functions. Used for key separation and key hygiene, not for protecting weak passwords. More

Cryptographic Hashes — SHA-2, BLAKE3

Cryptographic Hashes: SHA-2, BLAKE3 & beyond

🔐 Used in: password storage, digital signatures, blockchains, TLS, integrity checks

✅ One-way, deterministic, and foundational to all modern cryptography

What Is a Cryptographic Hash?

A cryptographic hash function takes arbitrary input data and produces a fixed-size output, called a digest.

Key properties:

  • Deterministic: same input → same output
  • One-way: infeasible to recover the input
  • Collision-resistant: infeasible to find two inputs with the same hash
  • Avalanche effect: tiny input change → completely different output

Hash functions are not encryption.There is no key. There is no decryption.

They exist to answer one question: “Has this data changed and can I trust it?”

What Hashes Are Used For (Real Systems)

  • Password storage (never store plaintext passwords)
  • Digital signatures (hash → sign)
  • TLS certificates (hashing messages before authentication)
  • Package managers (verify downloads)
  • Git (content-addressed storage)
  • Blockchains (immutability and chaining)

If symmetric ciphers protect confidentiality, hashes protect integrity and identity.

One-Way by Design

Consider this:

"hello"  →  2cf24dba5fb0a30e26e83b2ac5b9e29e...
"hellO"  →  0d4a1185eecb25c46b7a5d3bca4f4f8b...

One flipped bit >> Completely different output

This is not accidental, it’s the core security property.

SHA-2: the conservative tandard

💡 Used in TLS, certificates, package signing, blockchains. Stable, conservative, and widely trusted

My Crate Logo Crate used: sha2

SHA-21 is a family of hash functions standardized by NIST2:

  • SHA-2243
  • SHA-2564
  • SHA-3845
  • SHA-5126

SHA-256 is the most common. It is slow enough to be secure, fast enough for general use and extremely well analyzed. SHA-2 is boring and that’s a compliment.

🧪 Code Example: SHA-256 Hashing (source code)

#![allow(unused)]
fn main() {
pub fn run_sha256_example() {
    use sha2::{Digest, Sha256};

    let mut hasher = Sha256::new();
    Digest::update(&mut hasher, b"hello world");

    let result = hasher.finalize();
    println!("SHA-256: {:x}", result);
}
}

Output:

SHA-256: b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9

Same input. Same output. Always.

🟢 Conclusion SHA-2 is the safe default. If you don’t know which hash to use, SHA-256 will almost never be the wrong answer.

Why Hashes Are Not Password Protection

If you do this, you are already vulnerable.

#![allow(unused)]
fn main() {
hash(password)
}

because Hashes are fast, attackers can try billions per second, rainbow tables exist.

We fix this with KDFs7 (Argon28, scrypt9). See Key Derivation section

🚨 Fast hashes are dangerous for passwords.

BLAKE3: modern, fast, and arallel

💡 Used in modern systems, content addressing, integrity pipelines. Extremely fast, secure, and designed for today’s hardware

My Crate Logo Crate used: blake3

BLAKE310 is the modern evolution of BLAKE2:

  • Cryptographically secure
  • Massively parallel
  • Faster than SHA-2
  • Designed for multicore CPUs and SIMD11
  • Unlike SHA-2, BLAKE3 was designed after modern hardware existed.

It also supports:

  • Streaming
  • Incremental hashing12
  • Keyed hashing13
  • Extendable output (XOF)14

🧪 Code Example: BLAKE3 Hashing (source code)

#![allow(unused)]
fn main() {
pub fn run_blake3_example() {
    use blake3;

    let hash = blake3::hash(b"hello world");
    println!("BLAKE3: {}", hash);
}
}

Output:

BLAKE3: d74981efa70a0c880b8d8c1985d075dbcbf679b99a5f9914e5aaf96b831a9e24

SHA-2 vs. BLAKE3: which should you use?

PropertySHA-256BLAKE3
Standardized✅ Yes❌ Not NIST
Speed⚠️ Moderate🚀 Extremely fast
Parallelism❌ Limited✅ Built-in
Maturity🧱 Very conservative🧠 Modern design
Simplicity✅ Simple✅ Simple
Use cases✅ Compliance✅ Performance
✅ Interoperability✅ Content hashing
✅ Modern systems

Collision Resistance: what “Breaking a Hash” really means

Breaking a hash means one of two things:

  • Finding a collision15 faster than brute force16
  • Finding a preimage17 faster than brute force

For SHA-256:

  • No practical attacks exist
  • Collision cost ≈ 2¹²⁸
  • Preimage cost ≈ 2²⁵⁶

That is astronomically infeasible.

💡 Most real-world failures come from misuse, not broken math.

Hashes in Real Protocols

Hashes are rarely used alone.

They appear inside:

  • HMAC: keyed hashing for authentication
  • HKDF: key derivation
  • Digital signatures: sign(hash(message))
  • Merkle trees18: integrity structures
  • Password KDFs: slow hashing

🟢 Conclusion

Hash functions provide integrity, not secrecy.

They are one-way by design:

  • SHA-2 is conservative and universal
  • BLAKE3 is modern, fast, and scalable Hashes alone are not password protection

Cryptographic hashes are the glue of modern security.

They verify data, anchor trust, protect identities, and power nearly every cryptographic construction you’ll encounter. If encryption hides secrets, hashes define truth.


  1. SHA-2 (Secure Hash Algorithm 2): NIST-standardized family of cryptographic hash functions including SHA-224, SHA-256, SHA-384, and SHA-512. More

  2. NIST (National Institute of Standards and Technology): U.S. authority responsible for standardizing widely used cryptographic algorithms. More

  3. SHA-224: SHA-2 variant producing a 224-bit digest, derived from SHA-256. More

  4. SHA-256: Most widely deployed SHA-2 hash function, offering strong collision and preimage resistance. More

  5. SHA-384: SHA-2 variant optimized for 64-bit systems with higher security margin. More

  6. SHA-512: SHA-2 variant with the largest output size and best performance on 64-bit architectures. More

  7. KDF (Key Derivation Function): Function that transforms a weak or strong secret into cryptographically secure keys for use with symmetric encryption. More

  8. Argon2 (named after the mythological ship Argo and its crew, the Argonauts): The modern standard for password hashing and key derivation, designed to resist GPU and ASIC attacks using memory-hard computation. More

  9. Scrypt: A memory-hard password-based key derivation function built to make large-scale hardware brute-force attacks expensive and inefficient. Older than Argon2 but still widely used. More

  10. BLAKE3 (named after the BLAKE hash family): Modern cryptographic hash optimized for speed, parallelism, and implicity on modern hardware. More

  11. SIMD (Single Instruction, Multiple Data): CPU execution model that applies the same operation to multiple data elements in parallel for high performance. More

  12. Incremental hashing: Hashing method that processes input in chunks, allowing streaming and large data hashing without loading everything into memory. More

  13. Keyed hashing: Hash function variant that incorporates a secret key to provide message authentication and integrity guarantees. More

  14. Extendable output (XOF): Hash construction that can generate an arbitrary-length output stream from a single input and key. More

  15. Collision: Existence of two distinct inputs that produce the same hash output, undermining a hash function’s uniqueness guarantee. More

  16. Brute force: Exhaustive attack that tries all possible inputs or keys until the correct one is found. More

  17. Preimage: Original input corresponding to a given hash output; preimage resistance means it is infeasible to recover this input. More

  18. Merkle tree: Tree data structure where each node is a hash of its children, enabling efficient and secure data integrity verification. More

MACs & AEAD — HMAC, Poly1305, AES-GCM

MACs & AEAD: HMAC, Poly1305, AES-GCM

🔐 Used in: TLS, APIs, cookies, tokens, VPNs, messaging apps

✅ Essential for integrity and authenticity, not optional.

Encryption hides data. But encryption alone does not stop attackers from modifying it.

Modern cryptography requires two guarantees:

  • Confidentiality → Nobody can read the data
  • Integrity & Authenticity → Nobody can tamper with it unnoticed

This chapter is about the second half: the part beginners forget, and attackers exploit.

Why Encryption Alone Is Not Enough

A common beginner mistake: “My data is encrypted, so it’s secure.” That’s false.

If an attacker can flip bits in your ciphertext and you don’t detect it, your system is broken.

Real consequences:

  • Modified database records
  • Forged API requests1
  • Token manipulation2
  • Padding-oracle exploits3
  • Silent data corruption4

Encryption without integrity is malleable by default.

This is why MACs and AEAD exist.

MACs (Message Authentication Codes)

A MAC is a cryptographic checksum computed using a secret key.

It answers one critical question:

“Was this message produced by someone who knows the secret key and was it modified?”

MACs provide:

  • Integrity (detect modification)
  • Authenticity (prove origin)
  • No confidentiality (data remains readable)

A MAC is not encryption.

message + secret key → MAC

How to verify:

message + secret key → recomputed MAC → compare

If even one bit changes, verification fails.

HMAC (Hash-based Message Authentication Code): the standard MAC construction

💡 Used in JWT5, APIs6, OAuth7, AWS signing8, TLS internals9

Stable, conservative, battle-tested

My Crate Logo Crate used: hmac

HMAC allows two parties sharing a secret key to authenticate a message and detect any tampering, without requiring encryption.

HMAC combines:

  • a cryptographic hash function (e.g. SHA-256)
  • a secret key
  • a hardened construction resistant to length-extension attacks10

Unlike naïve hash(key || message), HMAC is safe.

🧪 Code Example: HMAC-SHA256 (source code)

#![allow(unused)]
fn main() {
pub fn run_hmac_example() {
    use hkdf::hmac::Hmac;
    use hkdf::hmac::digest::Mac;
    use sha2::Sha256;

    type HmacSha256 = Hmac<Sha256>;

    let key = b"super-secret-key";
    let message = b"transfer=1000&to=alice";

    let mut mac = <HmacSha256 as Mac>::new_from_slice(key).unwrap();
    Mac::update(&mut mac, message);

    let tag = mac.finalize().into_bytes();

    // Verification
    let mut verify = <HmacSha256 as Mac>::new_from_slice(key).unwrap();
    Mac::update(&mut verify, message);
    Mac::verify_slice(verify, tag.as_slice()).unwrap();
}
}

If the message or tag is altered, verification fails immediately.

🚨 Critical rule
Never compare MACs with ==. Always use constant-time verification APIs.

🟢 Conclusion

HMAC is conservative, widely deployed, and extremely hard to misuse.

If you need integrity without encryption, HMAC is the right tool.

Poly1305: one-time MAC for modern crypto

💡 Used in ChaCha20-Poly1305, TLS 1.3, WireGuard

Extremely fast, simple, and timing-safe

My Crate Logo Crate used: poly1305

Poly1305 is a modern MAC designed by Daniel J. Bernstein.

Key properties:

  • One-time MAC (key must never be reused)
  • Constant-time11 by design
  • Very small and fast
  • Designed to pair with stream ciphers12

Poly1305 is almost never used alone. It is generated from a cipher keystream, usually ChaCha20.

🧪 Code Example: Poly1305 (source code)

#![allow(unused)]
fn main() {
pub fn run_poly1305_example() {
    use poly1305::{
        Poly1305,
        universal_hash::{KeyInit, UniversalHash},
    };

    let key = [0u8; 32]; // placeholder: must be a one-time key in real use
    let message = b"authenticated message";

    let mut mac = Poly1305::new(&key.into());
    mac.update_padded(message);

    let tag = mac.finalize();
    let _tag_bytes: [u8; 16] = tag.into(); // if plain array wished
}
}
🚨 Critical rule
Poly1305 keys must never be reused. Reuse = forgery.

🟢 Conclusion

Poly1305 is fast, elegant, and extremely secure when used correctly, but it must be paired with a cipher that guarantees fresh keys.

AEAD: authenticated encryption (the right way)

Modern cryptography does not ask: “Should I encrypt or authenticate?”.

The answer is: Both. Together. Always.

AEAD guarantees:

  • Confidentiality
  • Integrity
  • Authenticity
  • Optional authentication of unencrypted metadata

If authentication fails → decryption must not happen.

AES-GCM: the enterprise standard AEAD

💡 Used in TLS, HTTPS, databases, cloud storage, hardware security modules, hardware-accelerated and widely standardized

My Crate Logo Crates used: aes-gcm

AES-GCM13 combines:

  • AES block cipher14
  • CTR mode15 (for encryption)
  • GHASH16 (for authentication)

🧪 Code Example: AES-256-GCM (source code)

#![allow(unused)]
fn main() {
pub fn run_aes256gcm_example() {
    use aes_gcm::aead::{Aead, KeyInit};
    use aes_gcm::{Aes256Gcm, Key, Nonce};

    let key = Key::<Aes256Gcm>::from_slice(&[0u8; 32]);
    let cipher = Aes256Gcm::new(key);

    let nonce = Nonce::from_slice(&[0u8; 12]);
    let ciphertext = cipher.encrypt(nonce, b"secret data".as_ref()).unwrap();

    let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).unwrap();
    println!("AES256-GCM: {:#?}", plaintext);
}
}

Output:

AES256-GCM: [
    115,
    101,
    99,
    114,
    101,
    116,
    32,
    100,
    97,
    116,
    97,
]
🚨 Critical rule
Never reuse a nonce with the same key. Ever. GCM nonce reuse = total compromise.

🟢 Conclusion

AES-GCM is extremely fast on modern CPUs and ideal for servers, but nonce management must be flawless.

ChaCha20-Poly1305: the safer default

💡 Used in WireGuard17, mobile apps, embedded systems, TLS fallback18

Designed for misuse resistance19

ChaCha20-Poly130520 combines:

  • ChaCha2021 (encryption)
  • Poly130522 (authentication)
  • A clean, unified AEAD API23

Advantages:

  • Constant-time11 by design
  • No cache-timing24 issues
  • Excellent performance everywhere
  • Fewer catastrophic mistakes

🧪 Code Example: ChaCha20-Poly1305 (source code)

#![allow(unused)]
fn main() {
pub fn run_chacha20poly1305_example() {
    use chacha20poly1305::aead::{Aead, KeyInit};
    use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};

    let key = Key::from_slice(&[0u8; 32]);
    let cipher = ChaCha20Poly1305::new(key);

    let nonce = Nonce::from_slice(&[0u8; 12]);
    let ciphertext = cipher.encrypt(nonce, b"secret data".as_ref()).unwrap();

    let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).unwrap();
    println!("Chacha20-Poly1305: {:#?}", plaintext);
}
}

Output:

Chacha20-Poly1305: [
    115,
    101,
    99,
    114,
    101,
    116,
    32,
    100,
    97,
    116,
    97,
]

🟢 Conclusion

ChaCha20-Poly1305 is often the best default choice: safer APIs, portable performance, and strong resistance to side-channel attacks25.

MAC vs AEAD: what should you use?

SituationUse
Integrity onlyHMAC
Streaming cipherPoly1305 (with ChaCha20)
General encryptionAEAD
Enterprise systemsAES-GCM
Mobile / embeddedChaCha20-Poly1305
🚨 Critical rule
If encryption is involved → always use AEAD. Rolling your own MAC + encryption is a mistake.

🟢 Conclusion

Encryption alone only hides data, it doesn’t stop attackers from changing it.

A MAC teaches the integrity lesson: “was this message modified, and did it come from someone with the key?”

Poly1305 shows modern MAC design: fast and safe when used correctly (one-time keys).

AEAD (AES-GCM, ChaCha20-Poly1305) combines encryption + authentication so tampering is detected and decryption is refused.

Practical rule: if you encrypt data in real systems, use AEAD by default and treat nonce/key management as a first-class security requirement.


  1. Forged API Request: Attack where an adversary crafts or alters API requests to impersonate a legitimate client or bypass authentication and authorization controls. More

  2. Token Manipulation: Tampering with authentication or session tokens (JWT, cookies, API keys) to escalate privileges, extend validity, or impersonate another user. More

  3. Padding Oracle Attack: Cryptographic attack exploiting padding validation errors in block ciphers to progressively recover plaintext or forge valid ciphertexts. More

  4. Silent Data Corruption: Undetected modification of data caused by hardware faults, software bugs, or transmission errors, leading to integrity loss without immediate failure signals. More

  5. JWT (JSON Web Token): Compact, URL-safe token format used to securely transmit signed or encrypted claims for authentication and authorization in distributed systems. More

  6. API (Application Programming Interface): Contract defining how software components communicate via structured requests, responses, authentication, and versioned endpoints. More

  7. OAuth 2.0: Industry-standard authorization framework enabling delegated access to protected resources without sharing user credentials with third-party applications. More

  8. AWS Request Signin: Cryptographic mechanism (SigV4) that authenticates and authorizes AWS API requests using HMAC-based signatures derived from secret credentials. More

  9. TLS Internals: Cryptographic protocols and handshake mechanisms that provide authentication, key exchange, confidentiality, and integrity for secure network communications. More

  10. Length-Extension Attack: Hash function vulnerability allowing attackers to append data to a hashed message and compute a valid hash without knowing the secret. More

  11. Constant-Time Algorithm: Implementation strategy where execution time is independent of secret data, preventing timing side-channel information leakage. More ↩2

  12. Stream Cipher: Symmetric encryption primitive that generates a pseudorandom keystream and encrypts data by XORing it with plaintext bytes. More

  13. AES-GCM: Standard AEAD using AES-CTR for encryption plus GHASH for authentication; fast but requires unique nonces per key. More

  14. AES Block Cipher: AES is a 128-bit block cipher primitive; you still need a mode/AEAD (CTR, GCM, etc.) to encrypt messages safely. More

  15. CTR Mode: Counter mode turns a block cipher into a keystream generator; it’s fast but malleable and must be paired with authentication. More

  16. GHASH: The polynomial-hash authenticator inside AES-GCM, computed over AAD and ciphertext to help produce the authentication tag. More

  17. WireGuard: Modern VPN protocol using a small, fixed set of strong primitives (notably ChaCha20-Poly1305) for high performance and simplicity. More

  18. TLS Fallback: Choosing an alternative cipher suite during TLS negotiation (often ChaCha20-Poly1305 on non-AES hardware) without sacrificing authenticated encryption. More

  19. Misuse Resistance: Design goal where common mistakes (especially nonce reuse) cause less catastrophic failure; still not “safe to misuse,” just safer. More

  20. ChaCha20-Poly1305: Widely deployed AEAD combining ChaCha20 encryption with Poly1305 authentication; strong, fast in software, and common in TLS and WireGuard. More

  21. ChaCha20: Fast ARX-based stream cipher; encrypts by XORing a generated keystream with plaintext; nonce reuse under the same key breaks confidentiality. More

  22. Poly1305: One-time MAC producing a 16-byte tag; secure only if the Poly1305 key is never reused, typically derived fresh per message. More

  23. AEAD API: Standard encrypt/decrypt interface using (key, nonce, AAD, plaintext/ciphertext) and returning success or authentication failure; never release plaintext on failure. More

  24. Cache Timing: Timing leakage caused by secret-dependent CPU cache accesses (e.g., lookup tables), which can reveal keys; mitigated by constant-time code and AES-NI. More

  25. Side-Channel Attack: Attack that exploits leaked information from an implementation (timing, cache, power, EM, faults) rather than breaking the underlying cryptography. More

Digital Signatures — RSA, Ed25519, ECDSA

Digital Signatures: Ed25519, ECDSA, RSA

🔐 Used in: TLS certificates, SSH, software updates, package signing, blockchains, identity systems

✅ A digital signature is public proof that this exact message came from the holder of a private key.

Digital signatures solve a problem encryption does not:

  • Encryption answers: “Who can read this?”
  • Signatures answer: “Who wrote this, and was it changed?”

If you can verify a signature, you can trust:

  • Authenticity: the signer had the private key
  • Integrity: the message was not modified

That is why signatures are the backbone of:

  • certificate chains1
  • secure updates
  • authenticated key exchange2 (e.g., TLS handshake signatures)
  • “verify before you run” security3

The Basic Model: sign → verify

In almost every signature scheme, you can think like this:

signature = Sign(private_key, message)
ok = Verify(public_key, message, signature)

Anyone can verify. Only the private-key holder can sign.

Hash-Then-Sign (why hashes show up everywhere)

Signatures are almost never computed over “big messages” directly.

Instead, protocols use:

digest = Hash(message)
signature = Sign(private_key, digest)

Why?

  • Hashes are fixed-size (fast to sign, easy to transport)
  • Hashes bind the entire message (one-bit change → totally different digest)
  • Collision resistance4 matters: signatures inherit the security of the hash

This is why you’ll constantly see “SHA-256 + ECDSA” or “SHA-256 + RSA-PSS”.

Ed25519: modern default for new systems

My Crate Logo Crate used: ed25519-dalek

Ed25519 is an elliptic-curve5 signature scheme designed to be:

  • fast
  • hard to misuse
  • deterministic (no fragile per-signature randomness like classic ECDSA)

The name “Ed25519” refers to an Edwards-curve signature scheme using the Curve25519 family with a 255-bit prime field.

It’s an excellent default when you control both ends of a system (new protocol, internal services, new products).

🧪 Code Example: Ed25519 (source code)

#![allow(unused)]
fn main() {
pub fn run_ed25519_example() {
    use ed25519_dalek::{Signer, SigningKey, Verifier};
    use rand::rngs::OsRng;

    let signing_key = SigningKey::generate(&mut OsRng);
    let verifying_key = signing_key.verifying_key();

    let message = b"sealed-in-rust: ed25519 signature demo";
    let signature = signing_key.sign(message);

    verifying_key.verify(message, &signature).unwrap();
    println!("Ed25519 signature verified.");
}
}

Output:

Ed25519 signature verified.

🚨 Critical rule: Always verify signatures with the library’s verification API (constant-time comparisons6 and correct checks7). Never compare signatures manually.

ECDSA: widely deployed, easy to get wrong

My Crate Logo Crate used: p256

ECDSA is the “classic” elliptic-curve signature family used everywhere (TLS, hardware tokens, many enterprise systems). ECDSA stands for Elliptic Curve Digital Signature Algorithm (DSA adapted to elliptic curves).

The danger in ECDSA is the per-signature secret nonce k:

  • If k is ever reused → the private key can be recovered
  • If k is biased/predictable → the private key can leak

Many libraries use deterministic nonces8 (RFC 6979) to reduce RNG foot-guns9, but the easiest safe rule is: use well-audited libraries and don’t customize nonce generation.

🧪 Code Example: ECDSA (source code)

#![allow(unused)]
fn main() {
pub fn run_ecdsa_example() {
    use p256::ecdsa::Signature;
    use p256::ecdsa::SigningKey;
    use p256::ecdsa::signature::{Signer, Verifier};
    use rand::rngs::OsRng;

    let signing_key = SigningKey::random(&mut OsRng);
    let verifying_key = signing_key.verifying_key();

    let message = b"sealed-in-rust: ecdsa(p-256) signature demo";
    let signature: Signature = signing_key.sign(message);

    verifying_key.verify(message, &signature).unwrap();
    println!("ECDSA P-256 signature verified.");
}
}

Output:

ECDSA P-256 signature verified.

RSA signatures: compatibility workhorse (use RSA-PSS)

My Crate Logo Crate used: rsa

RSA is older than ECC10, but it’s still deeply embedded in the world (certificate ecosystems, legacy devices, enterprise).

Important RSA signature reality:

  • Raw RSA is not a signature scheme
  • You must use a padding scheme designed for signatures

Today, that means:

  • Prefer RSA-PSS11 (modern, safer)
  • Avoid “textbook RSA”12 entirely

🧪 Code Example: RSA (source code)

#![allow(unused)]
fn main() {
pub fn run_rsa_example() {
    use rand::rngs::OsRng;
    use rsa::RsaPrivateKey;
    use rsa::pss::{BlindedSigningKey, VerifyingKey};
    use rsa::signature::{RandomizedSigner, Verifier};
    use sha2::Sha256;

    let mut rng = OsRng;
    let private_key = RsaPrivateKey::new(&mut rng, 2048).unwrap();
    let public_key = private_key.to_public_key();

    let signing_key = BlindedSigningKey::<Sha256>::new(private_key);
    let verifying_key = VerifyingKey::<Sha256>::new(public_key);

    let message = b"sealed-in-rust: rsa-pss signature demo";
    let signature = signing_key.sign_with_rng(&mut rng, message);

    verifying_key.verify(message, &signature).unwrap();
    println!("RSA-PSS signature verified.");
}
}

Output:

RSA-PSS signature verified.

Which one should you choose?

If you’re designing something new: Ed25519 is usually the best default

If you need broad TLS13 / enterprise / hardware ecosystem compatibility: ECDSA (P-256) is common

If you must support legacy systems or existing PKI deployments14: RSA (prefer PSS) still shows up everywhere

Common Mistakes (how signatures fail in real life)

  • Confusing signatures with encryption: signing does not hide data; it proves origin and integrity.
  • Verifying the wrong thing: always verify the exact bytes that were signed (canonical encoding matters).
  • Algorithm confusion: never accept “any algorithm”; allow-list what you expect (JWT-style mistakes15 are common)
  • Bad randomness (ECDSA): nonces are a single-point-of-failure
  • Using outdated RSA modes: “RSA without padding” is broken; “RSA-PSS” is the modern baseline

🟢 Conclusion

Digital signatures are the public-key primitive for authenticity. Hash-then-sign binds meaning, not just bytes.

Ed25519 is the modern default.

ECDSA is everywhere but fragile around nonces.

RSA is still common, but only with the right padding (prefer PSS).


  1. Certificate chain: ordered set of certificates where each certificate is signed by the next/higher authority up to a trusted root, used to bind a public key to an identity. More

  2. Authenticated key exchange (AKE): key exchange that also authenticates the peer (via signatures, certificates, or PSKs), preventing active man-in-the-middle attacks. More

  3. Verify-before-run security: practice of verifying a signature on code or artifacts (updates, packages, binaries) before installing or executing them. More

  4. Collision: two different inputs producing the same hash; collision resistance is critical for signature security. More

  5. Elliptic curve: the mathematical structure used by ECC; curves over finite fields provide the group operations behind ECDSA and Ed25519. More

  6. Constant-time comparison: comparing values (MACs, signatures, hashes) without early exits so timing does not leak information about secret-dependent equality. More

  7. Correct checks (verification hygiene): strict signature verification rules like algorithm allow-lists, parameter validation, and rejecting malformed/malleable signatures. More

  8. Deterministic nonce: deriving the per-signature nonce from the private key and message (e.g., RFC 6979) to reduce dependence on external randomness at signing time. More

  9. RNG foot-gun: common randomness mistake (weak seeding, predictability, reuse) that silently breaks cryptography, especially nonce-based signatures like ECDSA. More

  10. ECC (elliptic-curve cryptography): public-key cryptography built on elliptic-curve groups, typically giving smaller keys and faster operations than RSA at comparable security. More

  11. RSA-PSS: the modern RSA signature padding scheme (“Probabilistic Signature Scheme”), designed to be robust against signature forgeries and recommended over older RSA signature modes. More

  12. Textbook RSA: raw RSA without a padding scheme; deterministic and malleable, and therefore insecure for both encryption and signatures. More

  13. Broad TLS compatibility: choosing signature algorithms/parameters that work across a wide range of TLS clients, servers, libraries, and devices (often constrained by legacy support). More

  14. PKI deployment: the real-world operational certificate ecosystem (CAs, issuance policies, revocation, client trust stores) that shapes which algorithms can be used in practice. More

  15. JWT (JSON Web Token): signed token format; secure usage requires strict algorithm allow-lists and proper signature verification. More

Key Derivation Functions — Argon2, scrypt

Key Derivation Functions: Argon2, scrypt

🔐 Used in: password storage, login systems, encrypted vaults, disk encryption, backup tools

✅ Essential anytime a password becomes a key.

A password is not a cryptographic key. It’s short, predictable, and attacker-friendly.

So when you see requirements like:

  • “derive an encryption key from a passphrase”
  • “store user passwords safely”
  • “turn a shared secret into multiple keys”

You need a Key Derivation Function (KDF)1.

This chapter focuses on the most important real-world case: password-based key derivation using Argon2id2 and scrypt3.

The Problem: Passwords Are Weak Secrets

If an attacker steals your password database (or an encrypted vault file), they don’t need to “hack your login”.

They can do offline guessing: try billions of password candidates on their own hardware until one works.

Your goal is simple: make each password guess expensive.

That’s what password KDFs do:

  • they turn one password guess into lots of CPU work
  • and (for Argon2/scrypt) lots of memory

Salt: The Non-Secret Value That Stops Mass Attacks

Every password hash / derived key must use a unique, random salt4.

Salt is:

  • public (stored next to the hash / ciphertext)
  • unique per user / per encrypted file
  • what prevents “same password ⇒ same output” across victims

Without salts, attackers can reuse work at scale.

What You Should Store (And What You Must Not)

For password storage, you store:

  • the algorithm + parameters (e.g. Argon2id time/memory settings)
  • the salt
  • the resulting password hash

You must never store:

  • the plaintext password
  • a fast hash of the password (like SHA-256(password))

Modern libraries typically serialize all of this into a single string (e.g. a PHC-style5 hash string).

Argon2id: the modern default

💡 Used in password managers, servers, vaults, modern security guidance. Memory-hard, tunable, and the recommended Argon2 variant today

My Crate Logo Crate used: argon2

Argon2id is designed to make password cracking expensive on GPUs/ASICs6 by forcing large memory usage per guess.

What you tune (conceptually):

  • memory (MiB)
  • time (iterations)
  • parallelism (lanes/threads)

Rule of thumb: tune it so verification is “noticeable but acceptable” on your server, then periodically raise cost over time.

🧪 Code Example: Argon2id (source code)

#![allow(unused)]
fn main() {
pub fn run_argon2id_example() {
    use argon2::Argon2;
    use argon2::password_hash::{
        PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng,
    };

    let password = b"correct horse battery staple";
    let salt = SaltString::generate(&mut OsRng);

    let argon2 = Argon2::default(); // Argon2id by default
    let hash = argon2.hash_password(password, &salt).unwrap().to_string();

    // Store `hash` in your DB. Later, verify by parsing the encoded string.
    let parsed = PasswordHash::new(&hash).unwrap();
    argon2.verify_password(password, &parsed).unwrap();

    println!("Argon2id hash: {hash}");
}
}

Output:

Argon2id hash: $argon2id$v=19$m=19456,t=2,p=1$2SgpVk7SNjbMVDerM8ObNw$fJLxxiOuQK02MAx/bBYCydPDRQtMpi+gcqeWIJHUgaQ
🚨 Critical rule
Do not invent your own parameters or string formats. Use the library’s encoded hash format and verify with the library.

🟢 Conclusion

If you’re building a new system that stores passwords, Argon2id is the default choice.

Scrypt: still strong, still common

💡 Used in older-but-still-secure systems, disk encryption formats, cryptocurrencies. Memory-hard, widely deployed

My Crate Logo Crate used: scrypt

Scrypt has the same high-level goal as Argon2: make brute-force expensive, especially on specialized hardware. It’s still a valid choice, especially when interoperability matters.

🧪 Code Example: scrypt (source code)

#![allow(unused)]
fn main() {
pub fn run_scrypt_example() {
    use scrypt::{Params, scrypt};

    let password = b"correct horse battery staple";
    let salt = b"unique salt"; // should be random and unique per user/file in real systems

    // N=2^15, r=8, p=1 is a common baseline; tune for your environment.
    // log_n=15 (N=32768), r=8 ⇒ ~32 MiB RAM/guess; p=1 keeps parallelism modest.
    let params = Params::new(15, 8, 1, 32).unwrap();

    let mut key = [0u8; 32];
    scrypt(password, salt, &params, &mut key).unwrap();

    println!("scrypt-derived key: {}", hex::encode(key));
}
}

Output:

scrypt-derived key: 1b1829d47138ed3fddf496d90bd8f4da1a06b9ad18b4be890aa2d10cd7079326
🚨 Critical rule
Store the salt and parameters alongside the ciphertext so you can derive the same key during decryption.

🟢 Conclusion

If you can’t use Argon2id for some reason, scrypt is the next best widely-supported option.

KDFs in Real Systems: Password → Encryption Key

If you derive a key from a password, it usually feeds into an AEAD (like AES-GCM or ChaCha20-Poly1305).

The secure pattern looks like this:

password + salt + KDF parameters → 32-byte key → AEAD encrypt/decrypt

What must be persisted:

  • salt + KDF parameters (public)
  • AEAD nonce (public, unique)
  • ciphertext + tag

If you lose the salt/params/nonce, you lose decryptability.

Argon2 vs scrypt vs PBKDF2

If you need…Use
Best default for new password storageArgon2id
Broad legacy interoperabilityscrypt
“Legacy-safe” baseline onlyPBKDF27
🚨 Critical rule
Password KDFs (Argon2/scrypt/PBKDF2) are for weak secrets. For deriving multiple keys from an already-strong secret, use HKDF8 instead.

🟢 Conclusion

Passwords are attacker-controlled inputs.

A proper KDF turns each password guess into a costly operation, making offline attacks dramatically harder.

Practical rule: use Argon2id by default, fall back to scrypt when needed, and treat parameter + salt storage as part of your cryptographic design.


  1. KDF: Key Derivation Function. Derives cryptographic keys from a secret (often with salt/context), producing the right-length key material and supporting key separation. More

  2. Argon2: Modern memory-hard password hashing and KDF (recommended variant: Argon2id). More

  3. scrypt: Memory-hard password-based KDF. More

  4. Salt: Non-secret random value used to ensure uniqueness and defeat precomputation. More

  5. PHC: Standard string format that encodes a password hash/KDF output plus its parameters and salt. More

  6. GPU/ASIC attacks: Password cracking on specialized hardware; memory-hard KDFs aim to make each guess expensive. More

  7. PBKDF2: Iterated HMAC-based password KDF (not memory-hard). More

  8. HKDF: Key expansion KDF for strong secrets (not passwords). More

Randomness & Entropy — Nonces, IVs, CSPRNGs

Randomness & Entropy: nonces, IVs, CSPRNGs

🔐 Used in: encryption schemes, TLS, digital signatures1, key generation, session tokens

✅ Unpredictability is security. If randomness fails, everything collapses.

Why Randomness Is a Security Primitive

Modern cryptography does not just rely on strong mathematics.

It relies on unpredictability.

If an attacker can predict:

  • Your encryption IV2
  • Your nonce3
  • Your session token
  • Your key material

Then your system is broken, even if you use perfect algorithms.

Cryptography without randomness is just math.

What Is Entropy?

Entropy is a measure of unpredictability.

In cryptography, entropy answers one question: How hard is it to guess this value?

Examples:

  • A 4-digit PIN → ~13 bits of entropy (very weak)
  • A 256-bit key → 256 bits of entropy (astronomically strong)

True entropy comes from:

  • OS randomness pools4
  • Hardware noise5
  • Interrupt timing6
  • Environmental noise7

It does not come from:

  • rand()8
  • timestamps9
  • incremental counters
  • predictable seeds10

CSPRNG (Cryptographically Secure Pseudorandom Number Generator)

My Crate Logo Crate used: rand

A CSPRNG11 is a deterministic algorithm that expands a small amount of true entropy into large amounts of secure random data.

Properties:

  • Unpredictable
  • Resistant to state recovery
  • Backtracking resistant
  • Seeded from high-entropy OS sources

Your OS already provides one:

  • Linux → /dev/urandom
  • Windows → CryptGenRandom / BCryptGenRandom
  • macOS → SecRandomCopyBytes

Rust exposes this securely through the rand crate

🧪 Code Example: Secure Random Bytes (source code)

#![allow(unused)]
fn main() {
pub fn run_csprng_example() {
    use rand::RngCore;
    use rand::rngs::OsRng;

    let mut bytes = [0u8; 32];
    OsRng.fill_bytes(&mut bytes);

    println!("32 random bytes: {}", hex::encode(bytes));
    println!("random u64: {}", OsRng.next_u64());
}
}

Output:

32 random bytes: a7d6ba53f00fd0c7a90bb30312cd0b96ae22e1a5f438c91e4d93a62bff35f28d
random u64: 15302970456105725428

What you’re seeing is the same OS CSPRNG used in two different ways:

  • 32 random bytes: raw randomness you typically use directly for crypto inputs (keys, nonces, salts, IVs).
  • random u64: 8 random bytes returned as a convenient numeric type (handy for things like “pick a random index”).

Not predictable. Not reproducible. Not reversible. That’s exactly what we want.

It does not have to be secret. It must be unique.

Nonces are used in:

  • AES-GCM
  • ChaCha20-Poly1305
  • TLS record encryption12
  • Replay protection systems

🚨 Critical rule: If you ever reuse a nonce with the same key: You can completely break the encryption.

🟢 Best Practice

Use 96-bit (12-byte) nonces for GCM

Never reuse them. Prefer random nonces unless protocol specifies counters

What Happens If Randomness Fails?

History is brutal here:

  • Debian OpenSSL bug (2008) reduced entropy to 15 bits.
  • Android Bitcoin wallet bug reused ECDSA nonces.
  • PS3 signing failure reused ECDSA nonce → private key recovered.

In all cases:

  • The algorithm was correct.
  • Randomness was broken.
  • And everything collapsed.

Most catastrophic crypto failures are entropy failures.

Deterministic vs Secure Random

Never use: use rand::thread_rng();

For simulations? Fine. For cryptography? No.

Always prefer: use rand::rngs::OsRng;

Or use crates that internally rely on secure randomness (like ring, aes-gcm, chacha20poly1305).

Key Generation: the most important use case

All cryptographic keys must come from a secure, high-entropy source.

No passwords. No manual seeds. No shortcuts.

Entropy Budget (Thinking Like an Attacker)

If a value has:

  • 32 bits entropy → brute-forceable
  • 64 bits entropy → expensive but possible
  • 128 bits entropy → practically infeasible
  • 256 bits entropy → overkill for most cases

Modern security baseline: Minimum 128 bits of entropy for long-term security.

Randomness in Protocols

Randomness appears everywhere:

  • Session IDs
  • TLS handshake13
  • ECDSA nonces14
  • Key exchange salts15
  • Password salts16
  • Token generation
  • Secure cookies

You may not see it. But it is there.

Common Mistakes

  • Seeding RNG manually

  • Using timestamps

  • Reusing nonces

  • Storing IVs incorrectly

  • Assuming randomness == secrecy

  • Confusing UUID v4 with cryptographic token (context matters)

  • Testing Randomness (What You Should NOT Do). Do not test randomness with: “It looks random.”Cryptographic randomness is not visual randomness.

You rely on:

  • OS entropy pool
  • Well-audited CSPRNG
  • Mature cryptographic libraries

You do not roll your own RNG. Ever.

🟢 Conclusion

Randomness is not optional. It is a foundational cryptographic primitive.

Entropy defines unpredictability. CSPRNG expands secure entropy safely. Nonces must never repeat

IVs must follow algorithm rules

Reused randomness destroys security

Without entropy, cryptography is an illusion.


  1. Digital signature: private-key proof of authenticity and integrity; anyone with the public key can verify; enables non-repudiation. More

  2. Initialization vector (IV): non-secret per-message value required by some cipher modes; must follow scheme rules; reuse can leak information. More

  3. Nonce (“number used once”): per-operation value that must not repeat for a given key; reuse can catastrophically break AEAD security. More

  4. OS entropy pool: kernel-managed entropy mixed from multiple sources; used to seed CSPRNGs and provide secure random bytes. More

  5. Hardware noise: physical randomness (thermal/electronic noise, oscillator jitter) used as an entropy source for true randomness. More

  6. Interrupt timing: small unpredictable variations in interrupt arrival times used as entropy input; mixed by the OS for safety. More

  7. Environmental noise: entropy from external events (user input, device timings, sensors) collected and mixed into the OS pool. More

  8. rand: Rust crate for RNG traits and generators; for crypto randomness use OS-backed RNGs like OsRng/getrandom. More

  9. Timestamps: time-derived values are guessable and low-entropy; unsuitable as seeds, keys, nonces, or cryptographic randomness. More

  10. Predictable seed: RNG seed from guessable inputs (time, counters); makes outputs predictable and can reveal derived secrets. More

  11. CSPRNG: deterministic generator seeded with high entropy; output is computationally indistinguishable from random; resists prediction. More

  12. TLS record encryption: per-record symmetric AEAD that encrypts and authenticates application data using traffic keys/nonces. More

  13. TLS handshake: negotiates parameters, authenticates peers, and derives shared traffic keys/secrets used for record encryption. More

  14. ECDSA nonce: secret per-signature scalar k; must be unpredictable and never reused; reuse can leak the private key. More

  15. Key exchange salt: non-secret random value fed into a KDF/HKDF to separate contexts and reduce key-reuse risks. More

  16. Password salt: unique random value stored with a password hash; prevents rainbow tables and makes identical passwords hash differently. More

Glossary of Terms

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.

AEAD

AEAD (Authenticated Encryption with Associated Data) is a class of symmetric encryption schemes that provide confidentiality and integrity together. An AEAD encrypts plaintext into ciphertext and simultaneously produces an authentication tag which is verified during decryption. It also supports authenticating “associated data” (AAD): metadata such as headers or protocol fields that must be integrity-protected but remain unencrypted. If tag verification fails, decryption must fail and no plaintext should be released. Common AEADs include AES-GCM and ChaCha20-Poly1305.

AEAD API

An “AEAD API” is the standard programming interface exposed by AEAD libraries: given a key, nonce, plaintext, and optional AAD, it returns ciphertext plus an authentication tag. Decryption takes the same inputs and returns either the plaintext or an authentication failure, typically as an error result. Most APIs treat the nonce as public but unique, and require callers to enforce nonce rules; many also offer “in-place” variants for performance. Using the API correctly means never decrypting unauthenticated data and always handling verification errors as hard failures.

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.

AES Block Cipher

AES is a block cipher: it transforms fixed-size 128-bit blocks under a secret key. By itself, AES does not define how to encrypt messages longer than one block or how to authenticate data; those properties come from a “mode of operation” and/or an AEAD construction. Thinking “AES” vs “AES-GCM” is the difference between the raw primitive (block-by-block permutation) and a complete, safe encryption scheme. Most real-world systems use AES inside a mode such as CTR, CBC, or an AEAD like GCM, rather than using AES directly on blocks.

AES-GCM

AES-GCM (Galois/Counter Mode) is a widely standardized AEAD which combines AES in CTR mode for encryption with GHASH for authentication. It is extremely fast on modern CPUs with AES acceleration and is common in TLS, HTTPS, storage systems, and many enterprise protocols. AES-GCM requires a unique nonce for every encryption under the same key; repeating a nonce can reveal relationships between plaintexts and may enable tag forgery. Because of that, AES-GCM is best used with reliable nonce generation (counters, sequence numbers, or carefully managed randomness) and strict error handling on authentication failure.

API

An application programming interface defines how software components communicate through structured requests and responses. It specifies operations, data formats, authentication rules, error handling, and versioning conventions. APIs enable modular system design, interoperability, and automated integration between services. Security depends on proper identity verification, authorization enforcement, input validation, and transport protection.

Argon2

Argon2 is a memory-hard password hashing and key-derivation function (recommended variant: Argon2id).

It was the winner of the Password Hashing Competition (PHC) in 2015 and is designed to make large-scale cracking expensive on GPUs/FPGAs/ASICs by requiring significant memory per guess. Argon2 exposes tunable parameters for memory, time (iterations), and parallelism so you can set costs appropriate for your environment and increase them over time.

Authenticated Key Exchange (AKE)

An authenticated key exchange (AKE) is a protocol that establishes a shared secret and authenticates the peer you’re talking to. Plain Diffie–Hellman without authentication is vulnerable to active man-in-the-middle (MITM) attacks: an attacker can run separate handshakes with each side and sit in the middle. AKE prevents this by binding the handshake to an identity using a signature (public-key authentication), a certificate chain, or a pre-shared key (PSK). TLS is a common real-world example: the handshake negotiates keys and authenticates (at least) the server, typically via a certificate and signature over handshake transcripts.

AWS Request Signin (SigV4)

AWS request signing is a cryptographic mechanism used to authenticate and authorize API requests to AWS services. Requests are canonicalized, hashed, and signed using HMAC with secret credentials and request metadata. This protects against request tampering and limits replay attacks through timestamp validation. Correct canonicalization and key handling are critical for reliable and secure operation.

BLAKE3

BLAKE3 is a modern cryptographic hash function derived from the BLAKE hash family, designed for high performance, parallelism, and simplicity. It builds on the cryptographic foundations of BLAKE2 while introducing a tree-based structure that enables efficient multicore and SIMD execution. BLAKE3 supports incremental hashing, keyed hashing, and extendable output (XOF) within a single unified API. It is well suited for content hashing, integrity verification, and high-throughput systems, though it is not standardized by NIST. BLAKE3 is considered secure and is increasingly adopted in modern software systems.

Broad TLS Compatibility

Broad TLS compatibility means choosing algorithms and parameters that work across a wide range of TLS clients, servers, libraries, and devices. In practice, this often means sticking to widely supported curves and signature algorithms (and sometimes accepting legacy constraints), because you do not control what old devices or enterprise middleboxes can negotiate. Compatibility is a deployment constraint, not a security goal by itself: you still want to choose the strongest option that is supported by your actual client population and plan for algorithm agility over time.

Brute Force

A brute-force attack is an exhaustive search that tries all possible inputs or keys until the correct one is found. Its effectiveness depends entirely on the size of the search space and the speed of computation. Modern cryptography relies on making brute-force attacks computationally infeasible. KDFs and memory-hard functions are designed specifically to slow down brute-force attacks.

Cache Timing

Cache timing is a side-channel risk where an attacker learns secrets by observing how long computations take due to CPU cache effects. If an algorithm’s memory access pattern depends on secret data (for example, table lookups indexed by key bytes), the CPU may fetch different cache lines and create measurable timing differences. These differences can be exploited locally (or sometimes remotely) to recover keys or other sensitive state. Mitigations include constant-time implementations, avoiding secret-dependent table lookups, using hardware-accelerated instructions (e.g., AES-NI), and careful microarchitectural hardening.

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.

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.

ChaCha20

ChaCha20 is a modern stream cipher designed by Daniel J. Bernstein as a refinement of Salsa20. It uses simple add-rotate-xor (ARX) operations, making it fast in software and relatively easy to implement in constant-time. ChaCha20 generates a pseudorandom keystream from a 256-bit key, a nonce, and a counter; encryption XORs this keystream with the plaintext. Nonce reuse under the same key causes keystream reuse and breaks confidentiality, so nonces must be unique.

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. ChaCha20-Poly1305 is particularly attractive on clients without fast AES hardware because it is performant in pure software and easier to harden against timing leakage than table-based AES implementations. Like other AEADs, it requires unique nonces per key; nonce reuse can reveal relationships between plaintexts and must be avoided. Many libraries expose it through a uniform AEAD API, making it a common “safe default” choice across servers, mobile devices, and embedded systems.

Collision

A collision occurs when two distinct inputs produce the same hash output. Collision resistance means it is computationally infeasible to find such pairs intentionally. While collisions must exist mathematically, a secure hash makes finding them impractical. Collision resistance is critical for digital signatures, certificates, and integrity systems.

Constant-Time Algorithm

A constant-time algorithm executes independently of secret data to avoid leaking information through timing behavior. Branching, memory access patterns, and early exits must not depend on sensitive values. Timing side channels can otherwise reveal keys, authentication tags, or internal state. Constant-time techniques are essential for secure cryptographic implementations.

CSPRNG (Cryptographically Secure Pseudorandom Number Generator)

A CSPRNG is a deterministic generator that expands a small amount of high entropy into large amounts of pseudorandom output. It is designed so outputs are computationally indistinguishable from random and resistant to state-recovery attacks. In practice, you rely on well-audited CSPRNGs seeded by the operating system.

Certificate Chain

A certificate chain is an ordered set of certificates used to justify trust in a leaf (end-entity) certificate. Typically it is: leaf certificate → one or more intermediate CA certificates → a root CA certificate that is trusted via a local trust store (not via the network). Validating the chain means checking each signature, validity period, name constraints, key usage/extended key usage, policy rules, and (when applicable) revocation status (CRLs/OCSP). In TLS, certificate chains bind a public key to a server identity (e.g., a DNS name) so clients can authenticate who they are connecting to.

Constant-Time Comparison

A constant-time comparison checks whether two values are equal without leaking where they differ via timing. The key rule is “no early exits”: the runtime should not depend on the first mismatching byte, which would allow attackers to learn information by measuring response time. This is especially important when comparing MAC tags, authentication tokens, password hashes, and other verifier-side secrets. Use library-provided constant-time equality helpers rather than writing your own comparisons.

Correct Checks (Signature Verification)

“Correct checks” in signature verification means enforcing all the protocol and encoding rules around verification, not just calling a math primitive. Examples include: allow-listing acceptable algorithms, verifying you are checking the exact bytes that were signed (canonical encoding), rejecting malformed inputs, and validating parameters (e.g., public keys are on the curve and in the correct subgroup). Some schemes have extra malleability rules (e.g., “low-s” normalization in ECDSA) that must be enforced to prevent multiple signatures from verifying the same message. Most of these checks are easy to get subtly wrong, which is why you should rely on well-audited, high-level verification APIs.

CTR Mode

CTR (Counter) mode turns a block cipher like AES into a stream-cipher-like construction by encrypting a nonce/counter sequence to generate a keystream. That keystream is XORed with plaintext to produce ciphertext (and vice versa for decryption). CTR mode is fast and parallelizable, but it provides no integrity: it is malleable, and attackers can flip bits in the decrypted plaintext by flipping bits in ciphertext. Nonce/counter values must never repeat under the same key or the keystream repeats and confidentiality breaks.

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.

Deterministic Nonce (RFC 6979)

A deterministic nonce is a per-signature “random-looking” value that is derived deterministically from the private key and the message (and a hash function), rather than coming from an external RNG. RFC 6979 standardizes this approach for DSA/ECDSA-style signatures to reduce catastrophic failures caused by broken randomness at signing time. Deterministic nonces are not a free pass: side-channel leaks, repeated messages under the same key, or incorrect hashing/domain separation can still cause problems, and implementations must still be constant-time.

Digital Signature

A digital signature is a public-key mechanism that proves authenticity and integrity of a message. The signer uses a private key to produce the signature; anyone with the corresponding public key can verify it. Signatures are used in certificates, software updates, authentication, and secure protocols to prevent tampering and impersonation.

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.

ECDSA Nonce

The ECDSA nonce is the per-signature secret scalar k. It must be unique and unpredictable for each signature; reuse (or bias) can leak the long-term private key. Many implementations use deterministic nonces (RFC 6979) to avoid relying on external randomness at signing time.

ECC (Elliptic-Curve Cryptography)

Elliptic-curve cryptography (ECC) is a family of public-key systems built on groups derived from elliptic curves over finite fields. At a high level, ECC offers similar security to RSA with much smaller keys and often better performance (especially for signatures and key exchange). Common ECC building blocks include ECDH for key exchange and ECDSA/Ed25519 for signatures, using curves such as P-256 or Curve25519/Edwards25519. ECC security relies on the hardness of the elliptic-curve discrete logarithm problem for properly chosen curves and correctly implemented arithmetic.

Elliptic Curve

In cryptography, an elliptic curve is a mathematical structure (an algebraic curve over a finite field) whose points form a group with a defined addition operation. ECC uses that group for public-key operations: a private key is a scalar, and a public key is the result of “multiplying” a base point by that scalar. Different standardized curves (e.g., NIST P-256, Curve25519, Edwards25519) come with different performance and implementation tradeoffs, but they all provide the same basic ingredient: a hard discrete-log problem in the chosen group.

Environmental Noise

Environmental noise refers to entropy gathered from external events (user input timing, device latencies, sensor readings). The OS mixes these sources into its entropy pool to help seed and reseed its CSPRNG. The key idea is unpredictability to an attacker, not “random-looking” values.

Extendable Output Function

An Extendable Output Function (XOF) is a hash construction that can generate an arbitrary-length output from a single input. Instead of producing a fixed-size digest, an XOF acts like a pseudorandom stream derived from the input. XOFs are useful for key derivation, randomness expansion, and domain separation. Examples include SHAKE and the XOF mode of BLAKE3.

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.

Forged API Request

A forged API request is a maliciously crafted or modified request designed to impersonate a legitimate client or bypass security controls. Attackers may manipulate headers, parameters, identifiers, signatures, or timestamps to gain unauthorized access or perform restricted actions. These attacks often exploit weak authentication, missing authorization checks, predictable identifiers, or insufficient request validation. Mitigations include strong authentication, strict authorization, request integrity verification, and anti-replay mechanisms.

GHASH

GHASH is the authentication component used by AES-GCM. It is a polynomial hash over GF(2^128) computed on the associated data and ciphertext, producing a value that is combined with an AES-derived mask to form the final tag. GHASH is efficient and works well with hardware acceleration, but it inherits AES-GCM’s strict nonce-uniqueness requirement: repeating a nonce under the same key can reveal information and enable forgeries. In practice, you treat GHASH as an internal detail of GCM and focus on correct key and nonce management at the API boundary.

GPU/ASICs

GPUs and ASICs are specialized hardware commonly used to accelerate brute-force password cracking.

Compared to general-purpose CPUs, GPUs can run many guesses in parallel, and ASICs can be even faster per watt for specific computations. Memory-hard password KDFs (like Argon2 and scrypt) try to reduce this advantage by forcing each guess to allocate and touch a large amount of memory, making attacks more bandwidth/DRAM-limited and less cost-effective to scale.

Hardware Noise

Hardware noise is physical unpredictability (thermal noise, electronic noise, oscillator jitter) used as an entropy source. It may be provided by dedicated TRNG hardware or used to seed and refresh the OS entropy pool. Because raw noise can be biased, systems typically condition and mix it before use.

HKDF

HKDF is an HMAC-based KDF used to expand a strong secret into multiple independent keys.

It is designed for key separation and protocol key management (e.g., TLS and secure messaging), not for password hashing. HKDF provides no brute-force resistance: if the input secret is guessable (like a human password), attackers can still test guesses quickly, so you must use a password KDF (Argon2/scrypt/PBKDF2) instead.

Incremental Hashing

Incremental hashing is the ability to compute a hash progressively by processing input data in chunks. This allows hashing of large or streaming data without loading it entirely into memory. Incremental hashing is essential for file hashing, network protocols, and real-time data processing. Most modern cryptographic hash functions support incremental hashing by design.

Interrupt Timing

Interrupt timing entropy comes from tiny, hard-to-predict variations in when interrupts occur. On its own it is not a complete randomness source, but the OS can mix timing jitter with other inputs to improve robustness. Modern kernels treat this as one ingredient among many rather than a sole source of entropy.

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.

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.

IV (Initialization Vector)

An initialization vector (IV) is a non-secret input used by some encryption modes (e.g., CBC, CTR) to ensure uniqueness across encryptions. It must follow the scheme’s rules (size, randomness/uniqueness requirements), and it is typically transmitted alongside the ciphertext. Reusing an IV when the mode requires uniqueness can leak information about plaintexts and sometimes enable attacks.

JWT

A JSON Web Token is a compact, URL-safe token format used to transmit cryptographically protected claims between parties. It typically consists of a header, payload, and signature encoded using Base64URL. JWTs are widely used for stateless authentication and authorization in distributed systems and APIs. Secure usage requires strict signature verification, algorithm allow-lists, expiration checks, and issuer validation.

KDF

A Key Derivation Function (KDF) derives one or more cryptographic keys from an input secret plus optional context.

The input secret might be a human password (weak and guessable), a shared secret from a key exchange, or an existing cryptographic key. KDFs are used for key separation (derive independent keys for different purposes), key expansion (turn a short secret into multiple keys), and producing keys of the right size and distribution. Some KDFs are password-focused and deliberately expensive to compute (Argon2, scrypt, PBKDF2), while others are for strong secrets and prioritize clean key separation (HKDF).

Key Exchange Salt

A key exchange salt is a non-secret random value used as input to a KDF/HKDF during key derivation. It helps with domain separation and reduces risk from cross-protocol key reuse or subtle collisions. Salts should be unique per context/session and are usually sent in clear.

Keyed Hashing

Keyed hashing is a hashing mode that incorporates a secret key into the hash computation. It is used to provide message authentication and integrity, ensuring that only parties with the key can generate or verify the hash. Keyed hashing prevents attackers from forging valid hashes for modified messages. HMAC is the most widely used standardized construction for keyed hashing.

Length-Extension Attack

A length-extension attack exploits the internal structure of certain hash functions to append data to a hashed message. An attacker can compute a valid hash for an extended message without knowing the original secret. This vulnerability breaks naïve constructions such as hash-based authentication using secret-prefix hashing. Proper MAC constructions such as HMAC are specifically designed to prevent this attack.

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.

Merkle Tree

A Merkle tree is a hash-based tree data structure where each internal node is the hash of its child nodes. It allows efficient verification of large data sets by validating only a small subset of hashes. Merkle trees are used in blockchains, distributed systems, file systems, and secure databases. They provide tamper detection and integrity guarantees with logarithmic verification cost.

Misuse Resistance

Misuse resistance is a property of cryptographic constructions that reduces the damage caused by common implementation mistakes. In AEADs, the most important misuse is nonce reuse: many schemes (like AES-GCM) fail catastrophically if a nonce is repeated under the same key. Misuse-resistant AEADs (such as AES-GCM-SIV) are designed so that accidental nonce reuse does not immediately enable forgeries or full plaintext recovery. Misuse resistance is not a license to be sloppy, reusing nonces can still leak information, but it provides a safety net for real systems.

NIST

NIST is a U.S. federal agency responsible for developing and publishing technical standards, including cryptographic algorithms. It standardizes widely used primitives such as AES, SHA-2, RSA padding schemes, and digital signature algorithms. NIST standards are often required for government, defense, financial, and regulated industries. While NIST does not “invent” most algorithms, its approval process heavily influences global cryptographic adoption. Compliance with NIST standards is often referred to as “FIPS compliance.”

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.

Nonce

A nonce (“number used once”) is a per-operation value that must not repeat for a given key. In AEAD schemes like AES-GCM and ChaCha20-Poly1305, nonce reuse can completely break confidentiality and/or integrity. Nonces are usually public, but they must be unique; they can be random or derived from counters/sequence numbers.

OAuth 2.0

OAuth 2.0 is an authorization framework that enables delegated access to protected resources without sharing user credentials. It allows applications to obtain limited-scope access tokens issued by an authorization server. OAuth separates authentication from authorization and supports multiple standardized authorization flows. Secure deployments rely on PKCE, strict redirect validation, token verification, and safe client storage.

OS Entropy Pool

The OS entropy pool is the operating system’s internal randomness state, continuously mixed from multiple entropy sources. It seeds OS CSPRNGs and backs APIs that provide secure random bytes for keys, nonces, salts, and protocol secrets. Applications should use OS-provided randomness rather than collecting their own entropy.

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.

Padding Oracle Attack

A padding oracle attack exploits observable differences in error messages or timing when a system validates decrypted padding. By sending many crafted ciphertexts, an attacker can progressively recover plaintext or forge valid encrypted messages. This attack commonly affects block cipher modes such as CBC when padding errors are distinguishable. Using authenticated encryption, uniform error handling, and constant-time validation prevents this class of attack.

Password Salt

A password salt is a unique random value stored alongside a password hash. It prevents rainbow-table attacks and ensures identical passwords do not produce identical hashes across users. Salts do not need secrecy, but they must be unique and randomly generated per password.

PBKDF2

PBKDF2 is a standardized, iteration-based password KDF built on HMAC.

It slows down brute-force guessing by performing many repeated HMAC computations. PBKDF2 is widely supported and still acceptable when configured with a sufficiently high iteration count, but it is not memory-hard, so GPUs/ASICs can often test guesses efficiently. Today it’s commonly treated as a “legacy-safe” baseline when Argon2id or scrypt are not available.

PHC

PHC is commonly used to refer to the PHC (Password Hashing Competition) string format for password hashes and password-derived keys.

In practice, libraries often serialize “everything you need to verify later” into a single string: the algorithm identifier (e.g., argon2id), version, tunable parameters, salt, and the resulting hash/key material. Storing the library-produced encoded string (instead of inventing your own format) helps ensure correct parsing and future upgrades.

PKI Deployment

PKI deployment refers to the operational reality of using certificates and certificate authorities (CAs) in production systems. It includes how certificates are issued and rotated, which roots/intermediates are trusted by clients, how revocation is handled (CRLs/OCSP), and what policies and lifetimes are enforced. These constraints can strongly influence algorithm choices: a theoretically “better” algorithm may be impractical if legacy clients, hardware, or enterprise environments don’t support it.

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.

Poly1305

Poly1305 is a fast one-time message authentication code (MAC) designed by Daniel J. Bernstein. It computes a 16-byte authentication tag using arithmetic modulo a large prime, and it is designed to be implemented efficiently and in constant time. The crucial rule is that its key must be used only once; reusing a Poly1305 key can allow attackers to forge tags. In practice, Poly1305 is paired with a stream cipher (most commonly ChaCha20) which derives a fresh one-time key for each message.

Predictable Seed

A predictable seed is an RNG seed derived from guessable inputs like time, counters, or process IDs. If an attacker can guess the seed, they can reproduce RNG outputs and recover secrets derived from them. Cryptographic RNGs must be seeded from high-entropy sources (typically the OS).

Preimage

A preimage is an input that produces a specific hash output. Preimage resistance means it is computationally infeasible to recover the original input from its hash. This property ensures that hashes cannot be reversed to reveal sensitive data. Preimage resistance is fundamental for password hashing and data integrity.

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.

rand (Rust crate)

rand is the standard Rust ecosystem crate for randomness traits (RngCore), generators, and distributions. For cryptography, prefer OS-backed randomness like rand::rngs::OsRng (via getrandom) rather than weak or seeded PRNGs. rand also provides sampling utilities, but cryptographic safety depends on choosing the right RNG.

RNG Foot-Gun

An RNG foot-gun is a common mistake around randomness that accidentally breaks cryptography. Typical examples include: seeding a PRNG with time, using non-cryptographic PRNGs for secrets, reusing nonces, or generating “random” values from low-entropy inputs. These bugs are often silent and catastrophic (for example, ECDSA with a reused or biased nonce can leak the private key). The safe baseline is to rely on OS-provided CSPRNG APIs and well-audited libraries rather than rolling your own randomness.

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.

RSA-PSS

RSA-PSS (Probabilistic Signature Scheme) is the modern, recommended padding/encoding method for RSA signatures (standardized in PKCS#1 v2). It hashes the message and uses a randomized “salt” plus a mask generation function (MGF1) to create a signature encoding designed to resist practical forgery attacks. When implemented correctly, RSA-PSS has strong security properties and is the baseline RSA signature mode for new designs.

Textbook RSA

Textbook RSA is “raw” RSA applied directly to message integers with no standardized padding/encoding. It is deterministic and malleable, and it lacks the security properties required for real-world encryption or signatures. For encryption, use RSA-OAEP (or better: hybrid encryption with modern KEMs); for signatures, use RSA-PSS (or a modern ECC signature scheme).

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.

Salt

A salt is a non-secret random value used to make each password hash / derived key unique.

By ensuring that identical passwords do not produce identical outputs, salts defeat precomputed attacks (like rainbow tables) and prevent attackers from reusing work across many victims. Salts are stored alongside the hash/ciphertext and must be unique and randomly generated per user or per encrypted file; they do not “add secrecy” and do not slow brute force by themselves.

scrypt

scrypt is a memory-hard password-based KDF designed to make large-scale cracking expensive.

It forces each guess to use significant memory, reducing the advantage of specialized hardware. scrypt remains widely deployed (e.g., some disk encryption formats and cryptocurrencies) and is still a strong choice when interoperability matters, but newer guidance typically prefers Argon2id for new designs because it’s more modern and easier to tune.

SHA-2 (Secure Hash Algorithm 2)

SHA-2 is a family of cryptographic hash functions standardized by NIST as successors to SHA-1. It includes SHA-224, SHA-256, SHA-384, and SHA-512, which differ mainly in output size and internal parameters. SHA-2 is designed to provide strong collision resistance, preimage resistance, and avalanche behavior. It is widely used in TLS, digital signatures, certificate authorities, blockchains, and secure software distribution. Despite its age, SHA-2 remains secure and is considered a conservative, well-understood choice.

SHA-224

SHA-224 is a cryptographic hash function belonging to the SHA-2 family. It produces a 224-bit digest and is derived from the same internal structure as SHA-256, with different initialization constants and truncation. SHA-224 offers slightly reduced output size and security margin compared to SHA-256. It is used in constrained environments where smaller hash outputs are desirable, though SHA-256 is far more common.

SHA-256

SHA-256 is the most widely deployed member of the SHA-2 hash family. It produces a 256-bit digest and provides strong collision and preimage resistance. SHA-256 is used extensively in TLS, digital signatures, blockchains (including Bitcoin), and secure software verification. Its balance of security, performance, and broad support makes it the default hash function in many systems.

SHA-384

SHA-384 is a SHA-2 variant optimized for 64-bit architectures. It produces a 384-bit output and uses different internal parameters than SHA-256, providing a higher security margin. SHA-384 is often used in high-security or long-term systems where additional safety margin is desired. It is commonly paired with 64-bit platforms and high-assurance protocols.

SHA-512

SHA-512 is the largest-output member of the SHA-2 family, producing a 512-bit hash. It is optimized for 64-bit processors and often performs better than SHA-256 on such systems. SHA-512 provides an extremely large security margin against collision and preimage attacks. Variants such as SHA-512/256 reuse the SHA-512 core while producing shorter outputs.

Side-Channel Attack

A side-channel attack breaks security by exploiting information leaked by an implementation rather than weaknesses in the underlying math. Common side channels include timing, cache behavior, power consumption, electromagnetic emissions, and fault injection. For example, a timing difference in MAC verification or a secret-dependent memory access pattern can leak bits of a key over many observations. Defenses include constant-time code, uniform error handling, blinding techniques, hardened hardware, and minimizing attacker observability.

Silent Data Corruption

Silent data corruption occurs when data is modified without detection due to hardware faults, software bugs, or transmission errors. Because no immediate error is raised, corrupted data may propagate into backups, computations, or decision systems. This can silently compromise integrity, correctness, and long-term reliability of stored or processed information. End-to-end checksums, cryptographic integrity verification, and hardware redundancy reduce this risk.

SIMD

SIMD is a CPU execution model where a single instruction operates simultaneously on multiple data elements. It enables data-parallel computation using vector registers, significantly improving performance for cryptographic and numeric workloads. Modern CPUs use SIMD extensively for hashing, encryption, and multimedia processing. Algorithms designed with SIMD in mind (such as BLAKE3) can achieve very high throughput on modern hardware.

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.


Stream Cipher

A stream cipher encrypts data by generating a pseudorandom keystream and XORing it with plaintext bytes. The same operation is used for encryption and decryption. Reusing the same keystream for multiple messages compromises confidentiality immediately. Modern stream ciphers are typically combined with authentication to provide full data integrity.

Timestamps

Timestamps are time-derived values (seconds/milliseconds) that are often predictable to attackers. They have low entropy and should not be used as cryptographic seeds, keys, nonces, or “random” tokens. They are fine as metadata, but not as a security primitive.

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.

TLS Fallback

TLS fallback refers to selecting an alternative cipher suite or algorithm when the preferred choice is unavailable or performs poorly on a given client. In modern TLS, clients and servers negotiate a suite during the handshake; implementations may prefer AES-GCM on CPUs with AES acceleration and prefer ChaCha20-Poly1305 on devices without it. This is typically a performance and side-channel hardening decision, not a downgrade in security, as both suites are considered strong when used correctly. The key point is that TLS can adapt to heterogeneous hardware while maintaining authenticated encryption.

TLS Handshake

The TLS handshake is the setup phase where client and server negotiate parameters and establish shared secrets. It may authenticate the server (and optionally the client) and typically uses (EC)DHE for forward secrecy. Handshake outputs are used to derive traffic keys for encrypting application data.

TLS Internals

TLS internals describe the cryptographic protocols and handshake mechanisms that secure network communications. During the handshake, peers authenticate, negotiate algorithms, and derive shared symmetric session keys. Modern TLS provides confidentiality, integrity, authentication, and forward secrecy. It protects application data against eavesdropping, tampering, and active network attacks.

TLS Record Encryption

TLS record encryption protects application data after the handshake, splitting it into records and applying symmetric AEAD per record. It provides confidentiality and integrity using traffic keys and per-record nonces/sequence numbers. Correct key/nonce management and strict authentication failure handling are essential for security.

Token Manipulation

Token manipulation refers to tampering with authentication or session tokens to escalate privileges or impersonate another identity. Common targets include JWTs, cookies, and API keys where claims, signatures, or metadata may be altered or abused. Vulnerabilities often arise from weak verification, algorithm confusion, excessive token lifetime, or insecure storage. Defenses include strict signature validation, short expirations, audience checks, and secure token handling practices.

Verify-Before-Run Security

Verify-before-run security is the practice of verifying cryptographic signatures on code and artifacts before you install or execute them. Examples include signed OS updates, signed packages in language ecosystems, container image signing, secure boot chains, and signed release binaries. The security goal is supply-chain integrity: even if an attacker can tamper with distribution channels, clients reject modified artifacts unless the attacker also has the signing key.

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.

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

ConceptSummary
Kerckhoffs’ PrincipleA cryptosystem must be secure even if everything except the key is known
Shannon’s MaximThe enemy knows the system, don’t rely on obscurity
Perfect SecrecyCiphertext reveals no information without the key (e.g., OTP)
Semantic SecurityAn attacker can’t learn anything new from ciphertext

🔢 Mathematical Foundations

ConceptSummary
EntropyA measure of randomness; critical for secure key generation
Modular ArithmeticMath used in most crypto (e.g., a mod n)
Finite FieldsAlgebraic structures where crypto operations like ECC take place
Primes & FactorizationBasis for RSA’s difficulty: factoring large numbers is hard
Discrete Log ProblemHardness assumption behind Diffie-Hellman and ECC

🔄 Encryption Concepts

ConceptSummary
Symmetric EncryptionSame key used for encryption and decryption (e.g., AES)
Asymmetric EncryptionPublic-key systems like RSA and ECC
Block CipherEncrypts fixed-size blocks (e.g., AES-128)
Stream CipherEncrypts bit-by-bit or byte-by-byte (e.g., ChaCha20)
Modes of OperationTechniques to apply block ciphers to arbitrary-length data (e.g., CBC, GCM)
PaddingFills the last block in block cipher (e.g., PKCS#7)

🔁 Cryptographic Properties

ConceptSummary
ConfusionMakes the relationship between key and ciphertext complex
DiffusionSpreads the influence of each input bit across the ciphertext
Avalanche EffectSmall change in input → large change in output
Deterministic EncryptionSame ciphertext for same input+key not semantically secure
Non-deterministic EncryptionIncludes randomness (like IVs) to ensure unique ciphertexts

🔐 Hash Functions

ConceptSummary
Collision ResistanceIt’s hard to find two different inputs with the same hash
Preimage ResistanceGiven a hash, it’s hard to find an input that produces it
Second Preimage ResistanceGiven an input, hard to find another that hashes to the same value
Birthday ParadoxHash collisions can happen surprisingly early (~2^n/2 complexity)
Merkle–Damgård ConstructionA method used in many hash functions like SHA-1, SHA-256

🔏 Digital Signatures

ConceptSummary
AuthenticityVerifies that the message comes from the claimed sender
Non-repudiationSigner cannot deny having signed
ECDSA / RSA SignaturesAlgorithms for digital signatures using asymmetric keys

📡 Protocol Concepts

ConceptSummary
Key ExchangeSecurely establishing a shared key over an insecure channel (e.g., Diffie-Hellman)
Forward SecrecyCompromise of one key doesn’t expose past sessions
Replay AttackRe-sending valid data to trick the system again
Man-in-the-MiddleAttacker intercepts communication between two parties
NonceA number used once prevents replay attacks and ensures uniqueness
Initialization Vector (IV)Random value to ensure unique ciphertexts in block cipher modes

🛡️ Attack Models

ConceptSummary
Ciphertext-only attackAttacker only has access to encrypted messages
Known-plaintext attackAttacker knows some plaintext–ciphertext pairs
Chosen-plaintext attackAttacker can choose plaintexts and get their ciphertexts
Chosen-ciphertext attackAttacker can decrypt chosen ciphertexts
Side-channel attackExploits physical leaks (timing, power, EM radiation)