Practical Cryptography for Devs & CTFers: Concepts, Pitfalls, Tools, and Krypton-Inspired Labs
Practical Cryptography for Devs & CTFers
Halloo SuiiKawaii dessu! Cryptography topic today so the main reason Cryptography invented is for protects confidentiality, integrity, and authenticity. This post is hands-on yet academically grounded: you’ll build the right mental models, learn modern primitives, understand formal properties (IND-CPA/CCA, UF-CMA), avoid the classic foot-guns, and practice through Krypton-style mini labs.
If you plan to tackle Krypton, see my series hub: OverTheWire Krypton Overview.
Table of Contents
- 1) Threat model & mental model
- 2) Encoding vs encryption vs hashing vs signing
- 3) Classical ciphers (Krypton warm-up)
- 4) Symmetric crypto: AES/ChaCha, modes, AEAD, SIV
- 5) Asymmetric crypto: RSA/ECC, KEX, hybrid
- 6) Hashes, MACs, HKDF, password KDFs
- 7) Randomness, IVs/nonces, salts
- 8) Signatures, PKI, and TLS 1.3 mental model
- 9) Provable security (IND-CPA/CCA, UF-CMA) & composition
- 10) Side-channels & implementation hygiene
- 11) Key management & ops (envelope, rotation, KMS)
- 12) Tools & recipes (OpenSSL, GPG, CyberChef)
- 13) Python mini-cookbook (
cryptography
) - 14) Krypton-inspired mini labs
- 15) Verification harness (inline, copy & run)
- Cheatsheet (algorithms & sizes)
- Printable 1-page cheatsheet (PNG + PDF)
- Further reading & references
1) Threat model & mental model
Threat model checklist (data lifecycle):
- At rest: disks, backups → envelope encryption (DEK wrapped by KEK), access controls, audit logs.
- In transit: client ↔ server → TLS 1.3 (ECDHE + AEAD).
- In use: process memory → minimize plaintext exposure; consider streaming APIs, TEE when justified.
Mental model:
- Encrypt for confidentiality.
- MAC/sign for integrity + authenticity.
- Bind context (headers, filenames, version) via AEAD AAD.
- Ensure freshness (nonces, counters), and key separation (HKDF).
Further references:
2) Encoding vs encryption vs hashing vs signing
Thing | Reversible? | Keys? | Security goal | Examples |
---|---|---|---|---|
Encoding | Yes | No | Representation | Base64, Hex, URL |
Encryption | Yes | Yes | Confidentiality | AES-GCM, ChaCha20-Poly1305 |
Hash | No | No | Fingerprint | SHA-256, SHA-3 |
MAC | No | Yes (shared) | Integrity+auth | HMAC-SHA256 |
Signature | Verifiable | Priv/Public | Integrity+auth | Ed25519, RSA-PSS |
Further references:
3) Classical ciphers (Krypton warm-up)
Why they matter: build intuition for frequency analysis, key length discovery, and layer peeling (vibe of Krypton).
- Caesar/ROT:
echo "URYYB JBEYQ" | tr 'A-Z' 'N-ZA-M' # ROT13
- Affine
E(x)=(a·x + b) mod 26
, gcd(a,26)=1 → brute a,b is tiny. - Vigenère → Kasiski/Friedman → per-slice frequency.
- Transposition → permute columns/rails; look for n-gram anomalies.
Further references:
- MIT 6.875 L1: Introduction, One-Time Pad
- [Krypton series overview (my blog)](/posts/overthewire/krypton-overview/)
4) Symmetric crypto: AES/ChaCha, modes, AEAD, SIV
Modes (intuition):
- ECB: reveals patterns → never for data.
- CBC: legacy; IV must be random; padding-oracle risks.
- CTR: stream; never reuse nonce+key.
- GCM: AEAD (confidentiality + integrity) with 96-bit unique nonces.
- AES-SIV: misuse-resistant AEAD if nonce uniqueness can’t be guaranteed.
AEAD & AAD: bind context (headers, filenames, protocol version) so swaps are detected.
# AES-256-GCM demo
openssl enc -aes-256-gcm -salt -pbkdf2 \
-in secret.txt -out secret.bin \
-pass pass:'CorrectHorseBatteryStaple'
openssl enc -d -aes-256-gcm -pbkdf2 \
-in secret.bin -out secret.txt \
-pass pass:'CorrectHorseBatteryStaple'
Further references:
5) Asymmetric crypto: RSA/ECC, KEX, hybrid
- RSA: use OAEP (encryption) and PSS (signatures). Never raw RSA.
- ECC: X25519 (key exchange), Ed25519 (signatures) are excellent defaults.
- Hybrid (envelope): generate random AEAD key → encrypt data; wrap the AEAD key with RSA/ECC.
# GPG mini-demo (hybrid under the hood)
gpg --full-generate-key
gpg --armor --export your@email
gpg --encrypt --armor -r their@email file.txt
gpg --decrypt file.txt.asc
Further references:
6) Hashes, MACs, HKDF, password KDFs
- Hash: SHA-256/512, SHA-3; avoid MD5/SHA-1.
- MAC: HMAC-SHA256 when not using AEAD.
- HKDF: derive independent subkeys from one secret (
info
label per use). - Passwords: Argon2id (tune memory/time/parallelism), or scrypt/bcrypt; always unique salt.
# Fingerprint
openssl dgst -sha256 my.iso
# Argon2id (if argon2 CLI is available)
echo -n "p@ssw0rd" | argon2 somesalt -id -t 3 -m 16 -p 1
Further references:
7) Randomness, IVs/nonces, salts
- Use CSPRNG (OS RNG, language crypto APIs). Don’t invent RNGs.
- Nonces: never reuse with same key (GCM/CTR/ChaCha20-Poly1305). For untrusted counters, consider AES-SIV.
- Salts: public & unique → defeat precomputation (rainbow tables).
openssl rand -hex 32
Further references:
8) Signatures, PKI, and TLS 1.3 mental model
- Signatures (UF-CMA): Ed25519 or RSA-PSS.
- PKI: CAs sign X.509 certs; clients verify chain, names, usage.
- TLS 1.3: ECDHE (FS) → authenticate server via cert → AEAD for data.
Further references:
9) Provable security (IND-CPA/CCA, UF-CMA) & composition
- IND-CPA: indistinguishable under chosen-plaintext attack (semantic secrecy).
- IND-CCA2: resists adaptive chosen-ciphertext queries (decryption oracle).
- UF-CMA: unforgeability under chosen-message attack (signatures).
- Composition: Prefer AEAD or Encrypt-then-MAC; derive separate keys via HKDF labels.
Further references:
10) Side-channels & implementation hygiene
- Constant-time operations; avoid secret-dependent branches/array indices.
- Beware padding oracles; unify error messages and timing.
- Microarchitectural leaks (cache timing, branch prediction) → rely on vetted libs.
- File formats: authenticate headers/metadata via AAD; version fields.
- JWT gotchas: pin algorithms; strong keys; prefer EdDSA; validate claims/exp/aud.
Further references:
11) Key management & ops (envelope, rotation, KMS)
- Envelope: data encrypted by DEK; DEK wrapped by KEK (KMS/HSM).
- Rotation: schedule KEK rotation; re-wrap DEKs; rotate AEAD nonces per key.
- Access: least privilege; audit logs; detect anomalies.
- Secrets hygiene: avoid hard-coded keys; secret scanning in CI; commit signing.
Further references:
12) Tools & recipes (OpenSSL, GPG, CyberChef)
OpenSSL
# Random 32B key (hex)
openssl rand -hex 32
# SHA-256 digest (hex)
openssl dgst -sha256 file.bin
# RSA keypair (use OAEP/PSS in protocols, not raw RSA)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out rsa.pem
openssl rsa -in rsa.pem -pubout -out rsa.pub
GPG (hybrid)
gpg --full-generate-key
gpg --armor --export your@email
gpg --encrypt --armor -r their@email file.txt
gpg --decrypt file.txt.asc
CyberChef: build pipelines (Base64 → Hex → XOR → ROT) for Krypton-style peeling.
Further references:
13) Python mini-cookbook (cryptography
)
Install first:
pip install cryptography
AES-GCM (AEAD)
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from os import urandom
key = AESGCM.generate_key(bit_length=256) # 32 bytes
aesgcm = AESGCM(key)
nonce = urandom(12) # 96-bit unique per encryption
aad = b"header"
pt = b"secret message"
ct = aesgcm.encrypt(nonce, pt, aad) # ciphertext || tag
pt2 = aesgcm.decrypt(nonce, ct, aad) # raises if tag invalid
assert pt2 == pt
HKDF (derive labeled subkeys)
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
from os import urandom
ikm = urandom(32)
salt = urandom(16)
enc_key = HKDF(algorithm=hashes.SHA256(), length=32, salt=salt, info=b"enc").derive(ikm)
mac_key = HKDF(algorithm=hashes.SHA256(), length=32, salt=salt, info=b"mac").derive(ikm)
Ed25519 signatures
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
sk = Ed25519PrivateKey.generate()
pk = sk.public_key()
msg = b"sign me"
sig = sk.sign(msg)
pk.verify(sig, msg) # raises if invalid
X25519 key agreement + AEAD
from cryptography.hazmat.primitives.asymmetric import x25519
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from os import urandom
a_priv, b_priv = x25519.X25519PrivateKey.generate(), x25519.X25519PrivateKey.generate()
a_pub, b_pub = a_priv.public_key(), b_priv.public_key()
a_shared = a_priv.exchange(b_pub)
b_shared = b_priv.exchange(a_pub)
assert a_shared == b_shared
key = HKDF(hashes.SHA256(), 32, salt=urandom(16), info=b"x25519-aead").derive(a_shared)
aes = AESGCM(key)
nonce = urandom(12)
ct = aes.encrypt(nonce, b"psst", b"aad")
pt = aes.decrypt(nonce, ct, b"aad")
Note: Store nonces with ciphertext; ensure per-key nonce uniqueness (or use AES-SIV for misuse-resistance).
14) Krypton-inspired mini labs
Lab A — Encoding stack Hex → Base64 → Gunzip.
xxd -r -p msg.hex > msg.bin
base64 -d msg.b64 > msg.gz
file msg.gz && gunzip msg.gz
Lab B — ROT/Vigenère intuition Write a small Caesar brute and eyeball plausible English.
Lab C — AEAD integrity Flip a byte in AES-GCM ciphertext → decryption should fail.
echo "hello" > p.txt
openssl enc -aes-256-gcm -salt -pbkdf2 -in p.txt -out c.bin -pass pass:pw
printf '\x00' | dd of=c.bin bs=1 seek=10 count=1 conv=notrunc 2>/dev/null
openssl enc -d -aes-256-gcm -pbkdf2 -in c.bin -out x.txt -pass pass:pw || echo "tamper detected"
Lab D — Hash & KDF Try Argon2id with different memory/time; chart time vs settings.
15) Verification harness (inline, copy & run)
These tiny harnesses let you prove the properties above.
Shell harness (OpenSSL) — AES-GCM, tamper detection, SHA-256
```bash #!/usr/bin/env bash # crypto_verification_harness.sh # Quick checks for AES-GCM, tamper detection, and SHA-256. # Usage: bash crypto_verification_harness.sh set -euo pipefail workdir="$(mktemp -d)" trap 'rm -rf "$workdir"' EXIT echo "[*] Working dir: $workdir" cd "$workdir" # 1) Prepare plaintext echo "hello cryptography" > p.txt # 2) AES-256-GCM encrypt/decrypt with OpenSSL echo "[*] AES-GCM encrypt..." openssl enc -aes-256-gcm -salt -pbkdf2 -in p.txt -out c.bin -pass pass:'pw' >/dev/null 2>&1 echo "[*] AES-GCM decrypt..." openssl enc -d -aes-256-gcm -pbkdf2 -in c.bin -out d.txt -pass pass:'pw' >/dev/null 2>&1 if diff -q p.txt d.txt >/dev/null; then echo "[+] Decryption matches original (confidentiality OK)" else echo "[-] Decrypted text differs!" ; exit 1 fi # 3) Tamper detection echo "[*] Tamper ciphertext byte and expect failure..." printf '\x00' | dd of=c.bin bs=1 seek=10 count=1 conv=notrunc 2>/dev/null if openssl enc -d -aes-256-gcm -pbkdf2 -in c.bin -out t.txt -pass pass:'pw' >/dev/null 2>&1; then echo "[-] Decryption succeeded after tamper (unexpected)!" ; exit 1 else echo "[+] Tamper detected (integrity OK)" fi # 4) SHA-256 digest echo "[*] SHA-256 of plaintext:" openssl dgst -sha256 p.txt echo "[*] Done." ```Python harness — AES-GCM, HKDF, Ed25519 (cryptography
)
```python
#!/usr/bin/env python3
# crypto_verification_harness.py
# Tests AES-GCM, HKDF, Ed25519 using the 'cryptography' library.
# Usage: python3 crypto_verification_harness.py
import os, sys
try:
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
except Exception as e:
print("[!] Missing dependency 'cryptography'. Install via: pip install cryptography")
sys.exit(1)
def test_aes_gcm():
key = AESGCM.generate_key(bit_length=256)
aes = AESGCM(key)
nonce = os.urandom(12)
aad = b"header"
pt = b"secret message"
ct = aes.encrypt(nonce, pt, aad)
pt2 = aes.decrypt(nonce, ct, aad)
assert pt2 == pt
# tamper
tampered = bytearray(ct)
tampered[5] ^= 0xFF
try:
aes.decrypt(nonce, bytes(tampered), aad)
raise AssertionError("Tamper not detected")
except Exception:
pass
print("[+] AES-GCM OK (encrypt/decrypt + tamper detection)")
def test_hkdf():
ikm = os.urandom(32)
salt = os.urandom(16)
info = b"example-subkey"
kdf = HKDF(algorithm=hashes.SHA256(), length=32, salt=salt, info=info)
k1 = kdf.derive(ikm)
kdf2 = HKDF(algorithm=hashes.SHA256(), length=32, salt=salt, info=info)
k2 = kdf2.derive(ikm)
assert k1 == k2
print("[+] HKDF OK (deterministic for same inputs)")
def test_ed25519():
sk = Ed25519PrivateKey.generate()
pk = sk.public_key()
msg = b"sign me"
sig = sk.sign(msg)
pk.verify(sig, msg)
print("[+] Ed25519 OK (sign/verify)")
def main():
test_aes_gcm()
test_hkdf()
test_ed25519()
print("[*] All tests passed.")
if __name__ == "__main__":
main()
```
Cheatsheet (algorithms & sizes)
Purpose | Recommended | Notes |
---|---|---|
Symmetric AEAD | AES-GCM (128/256), ChaCha20-Poly1305, AES-SIV | Prefer AEAD; SIV for misuse-resistance. |
Signatures | Ed25519, RSA-PSS (2048+), ECDSA P-256 | Ed25519: simple & robust. |
Key exchange | X25519, ECDH P-256 | Use ephemeral keys for FS. |
Hash | SHA-256/512, SHA-3-256 | Avoid MD5/SHA-1. |
HKDF | HKDF-SHA256 | Separate labels per key use. |
Password KDF | Argon2id, scrypt/bcrypt | Unique salt; tune cost. |
Nonce size | GCM: 96-bit | Must be unique per key. |
RSA size | 2048/3072 bits | Prefer ECC for new designs. |
Printable 1-page cheatsheet (PNG + PDF)

