Documentation

# 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
[Python - AES]
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}")
[Node.js - AES]
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);
[Java - AES]
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

Algorithm Key Size Status Use Case
AES (Advanced Encryption Standard) 128, 192, 256 bits RECOMMENDED General purpose, industry standard
ChaCha20 256 bits RECOMMENDED Mobile devices, high performance
DES (Data Encryption Standard) 56 bits DEPRECATED Legacy systems only (INSECURE)
3DES (Triple DES) 168 bits RETIRING Legacy banking systems (phase out)
Blowfish 32-448 bits LEGACY Old systems (use AES instead)

# 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
[Python - RSA]
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}")
[Node.js - RSA]
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());
[Java - RSA]
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

Algorithm Key Size Status Use Case
RSA 2048-4096 bits RECOMMENDED Digital signatures, key exchange
ECC (Elliptic Curve) 256-521 bits RECOMMENDED Mobile, IoT (smaller keys, same security)
Ed25519 256 bits RECOMMENDED SSH keys, digital signatures
DSA 1024-3072 bits LEGACY Old systems (use RSA/ECC instead)

# 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:

  1. Generate a random symmetric key (e.g., AES key)
  2. Encrypt the actual data with the symmetric key (fast)
  3. Encrypt the symmetric key with the recipient's public key (secure distribution)
  4. 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..."
[Python - Hybrid]
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()}")
[Node.js - Hybrid]
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

Mode Security Speed Parallel Authenticated Recommended
GCM High Fast Yes Yes YES
CBC Good Medium No No OK
CTR Good Fast Yes No OK
ECB INSECURE Fast Yes No NEVER

# Key Management

CRITICAL The security of encryption depends entirely on keeping keys secret!

# Key Generation

ALWAYS use cryptographically secure random number generators:

[Python - Secure Keys]
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!
[Node.js - Secure Keys]
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!
[Java - Secure Keys]
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:

Algorithm Status Notes
Argon2 RECOMMENDED Winner of Password Hashing Competition
bcrypt GOOD Widely used, battle-tested
scrypt GOOD Memory-hard, resistant to hardware attacks
PBKDF2 ACCEPTABLE Slower alternatives preferred
SHA-256 NEVER USE Too fast, no salt, vulnerable
MD5 NEVER USE Completely broken

# 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

[Python]
# ✅ 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
[Node.js]
// ✅ Use built-in crypto module
const crypto = require('crypto');

// Or for advanced features
const sodium = require('libsodium-wrappers');
[Java]
// ✅ 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;
[Go]
// ✅ 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

Aspect Encryption Hashing
Reversible Yes (with key) No (one-way)
Purpose Confidentiality Integrity, authentication
Output Variable (same size as input) Fixed size
Use Case Protecting data Passwords, checksums
Example AES, RSA SHA-256, Argon2

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:

# 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