#
Encryption: Protecting Data Through Cryptography
FOUNDATIONAL SECURITY CONCEPT DATA PROTECTION
#
What is Encryption?
Simple Definition: Encryption is the process of converting readable data (plaintext) into an unreadable format (ciphertext) to protect it from unauthorized access. Only those with the correct decryption key can convert it back to readable form.
Real-World Analogy: Think of encryption like a lockbox. You put your valuables (data) inside, lock it with a key, and even if someone steals the box, they can't access the contents without the key.
#
Why Encryption Matters
#
The Digital Security Foundation
Encryption is the backbone of modern digital security. Without it:
Your passwords would be visible to anyone intercepting network traffic Credit card numbers would be stolen during online purchases Private messages could be read by hackers or governments Business secrets would be exposed to competitors Personal photos and files would be accessible to attackers
#
Real-World Impact
WITHOUT ENCRYPTION
- 2013 Target Breach: 40 million credit cards stolen because payment data wasn't properly encrypted
- 2014 Sony Pictures Hack: Confidential emails and documents exposed due to poor encryption practices
- 2017 Equifax Breach: 147 million SSNs exposed, partly due to unencrypted data transmission
WITH ENCRYPTION
- WhatsApp: End-to-end encryption protects 2+ billion users' messages
- Apple: iPhone encryption prevents unauthorized access even by law enforcement
- Banking: HTTPS encryption protects trillions of dollars in daily transactions
#
How Encryption Works
#
The Basic Process
Start with readable data that needs protection.
Original Message: "Hello, this is a secret message"
Use a mathematical algorithm with a key to transform the data.
Algorithm: AES-256
Key: "my-secret-encryption-key-12345"
The output is scrambled, unreadable data.
Encrypted Output: "8f3a9c7e2d1b5a4f9e3c7d2a1b8f4e6c..."
Only someone with the correct key can reverse the process.
Decrypted Output: "Hello, this is a secret message"
#
Types of Encryption
#
1. Symmetric Encryption
SINGLE KEY FAST
How It Works: Uses the same key for both encryption and decryption.
Real-World Analogy: Like a house key that both locks and unlocks the door.
Advantages:
- Fast: Very quick encryption/decryption
- Efficient: Low computational overhead
- Strong: Can provide excellent security
Disadvantages:
- Key Distribution Problem: How do you securely share the key?
- Key Management: Need different keys for different parties
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
# Generate a random 256-bit key
key = get_random_bytes(32) # 32 bytes = 256 bits
# Create cipher object
cipher = AES.new(key, AES.MODE_CBC)
# Encrypt data
plaintext = b"Hello, this is a secret message"
ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
print(f"Original: {plaintext}")
print(f"Encrypted: {ciphertext.hex()}")
print(f"IV: {cipher.iv.hex()}")
# Decrypt data
decipher = AES.new(key, AES.MODE_CBC, cipher.iv)
decrypted = unpad(decipher.decrypt(ciphertext), AES.block_size)
print(f"Decrypted: {decrypted}")
const crypto = require('crypto');
// Generate a random 256-bit key
const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);
// Encrypt
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = cipher.update('Hello, this is a secret message', 'utf8', 'hex');
encrypted += cipher.final('hex');
console.log('Original:', 'Hello, this is a secret message');
console.log('Encrypted:', encrypted);
// Decrypt
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
console.log('Decrypted:', decrypted);
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.util.Base64;
public class AESExample {
public static void main(String[] args) throws Exception {
// Generate key
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey key = keyGen.generateKey();
// Generate IV
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// Encrypt
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted = cipher.doFinal(
"Hello, this is a secret message".getBytes()
);
System.out.println("Encrypted: " +
Base64.getEncoder().encodeToString(encrypted));
// Decrypt
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Decrypted: " + new String(decrypted));
}
}
#
Common Symmetric Algorithms
#
2. Asymmetric Encryption (Public Key Cryptography)
KEY PAIR SECURE DISTRIBUTION
How It Works: Uses two different keys - a public key for encryption and a private key for decryption.
Real-World Analogy: Like a mailbox - anyone can drop mail in (public key), but only the owner with the key can open it (private key).
Advantages:
- No Key Distribution Problem: Public key can be shared openly
- Digital Signatures: Can prove authenticity and integrity
- Better Key Management: Don't need to share secret keys
Disadvantages:
- Slower: 100-1000x slower than symmetric encryption
- Resource Intensive: Requires more computational power
- Larger Output: Ciphertext is larger than plaintext
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
import base64
# Generate RSA key pair
key = RSA.generate(2048)
private_key = key
public_key = key.publickey()
print("Public Key:")
print(public_key.export_key().decode())
# Encrypt with public key
cipher = PKCS1_OAEP.new(public_key)
message = b"Hello, this is a secret message"
encrypted = cipher.encrypt(message)
print(f"\nOriginal: {message}")
print(f"Encrypted: {base64.b64encode(encrypted).decode()}")
# Decrypt with private key
decipher = PKCS1_OAEP.new(private_key)
decrypted = decipher.decrypt(encrypted)
print(f"Decrypted: {decrypted}")
const crypto = require('crypto');
// Generate RSA key pair
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
console.log('Public Key:', publicKey);
// Encrypt with public key
const message = 'Hello, this is a secret message';
const encrypted = crypto.publicEncrypt(
publicKey,
Buffer.from(message)
);
console.log('\nOriginal:', message);
console.log('Encrypted:', encrypted.toString('base64'));
// Decrypt with private key
const decrypted = crypto.privateDecrypt(
privateKey,
encrypted
);
console.log('Decrypted:', decrypted.toString());
import java.security.*;
import javax.crypto.Cipher;
import java.util.Base64;
public class RSAExample {
public static void main(String[] args) throws Exception {
// Generate key pair
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair pair = keyGen.generateKeyPair();
PublicKey publicKey = pair.getPublic();
PrivateKey privateKey = pair.getPrivate();
// Encrypt with public key
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encrypted = cipher.doFinal(
"Hello, this is a secret message".getBytes()
);
System.out.println("Encrypted: " +
Base64.getEncoder().encodeToString(encrypted));
// Decrypt with private key
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Decrypted: " + new String(decrypted));
}
}
#
Common Asymmetric Algorithms
#
3. Hybrid Encryption (Best of Both Worlds)
INDUSTRY STANDARD HTTPS/TLS
How It Works: Combines symmetric and asymmetric encryption to get the benefits of both.
Process:
- Generate a random symmetric key (e.g., AES key)
- Encrypt the actual data with the symmetric key (fast)
- Encrypt the symmetric key with the recipient's public key (secure distribution)
- Send both the encrypted data and encrypted key
┌─────────────┐
│ Sender │
└──────┬──────┘
│
├─1─> Generate random AES key
│
├─2─> Encrypt message with AES key (fast)
│ Message: "Secret data..."
│ Result: [Encrypted Data]
│
├─3─> Encrypt AES key with recipient's RSA public key
│ AES Key: "random-key-123"
│ Result: [Encrypted Key]
│
└─4─> Send both [Encrypted Data] + [Encrypted Key]
│
▼
┌──────────────┐
│ Recipient │
└──────┬───────┘
│
├─5─> Decrypt AES key with RSA private key
│ Result: "random-key-123"
│
└─6─> Decrypt data with AES key
Result: "Secret data..."
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.Random import get_random_bytes
import base64
# Generate RSA key pair
rsa_key = RSA.generate(2048)
public_key = rsa_key.publickey()
# === ENCRYPTION ===
# 1. Generate random AES key
aes_key = get_random_bytes(32)
# 2. Encrypt data with AES (symmetric)
aes_cipher = AES.new(aes_key, AES.MODE_EAX)
message = b"This is a large amount of secret data that needs to be encrypted efficiently..."
ciphertext, tag = aes_cipher.encrypt_and_digest(message)
# 3. Encrypt AES key with RSA public key (asymmetric)
rsa_cipher = PKCS1_OAEP.new(public_key)
encrypted_aes_key = rsa_cipher.encrypt(aes_key)
print("=== ENCRYPTED ===")
print(f"Encrypted AES Key: {base64.b64encode(encrypted_aes_key).decode()[:50]}...")
print(f"Encrypted Data: {base64.b64encode(ciphertext).decode()[:50]}...")
# === DECRYPTION ===
# 4. Decrypt AES key with RSA private key
rsa_decipher = PKCS1_OAEP.new(rsa_key)
decrypted_aes_key = rsa_decipher.decrypt(encrypted_aes_key)
# 5. Decrypt data with AES key
aes_decipher = AES.new(decrypted_aes_key, AES.MODE_EAX, aes_cipher.nonce)
decrypted_message = aes_decipher.decrypt_and_verify(ciphertext, tag)
print("\n=== DECRYPTED ===")
print(f"Message: {decrypted_message.decode()}")
const crypto = require('crypto');
// Generate RSA key pair
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048
});
// === ENCRYPTION ===
// 1. Generate random AES key
const aesKey = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);
// 2. Encrypt data with AES
const message = 'This is a large amount of secret data...';
const aesCipher = crypto.createCipheriv('aes-256-cbc', aesKey, iv);
let encrypted = aesCipher.update(message, 'utf8', 'hex');
encrypted += aesCipher.final('hex');
// 3. Encrypt AES key with RSA public key
const encryptedAesKey = crypto.publicEncrypt(
publicKey,
Buffer.concat([aesKey, iv])
);
console.log('=== ENCRYPTED ===');
console.log('Encrypted AES Key:', encryptedAesKey.toString('base64').slice(0, 50) + '...');
console.log('Encrypted Data:', encrypted.slice(0, 50) + '...');
// === DECRYPTION ===
// 4. Decrypt AES key with RSA private key
const decryptedKeys = crypto.privateDecrypt(privateKey, encryptedAesKey);
const decryptedAesKey = decryptedKeys.slice(0, 32);
const decryptedIv = decryptedKeys.slice(32);
// 5. Decrypt data with AES
const aesDecipher = crypto.createDecipheriv('aes-256-cbc', decryptedAesKey, decryptedIv);
let decrypted = aesDecipher.update(encrypted, 'hex', 'utf8');
decrypted += aesDecipher.final('utf8');
console.log('\n=== DECRYPTED ===');
console.log('Message:', decrypted);
Where Hybrid Encryption is Used:
- HTTPS/TLS: Every secure website
- Email Encryption: PGP/GPG
- Messaging Apps: Signal, WhatsApp
- Cloud Storage: Encrypted file storage
- VPN: Secure tunnel establishment
#
Encryption Modes of Operation
When using block ciphers like AES, you need to choose a mode of operation that determines how blocks of data are encrypted.
#
Common Modes
SECURE REQUIRES IV
How It Works: Each plaintext block is XORed with the previous ciphertext block before encryption.
Advantages:
- Secure when implemented correctly
- Same plaintext produces different ciphertext (with different IV)
Disadvantages:
- Sequential (can't parallelize encryption)
- Requires padding
- Vulnerable to padding oracle attacks if not handled properly
Use Case: File encryption, database encryption
# CBC Mode
cipher = AES.new(key, AES.MODE_CBC)
ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
# Must store cipher.iv for decryption!
RECOMMENDED AUTHENTICATED
How It Works: Combines encryption with authentication, providing both confidentiality and integrity.
Advantages:
- Authenticated encryption (detects tampering)
- Parallelizable (fast)
- No padding required
- Built-in integrity check
Disadvantages:
- Slightly more complex
- IV reuse is catastrophic
Use Case: HTTPS/TLS, modern applications, network protocols
# GCM Mode (Authenticated Encryption)
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
# Tag verifies data hasn't been tampered with
NEVER USE INSECURE
How It Works: Each block is encrypted independently with the same key.
Problems:
- Same plaintext block → same ciphertext block
- Patterns in plaintext visible in ciphertext
- No integrity protection
Famous Example: The ECB Penguin
Original Image → ECB Encryption → Pattern Still Visible! ❌
NEVER USE ECB MODE IN PRODUCTION!
SECURE PARALLELIZABLE
How It Works: Converts block cipher into stream cipher using counter.
Advantages:
- Parallelizable (very fast)
- No padding required
- Random access possible
Disadvantages:
- No built-in authentication
- IV/nonce must never repeat
Use Case: Disk encryption, high-performance applications
# CTR Mode
cipher = AES.new(key, AES.MODE_CTR)
ciphertext = cipher.encrypt(plaintext)
#
Mode Comparison
#
Key Management
CRITICAL The security of encryption depends entirely on keeping keys secret!
#
Key Generation
ALWAYS use cryptographically secure random number generators:
from Crypto.Random import get_random_bytes
# ✅ CORRECT: Cryptographically secure
key = get_random_bytes(32) # 256-bit key
# ❌ WRONG: Predictable
import random
key = bytes([random.randint(0, 255) for _ in range(32)]) # INSECURE!
const crypto = require('crypto');
// ✅ CORRECT: Cryptographically secure
const key = crypto.randomBytes(32);
// ❌ WRONG: Predictable
const insecureKey = Buffer.from(
Array.from({length: 32}, () => Math.floor(Math.random() * 256))
); // INSECURE!
import java.security.SecureRandom;
// ✅ CORRECT: Cryptographically secure
SecureRandom random = new SecureRandom();
byte[] key = new byte[32];
random.nextBytes(key);
// ❌ WRONG: Predictable
Random insecureRandom = new Random();
insecureRandom.nextBytes(key); // INSECURE!
#
Key Storage
NEVER HARDCODE KEYS
Bad Practices:
# ❌ NEVER DO THIS
SECRET_KEY = "my-secret-key-12345" # Hardcoded in source
API_KEY = "sk_live_abc123xyz" # Committed to Git
# ❌ NEVER DO THIS
def encrypt_data(data):
key = b"0123456789abcdef" # Hardcoded key
cipher = AES.new(key, AES.MODE_GCM)
return cipher.encrypt(data)
Good Practices:
import os
from base64 import b64decode
# ✅ Load from environment
key = b64decode(os.environ['ENCRYPTION_KEY'])
# Set in environment
export ENCRYPTION_KEY="base64_encoded_key_here"
# ✅ Use AWS KMS
import boto3
kms = boto3.client('kms')
response = kms.decrypt(CiphertextBlob=encrypted_key)
key = response['Plaintext']
# ✅ Use Azure Key Vault
from azure.keyvault.secrets import SecretClient
client = SecretClient(vault_url=vault_url, credential=credential)
key = client.get_secret("encryption-key").value
# ✅ Use HSM for high-security applications
from pkcs11 import Session
# Keys never leave the HSM hardware
session = Session(...)
key = session.get_key(...)
encrypted = key.encrypt(data)
#
Key Rotation
Best Practice: Regularly rotate encryption keys.
from datetime import datetime, timedelta
class KeyManager:
def __init__(self):
self.keys = {}
self.current_key_id = None
def rotate_key(self):
"""Rotate encryption key every 90 days"""
new_key_id = datetime.now().strftime("%Y%m%d")
new_key = get_random_bytes(32)
self.keys[new_key_id] = {
'key': new_key,
'created': datetime.now(),
'expires': datetime.now() + timedelta(days=90)
}
self.current_key_id = new_key_id
return new_key_id
def encrypt(self, data):
"""Encrypt with current key"""
key_id = self.current_key_id
key = self.keys[key_id]['key']
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(data)
# Include key_id so we know which key to use for decryption
return {
'key_id': key_id,
'ciphertext': ciphertext,
'nonce': cipher.nonce,
'tag': tag
}
def decrypt(self, encrypted_data):
"""Decrypt with appropriate key"""
key_id = encrypted_data['key_id']
key = self.keys[key_id]['key']
cipher = AES.new(key, AES.MODE_GCM, nonce=encrypted_data['nonce'])
return cipher.decrypt_and_verify(
encrypted_data['ciphertext'],
encrypted_data['tag']
)
#
Common Use Cases
#
1. Encrypting Data at Rest
Scenario: Protecting stored data (databases, files, backups)
import sqlite3
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
import json
class EncryptedDatabase:
def __init__(self, db_path, master_key):
self.conn = sqlite3.connect(db_path)
self.master_key = master_key
def store_encrypted(self, table, data):
"""Store encrypted data"""
# Generate unique key for this record
key = get_random_bytes(32)
# Encrypt data
cipher = AES.new(key, AES.MODE_GCM)
json_data = json.dumps(data).encode()
ciphertext, tag = cipher.encrypt_and_digest(json_data)
# Encrypt the key with master key
master_cipher = AES.new(self.master_key, AES.MODE_GCM)
encrypted_key, key_tag = master_cipher.encrypt_and_digest(key)
# Store in database
self.conn.execute(
f"INSERT INTO {table} (data, key, nonce, tag, key_nonce, key_tag) VALUES (?, ?, ?, ?, ?, ?)",
(ciphertext, encrypted_key, cipher.nonce, tag, master_cipher.nonce, key_tag)
)
self.conn.commit()
def retrieve_encrypted(self, table, record_id):
"""Retrieve and decrypt data"""
cursor = self.conn.execute(
f"SELECT data, key, nonce, tag, key_nonce, key_tag FROM {table} WHERE id = ?",
(record_id,)
)
row = cursor.fetchone()
# Decrypt the key
master_cipher = AES.new(self.master_key, AES.MODE_GCM, nonce=row[4])
key = master_cipher.decrypt_and_verify(row[1], row[5])
# Decrypt the data
cipher = AES.new(key, AES.MODE_GCM, nonce=row[2])
json_data = cipher.decrypt_and_verify(row[0], row[3])
return json.loads(json_data)
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
import os
def encrypt_file(input_file, output_file, password):
"""Encrypt a file with password"""
# Derive key from password
salt = get_random_bytes(32)
key = PBKDF2(password, salt, dkLen=32, count=1000000)
# Read file
with open(input_file, 'rb') as f:
plaintext = f.read()
# Encrypt
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
# Write encrypted file
with open(output_file, 'wb') as f:
f.write(salt) # 32 bytes
f.write(cipher.nonce) # 16 bytes
f.write(tag) # 16 bytes
f.write(ciphertext) # Variable
def decrypt_file(input_file, output_file, password):
"""Decrypt a file with password"""
# Read encrypted file
with open(input_file, 'rb') as f:
salt = f.read(32)
nonce = f.read(16)
tag = f.read(16)
ciphertext = f.read()
# Derive key from password
key = PBKDF2(password, salt, dkLen=32, count=1000000)
# Decrypt
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
# Write decrypted file
with open(output_file, 'wb') as f:
f.write(plaintext)
# Usage
encrypt_file('secret.pdf', 'secret.pdf.enc', 'strong-password-123')
decrypt_file('secret.pdf.enc', 'secret.pdf', 'strong-password-123')
#
2. Encrypting Data in Transit
Scenario: Protecting data during transmission (HTTPS, VPN, APIs)
Modern web applications should always use HTTPS:
# Flask with HTTPS
from flask import Flask
app = Flask(__name__)
@app.route('/api/data')
def get_data():
return {'secret': 'data'}
if __name__ == '__main__':
# Use SSL context
app.run(
ssl_context=('cert.pem', 'key.pem'),
host='0.0.0.0',
port=443
)
// Node.js with HTTPS
const https = require('https');
const fs = require('fs');
const express = require('express');
const app = express();
const options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
};
https.createServer(options, app).listen(443);
class SecureChannel:
"""End-to-end encrypted communication"""
def __init__(self):
# Each party generates key pair
self.private_key = RSA.generate(2048)
self.public_key = self.private_key.publickey()
def get_public_key(self):
"""Share public key with other party"""
return self.public_key.export_key()
def encrypt_message(self, message, recipient_public_key_pem):
"""Encrypt message for recipient"""
recipient_public_key = RSA.import_key(recipient_public_key_pem)
# Generate session key
session_key = get_random_bytes(32)
# Encrypt message with session key
cipher = AES.new(session_key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(message.encode())
# Encrypt session key with recipient's public key
rsa_cipher = PKCS1_OAEP.new(recipient_public_key)
encrypted_session_key = rsa_cipher.encrypt(session_key)
return {
'encrypted_key': encrypted_session_key,
'nonce': cipher.nonce,
'tag': tag,
'ciphertext': ciphertext
}
def decrypt_message(self, encrypted_data):
"""Decrypt received message"""
# Decrypt session key with private key
rsa_cipher = PKCS1_OAEP.new(self.private_key)
session_key = rsa_cipher.decrypt(encrypted_data['encrypted_key'])
# Decrypt message with session key
cipher = AES.new(session_key, AES.MODE_GCM, nonce=encrypted_data['nonce'])
plaintext = cipher.decrypt_and_verify(
encrypted_data['ciphertext'],
encrypted_data['tag']
)
return plaintext.decode()
# Usage
alice = SecureChannel()
bob = SecureChannel()
# Alice encrypts message for Bob
encrypted = alice.encrypt_message("Hi Bob!", bob.get_public_key())
# Bob decrypts Alice's message
message = bob.decrypt_message(encrypted)
print(message) # "Hi Bob!"
#
3. Password Storage (Hashing, Not Encryption!)
IMPORTANT Passwords should be HASHED, not encrypted!
Why? You should never be able to retrieve the original password.
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
ph = PasswordHasher()
# Store password (registration)
password = "user_password_123"
hashed = ph.hash(password)
# Store hashed in database: $argon2id$v=19$m=65536,t=3,p=4$...
# Verify password (login)
try:
ph.verify(hashed, "user_password_123") # ✅ Correct
print("Login successful!")
except VerifyMismatchError:
print("Invalid password!")
# Check if rehash needed (security updates)
if ph.check_needs_rehash(hashed):
hashed = ph.hash(password)
# Update database with new hash
Password Hashing Algorithms:
#
Common Encryption Mistakes
#
1. Using ECB Mode
# ❌ WRONG: ECB mode is insecure
cipher = AES.new(key, AES.MODE_ECB)
ciphertext = cipher.encrypt(plaintext)
# ✅ CORRECT: Use GCM or CBC
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
#
2. Not Using Authenticated Encryption
# ❌ WRONG: No integrity check
cipher = AES.new(key, AES.MODE_CBC)
ciphertext = cipher.encrypt(plaintext)
# Attacker can modify ciphertext!
# ✅ CORRECT: Use authenticated encryption
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
# Tag detects tampering
#
3. Reusing IV/Nonce
# ❌ WRONG: Reusing IV
iv = b"1234567890123456" # Fixed IV
cipher = AES.new(key, AES.MODE_CBC, iv)
# Same plaintext → same ciphertext!
# ✅ CORRECT: Generate random IV for each encryption
cipher = AES.new(key, AES.MODE_CBC) # Random IV generated
# Must store cipher.iv to decrypt later
#
4. Weak Keys
# ❌ WRONG: Weak key derivation
key = hashlib.md5(password.encode()).digest()
# ✅ CORRECT: Proper key derivation
from Crypto.Protocol.KDF import PBKDF2
salt = get_random_bytes(32)
key = PBKDF2(password, salt, dkLen=32, count=1000000)
#
5. Not Handling Padding Correctly
# ❌ WRONG: Manual padding
plaintext += b"\x00" * (16 - len(plaintext) % 16)
# ✅ CORRECT: Use proper padding
from Crypto.Util.Padding import pad, unpad
padded = pad(plaintext, AES.block_size)
#
6. Encrypting Without MAC (Message Authentication Code)
# ❌ WRONG: Encryption only
ciphertext = encrypt(plaintext)
send(ciphertext)
# Attacker can tamper with ciphertext
# ✅ CORRECT: Encrypt-then-MAC or use GCM
ciphertext = encrypt(plaintext)
mac = hmac(key_mac, ciphertext)
send(ciphertext + mac)
# Or use GCM which includes authentication
#
Encryption Best Practices
#
For Developers
DO
Use standard libraries (don't roll your own crypto) Use AES-256-GCM for symmetric encryption Use RSA-2048+ or ECC-256+ for asymmetric encryption Generate random IV/nonce for each encryption Use authenticated encryption (GCM, ChaCha20-Poly1305) Use key derivation functions (PBKDF2, Argon2) for passwords Rotate keys regularly Use HTTPS/TLS for all network communication Store keys securely (KMS, HSM, environment variables) Keep cryptographic libraries updated
DON'T
Never use ECB mode Never reuse IV/nonce Never hardcode keys in source code Never use MD5 or SHA1 for security Never implement your own encryption algorithm Never store passwords encrypted (hash them instead) Never use encryption without authentication Never use weak keys (<128 bits)
#
Recommended Libraries
# ✅ Use PyCryptodome or cryptography
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
# Or
from Crypto.Cipher import AES
// ✅ Use built-in crypto module
const crypto = require('crypto');
// Or for advanced features
const sodium = require('libsodium-wrappers');
// ✅ Use javax.crypto (built-in)
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
// Or Google Tink for modern API
import com.google.crypto.tink.Aead;
import com.google.crypto.tink.KeysetHandle;
// ✅ Use crypto package
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
)
#
FAQ
Use Symmetric (AES) when:
- Both parties can securely share a key
- Encrypting large amounts of data
- Performance is critical (files, databases)
Use Asymmetric (RSA/ECC) when:
- Sharing keys securely is difficult
- Digital signatures are needed
- Key exchange in untrusted networks
Use Hybrid (best) when:
- Encrypting data for transmission (HTTPS)
- Need both security and performance
- Building production systems
Recommended Key Sizes:
- AES: 256 bits (32 bytes) - excellent security
- RSA: 2048 bits minimum, 4096 bits for high security
- ECC: 256 bits (equivalent to RSA-3072)
Rule of thumb: Longer keys = more security, but slower performance
Yes and No:
Threatened:
- RSA: Quantum computers can break RSA
- ECC: Also vulnerable to quantum attacks
- Diffie-Hellman: Key exchange compromised
Safe:
- AES-256: Still secure against quantum computers
- SHA-256: Hash functions remain secure
Solution: Post-quantum cryptography (NIST standardizing new algorithms)
Always Encrypt:
- Passwords (hash them!)
- Credit card numbers
- Social Security Numbers
- Personal health information
- Authentication tokens
- API keys
- Private communications
Consider Encrypting:
- User personal data (names, emails)
- Business documents
- Database backups
- Log files with sensitive info
May Not Need Encryption:
- Public data
- Non-sensitive configuration
- Public website content
Rule: If data breach would cause harm, encrypt it!
#
Next Steps
#
Learning Resources
Books:
- "Serious Cryptography" by Jean-Philippe Aumasson
- "Cryptography Engineering" by Ferguson, Schneier, and Kohno
- "Applied Cryptography" by Bruce Schneier
Courses:
- Cryptography I (Coursera - Stanford)
- Applied Cryptography (Udacity)
- Practical Cryptography (Pluralsight)
Practice:
- CryptoHack - Learn cryptography through challenges
- Cryptopals - Hands-on cryptography exercises
#
Related Topics
Hashing - Understanding hash functions Digital Signatures - Authentication and non-repudiation TLS/SSL - Securing web communications Key Management - Best practices for key lifecycle
#
Protected by Layerd AI
Layerd AI Guardian Proxy helps enforce encryption best practices:
TLS/SSL Enforcement - Ensures all connections use strong encryption Weak Cipher Detection - Blocks outdated encryption algorithms Certificate Validation - Prevents man-in-the-middle attacks Key Exposure Prevention - Detects hardcoded keys in requests Encryption Compliance - Enforces regulatory requirements (PCI-DSS, HIPAA)
Learn more about Layerd AI Protection →
Last updated: November 2025