[Download the PDF version](/assets/downloads/crypto/crypto_cheatsheet.pdf)
Further reading & references
Video primers (curated)
- 7 Cryptography Concepts EVERY Developer Should Know
- Lecture 1 — Christof Paar: Introduction to Cryptography
- MIT 6.875 L1 — Introduction, One-Time Pad
- Asymmetric Encryption — Simply Explained
- Public Key Cryptography — Computerphile
- AES Explained — Computerphile
- How secure is 256-bit security?
- Diffie-Hellman: How to Share a Secret
- What is a Cryptographic Hashing Function?
- Your Encryption Isn’t Quantum Safe
- What is PKI?
- The Unbreakable Kryptos Code
Books & formal
- Serious Cryptography — Aumasson
- Cryptography Engineering — Ferguson, Schneier, Kohno
- Introduction to Modern Cryptography — Katz & Lindell
Standards & specs
- RFC 8446 (TLS 1.3) • RFC 5869 (HKDF) • RFC 7748 (X25519) • RFC 8032 (Ed25519) • RFC 5116 (AEAD)
- NIST SP 800-38D (GCM) • NIST SP 800-56A (ECC key establishment) • NIST SP 800-90 (DRBGs)
Thanks for reading!
Until next time — Otsumachi!! 💖☄️✨