Symmetric Encryption
Symmetric encryption uses a single shared key for both encryption and decryption. It's fast (100–1000× faster than asymmetric), suitable for bulk data, and forms the backbone of every encrypted connection. The challenge is key distribution — both parties must securely share the key beforehand.
| Property | Symmetric | Asymmetric |
|---|---|---|
| Keys | One shared secret key | Key pair (public + private) |
| Speed | Very fast (~1 GB/s with AES-NI) | Slow (~1000× slower) |
| Key distribution | Must share key securely (the hard problem) | Public key can be shared openly |
| Use cases | Bulk data encryption, disk encryption, VPN tunnels | Key exchange, digital signatures, authentication |
| Algorithms | AES, ChaCha20, 3DES (legacy) | RSA, ECDSA, Ed25519, X25519 |
| Key sizes | 128-bit or 256-bit typical | 2048-bit RSA, 256-bit ECC |
AES (Advanced Encryption Standard)
AES is the dominant symmetric cipher. It's a block cipher operating on 128-bit (16-byte) blocks with key sizes of 128, 192, or 256 bits. AES is hardware-accelerated on modern CPUs via AES-NI instructions, achieving gigabytes/second throughput.
# Encrypt a file with AES-256-CBC using OpenSSL
echo "Top secret message: the launch codes are 12345" > secret.txt
# Encrypt — will prompt for password
openssl enc -aes-256-cbc -salt -pbkdf2 -iter 100000 \
-in secret.txt -out secret.enc
# Decrypt
openssl enc -d -aes-256-cbc -pbkdf2 -iter 100000 \
-in secret.enc -out decrypted.txt
cat decrypted.txt
# Output: Top secret message: the launch codes are 12345
# Check that your CPU supports AES-NI hardware acceleration
grep -o aes /proc/cpuinfo | head -1
# Output: aes (means hardware AES is available)
Block Cipher Modes
AES operates on fixed 16-byte blocks. Block cipher modes define how to handle messages longer than one block and how to prevent patterns in plaintext from leaking into ciphertext.
# Compare ECB vs CBC — demonstrating pattern leakage
# Create a file with repeating 16-byte blocks
python3 -c "print('A' * 16 * 10)" > repeated.txt
# Encrypt with ECB (BAD — patterns preserved)
openssl enc -aes-256-ecb -nosalt -pass pass:mykey \
-in repeated.txt -out repeated_ecb.enc
# Encrypt with CBC (GOOD — patterns hidden by chaining)
openssl enc -aes-256-cbc -nosalt -pass pass:mykey \
-in repeated.txt -out repeated_cbc.enc
# Compare: ECB output has repeated blocks, CBC doesn't
xxd repeated_ecb.enc | head -5
xxd repeated_cbc.enc | head -5
# Use GCM mode (preferred — AEAD)
openssl enc -aes-256-gcm -salt -pbkdf2 -iter 100000 \
-in secret.txt -out secret_gcm.enc
Asymmetric Encryption
Asymmetric (public-key) cryptography uses a mathematically linked key pair: a public key (shared openly) and a private key (kept secret). Data encrypted with the public key can only be decrypted with the private key, and vice versa. This solves the key distribution problem — you can encrypt a message for someone you've never met, using only their public key.
sequenceDiagram
participant A as Alice
participant B as Bob
Note over A,B: Encryption (Confidentiality)
A->>A: Encrypt message with Bob's PUBLIC key
A->>B: Send encrypted message
B->>B: Decrypt with Bob's PRIVATE key
Note over A,B: Digital Signature (Authentication)
A->>A: Sign hash with Alice's PRIVATE key
A->>B: Send message + signature
B->>B: Verify signature with Alice's PUBLIC key
RSA
RSA is the most widely deployed asymmetric algorithm. Its security relies on the difficulty of factoring large prime products. RSA keys are typically 2048 or 4096 bits. RSA is used for key exchange (encrypt a symmetric session key) and digital signatures (sign a document hash).
# Generate an RSA 2048-bit private key
openssl genrsa -out private.pem 2048
# Extract the public key from the private key
openssl rsa -in private.pem -pubout -out public.pem
# View key details (modulus size, exponent)
openssl rsa -in private.pem -text -noout | head -5
# Output: Private-Key: (2048 bit, 2 primes)
# Encrypt a small message with the public key
echo "Secret session key: abc123" > message.txt
openssl rsautl -encrypt -pubin -inkey public.pem \
-in message.txt -out message.enc
# Decrypt with the private key
openssl rsautl -decrypt -inkey private.pem \
-in message.enc -out message_dec.txt
cat message_dec.txt
# Output: Secret session key: abc123
Elliptic Curve Cryptography (ECC)
ECC provides equivalent security to RSA with much smaller keys. A 256-bit ECC key offers security comparable to a 3072-bit RSA key. This means faster operations, smaller certificates, and less bandwidth — making ECC the standard for modern TLS and SSH.
# Generate an EC private key using the P-256 curve (secp256r1)
openssl ecparam -genkey -name prime256v1 -out ec_private.pem
# Extract the public key
openssl ec -in ec_private.pem -pubout -out ec_public.pem
# View curve and key details
openssl ec -in ec_private.pem -text -noout | head -5
# Output: EC-Parameters: (256 bit)
# List available curves
openssl ecparam -list_curves | head -10
# Common curves: prime256v1 (P-256), secp384r1 (P-384), secp521r1 (P-521)
# Ed25519 — modern, fast, constant-time (used in SSH, Signal)
openssl genpkey -algorithm Ed25519 -out ed25519_private.pem
openssl pkey -in ed25519_private.pem -pubout -out ed25519_public.pem
Hashing
A cryptographic hash function maps arbitrary-length input to a fixed-size output (the digest) with three critical properties: (1) pre-image resistance — can't reverse the hash to find the input, (2) second pre-image resistance — can't find a different input with the same hash, (3) collision resistance — can't find any two inputs with the same hash.
| Algorithm | Output Size | Status | Use Case |
|---|---|---|---|
| MD5 | 128 bits (32 hex) | Broken | Checksums only (not security) |
| SHA-1 | 160 bits (40 hex) | Broken | Legacy git commits (deprecated) |
| SHA-256 | 256 bits (64 hex) | Current | TLS, code signing, blockchain |
| SHA-3 (Keccak) | 256/512 bits | Current | Backup if SHA-2 family breaks |
| bcrypt | 184 bits | Current | Password storage (slow by design) |
| Argon2 | Configurable | Current (best) | Password storage (memory-hard) |
SHA-256
# Hash a string with SHA-256
echo -n "hello world" | sha256sum
# b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 -
# Hash a file
sha256sum /etc/passwd
# Compare file integrity (download verification)
# Download a file and its checksum
echo "important data" > download.bin
sha256sum download.bin > download.sha256
# Later: verify the file hasn't been modified
sha256sum -c download.sha256
# Output: download.bin: OK
# Multiple hash algorithms for comparison
echo -n "hello" | md5sum # 128-bit (5d41402abc4b2a76b9719d911017c592)
echo -n "hello" | sha1sum # 160-bit
echo -n "hello" | sha256sum # 256-bit
echo -n "hello" | sha512sum # 512-bit
bcrypt for Passwords
Never store passwords as plain SHA-256 hashes — attackers can crack billions of hashes per second with GPUs. bcrypt and Argon2 are intentionally slow (tunable work factor) and include a random salt, making rainbow tables useless and brute-force prohibitively expensive.
# Generate a bcrypt hash (using htpasswd from Apache utils)
# Install: sudo apt install apache2-utils
htpasswd -nbBC 12 "" "MyPassword123" | cut -d: -f2
# Output: $2y$12$... (the 12 = cost factor = 2^12 iterations)
# Using Python for bcrypt
python3 -c "
import hashlib, os
# BAD: plain SHA-256 (crackable at billions/sec)
plain_hash = hashlib.sha256(b'MyPassword123').hexdigest()
print(f'SHA-256 (BAD): {plain_hash[:40]}...')
# BETTER: SHA-256 with salt (resists rainbow tables, still fast to crack)
salt = os.urandom(16)
salted = hashlib.sha256(salt + b'MyPassword123').hexdigest()
print(f'Salted SHA-256: {salted[:40]}...')
# BEST: use bcrypt or argon2 (slow by design)
# pip install bcrypt
# import bcrypt
# hashed = bcrypt.hashpw(b'MyPassword123', bcrypt.gensalt(rounds=12))
print('BEST: Use bcrypt.hashpw() or argon2 — intentionally slow')
"
Digital Signatures
A digital signature proves authenticity (who signed it) and integrity (the message wasn't altered). The signer hashes the message, then encrypts the hash with their private key. Anyone with the signer's public key can verify: they decrypt the signature to get the hash, then independently hash the message and compare. If they match, the message is authentic and unmodified.
# Step 1: Create a document to sign
echo "I authorize the transfer of $1,000,000 to Account 12345" > contract.txt
# Step 2: Generate an RSA key pair (if not already done)
openssl genrsa -out signer_private.pem 2048
openssl rsa -in signer_private.pem -pubout -out signer_public.pem
# Step 3: Sign the document (hash + encrypt hash with private key)
openssl dgst -sha256 -sign signer_private.pem \
-out contract.sig contract.txt
# Step 4: Verify the signature (anyone with the public key can do this)
openssl dgst -sha256 -verify signer_public.pem \
-signature contract.sig contract.txt
# Output: Verified OK
# Step 5: Tamper with the document and verify again
echo "I authorize the transfer of $9,999,999 to Account 99999" > contract.txt
openssl dgst -sha256 -verify signer_public.pem \
-signature contract.sig contract.txt
# Output: Verification Failure — the document was modified!
# Sign with ECDSA (smaller signatures, faster)
openssl ecparam -genkey -name prime256v1 -out ec_signer.pem
openssl ec -in ec_signer.pem -pubout -out ec_signer_pub.pem
echo "Signed with ECC" > doc.txt
openssl dgst -sha256 -sign ec_signer.pem -out doc.sig doc.txt
openssl dgst -sha256 -verify ec_signer_pub.pem -signature doc.sig doc.txt
# Output: Verified OK
# Compare signature sizes
wc -c contract.sig # RSA-2048: 256 bytes
wc -c doc.sig # ECDSA P-256: ~72 bytes (much smaller!)
Key Exchange — Diffie-Hellman
Diffie-Hellman (DH) allows two parties to establish a shared secret over an insecure channel without ever transmitting the secret itself. Each party generates a private value, computes a public value from it, exchanges public values, and then both independently derive the same shared secret. An eavesdropper who sees only the public values cannot compute the shared secret (the Discrete Logarithm Problem).
# Demonstrate Diffie-Hellman key exchange with OpenSSL
# Generate DH parameters (shared prime p and generator g)
openssl dhparam -out dhparams.pem 2048
# Alice generates her DH private/public key
openssl genpkey -paramfile dhparams.pem -out alice_dh_private.pem
openssl pkey -in alice_dh_private.pem -pubout -out alice_dh_public.pem
# Bob generates his DH private/public key
openssl genpkey -paramfile dhparams.pem -out bob_dh_private.pem
openssl pkey -in bob_dh_private.pem -pubout -out bob_dh_public.pem
# Alice computes the shared secret using her private key + Bob's public key
openssl pkeyutl -derive -inkey alice_dh_private.pem \
-peerkey bob_dh_public.pem -out alice_shared.bin
# Bob computes the shared secret using his private key + Alice's public key
openssl pkeyutl -derive -inkey bob_dh_private.pem \
-peerkey alice_dh_public.pem -out bob_shared.bin
# Both shared secrets are identical!
sha256sum alice_shared.bin bob_shared.bin
# Same hash = same secret derived independently
# ECDH — Elliptic Curve Diffie-Hellman (faster, smaller keys)
# Used in modern TLS (X25519 curve)
# Alice's ECDH key pair
openssl genpkey -algorithm X25519 -out alice_x25519.pem
openssl pkey -in alice_x25519.pem -pubout -out alice_x25519_pub.pem
# Bob's ECDH key pair
openssl genpkey -algorithm X25519 -out bob_x25519.pem
openssl pkey -in bob_x25519.pem -pubout -out bob_x25519_pub.pem
# Derive shared secrets
openssl pkeyutl -derive -inkey alice_x25519.pem \
-peerkey bob_x25519_pub.pem -out shared_alice.bin
openssl pkeyutl -derive -inkey bob_x25519.pem \
-peerkey alice_x25519_pub.pem -out shared_bob.bin
# Verify they match
diff shared_alice.bin shared_bob.bin && echo "Shared secrets match!"
OpenSSL in Practice
OpenSSL is the Swiss Army knife of cryptography on Linux — a command-line tool and library for encryption, hashing, key generation, certificate management, and protocol testing. Every sysadmin and developer should know these essential commands.
# Generate cryptographically secure random bytes
openssl rand -hex 32 # 32 random bytes as hex (64 hex chars)
openssl rand -base64 24 # 24 random bytes as base64
# Benchmark your system's crypto performance
openssl speed aes-256-cbc sha256 rsa2048
# Shows operations/second for each algorithm
# Inspect a remote server's TLS certificate
openssl s_client -connect google.com:443 -brief
# Shows: protocol, cipher, certificate chain
# View certificate details
echo | openssl s_client -connect google.com:443 2>/dev/null | \
openssl x509 -noout -subject -issuer -dates
# Subject: CN = *.google.com
# Issuer: CN = GTS CA 1C3
# Validity dates
# Generate a self-signed certificate (for dev/testing)
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \
-days 365 -nodes -subj "/CN=localhost"
# Verify file integrity with HMAC (keyed hash)
echo -n "important data" | openssl dgst -sha256 -hmac "shared-secret-key"
# Only someone with the same key can produce the same HMAC
How TLS Uses All These Primitives Together
A single TLS (HTTPS) connection uses every primitive covered in this article:
1. Key Exchange (Diffie-Hellman/ECDH): Client and server perform ECDHE (Ephemeral Elliptic Curve Diffie-Hellman) to derive a shared secret — without ever transmitting it.
2. Symmetric Encryption (AES-256-GCM): The shared secret becomes the session key for AES-GCM, which encrypts all subsequent data at wire speed.
3. Hashing (SHA-256): Used inside the key derivation function (HKDF) and for HMAC integrity checks.
4. Digital Signatures (ECDSA/RSA): The server's certificate contains its public key, signed by a Certificate Authority's private key. The client verifies this chain to confirm the server's identity.
5. Asymmetric Encryption: In older TLS versions (RSA key exchange), the client encrypted the pre-master secret with the server's RSA public key. Modern TLS 1.3 uses only DH-based key exchange.
Exercises
# Exercise 1: Encrypt and decrypt a file with AES-256-GCM
echo "Exercise 1 secret" > ex1.txt
openssl enc -aes-256-cbc -salt -pbkdf2 -iter 100000 -in ex1.txt -out ex1.enc
openssl enc -d -aes-256-cbc -pbkdf2 -iter 100000 -in ex1.enc -out ex1_dec.txt
diff ex1.txt ex1_dec.txt && echo "Decryption successful!"
# Exercise 2: Generate an RSA key pair and sign a document
openssl genrsa -out ex2_key.pem 2048
openssl rsa -in ex2_key.pem -pubout -out ex2_pub.pem
echo "This is my signed document" > ex2_doc.txt
openssl dgst -sha256 -sign ex2_key.pem -out ex2_doc.sig ex2_doc.txt
openssl dgst -sha256 -verify ex2_pub.pem -signature ex2_doc.sig ex2_doc.txt
# Exercise 3: Compare hash speeds
time openssl dgst -md5 /dev/zero < /dev/null
time openssl dgst -sha256 /dev/zero < /dev/null
# Use: dd if=/dev/zero bs=1M count=100 | openssl dgst -sha256
# Exercise 4: Check a website's TLS certificate expiry
echo | openssl s_client -connect github.com:443 2>/dev/null | \
openssl x509 -noout -dates
# Exercise 5: Generate random passwords/tokens
openssl rand -base64 32 # 256-bit random token
openssl rand -hex 16 # 128-bit hex string (API key)
Conclusion & Next Steps
Cryptography is the foundation of all digital security. Symmetric encryption (AES) provides fast bulk encryption. Asymmetric encryption (RSA, ECC) solves key distribution and enables digital signatures. Hashing (SHA-256) provides integrity verification. Diffie-Hellman enables secure key exchange over insecure channels. Together, these primitives compose into protocols like TLS that secure every HTTPS connection. In Part 17, we'll see how TLS and PKI (Public Key Infrastructure) orchestrate these building blocks into the certificate trust chain that makes the internet work.