Back to Computing & Systems Foundations Series

Part 16: Cryptography Fundamentals

May 13, 2026Wasil Zafar19 min read

The building blocks of all security — symmetric and asymmetric encryption, hashing, digital signatures, key exchange, and hands-on OpenSSL commands.

Table of Contents

  1. Symmetric Encryption
  2. Asymmetric Encryption
  3. Hashing
  4. Digital Signatures
  5. Key Exchange — Diffie-Hellman
  6. OpenSSL in Practice
  7. Exercises
  8. Conclusion

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.

PropertySymmetricAsymmetric
KeysOne shared secret keyKey pair (public + private)
SpeedVery fast (~1 GB/s with AES-NI)Slow (~1000× slower)
Key distributionMust share key securely (the hard problem)Public key can be shared openly
Use casesBulk data encryption, disk encryption, VPN tunnelsKey exchange, digital signatures, authentication
AlgorithmsAES, ChaCha20, 3DES (legacy)RSA, ECDSA, Ed25519, X25519
Key sizes128-bit or 256-bit typical2048-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.

Never Use ECB Mode: ECB (Electronic Codebook) encrypts each block independently with the same key. Identical plaintext blocks produce identical ciphertext blocks, leaking patterns. The famous "ECB penguin" demonstrates this — encrypt a bitmap image in ECB and the shape is still visible. Always use CBC (Cipher Block Chaining) or GCM (Galois/Counter Mode). GCM is preferred because it provides both encryption and authentication (AEAD — Authenticated Encryption with Associated Data), preventing tampering without a separate HMAC step.
# 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.

Asymmetric Encryption & Digital Signature Flow
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.

AlgorithmOutput SizeStatusUse Case
MD5128 bits (32 hex)BrokenChecksums only (not security)
SHA-1160 bits (40 hex)BrokenLegacy git commits (deprecated)
SHA-256256 bits (64 hex)CurrentTLS, code signing, blockchain
SHA-3 (Keccak)256/512 bitsCurrentBackup if SHA-2 family breaks
bcrypt184 bitsCurrentPassword storage (slow by design)
Argon2ConfigurableCurrent (best)Password storage (memory-hard)
MD5 and SHA-1 Are Broken: Both have demonstrated collision attacks — attackers can craft two different files with the same hash. In 2017, Google's SHAttered attack produced two different PDFs with identical SHA-1 hashes (cost: ~$110K in compute). MD5 collisions are trivial (seconds on a laptop). Never use MD5 or SHA-1 for security (certificates, signatures, integrity). Use SHA-256 or SHA-3 for integrity checks, and bcrypt/Argon2 for passwords.

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 It All Fits Together

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.

TLS 1.3ECDHEAEADCertificate Chain

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.