Documentation

# Encryption: Protecting Data Through Cryptography

FOUNDATIONAL SECURITY CONCEPT DATA PROTECTION CYBERSECURITY CORE

# What is Encryption?

DEFINITION

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.


# Why Encryption Matters

CRITICAL IMPORTANCE

# The Digital Security Foundation

Without encryption, these scenarios would happen constantly:

Passwords exposed - Anyone intercepting network traffic could see your credentials Credit cards stolen - Payment details visible during every online purchase Messages intercepted - Private communications readable by hackers or governments Business espionage - Corporate secrets exposed to competitors Identity theft - Personal data and files accessible to attackers Financial fraud - Banking information stolen in real-time Medical records leaked - Private health information exposed

# Real-World Impact: The Difference Encryption Makes


# How Encryption Works

FUNDAMENTAL PROCESS

# The Basic Process: 4 Critical Steps

Start with readable data that needs protection.

plaintext
Original Message: "Hello, this is a secret message"

Examples of Plaintext:

  • Chat messages
  • Credit card numbers
  • Document contents
  • Passwords (should be hashed, not encrypted!)
  • Database records

Use a mathematical algorithm with a secret key to transform the data.

Algorithm: AES-256-GCM
Key: "my-secret-encryption-key-12345-very-long-and-random"
Key Length: 256 bits (32 bytes)
Mode: GCM (Galois/Counter Mode)

Popular Algorithms:

  • AES-256 - Industry standard, very secure
  • ChaCha20 - Modern, fast on mobile devices
  • RSA-2048 - For public key encryption
  • DES - OBSOLETE, never use!

The output is scrambled, unreadable data that looks like random bytes.

hex
Encrypted Output: "8f3a9c7e2d1b5a4f9e3c7d2a1b8f4e6c2d9f1a3b5c7e9f0a2b4c6d8e0f2a4b6c..."

Ciphertext Properties:

  • Appears completely random
  • Same size or slightly larger than original
  • Cannot be reversed without the key
  • Secure even if attacker intercepts it

Only someone with the correct key can reverse the process and recover the original data.

plaintext
Decrypted Output: "Hello, this is a secret message"

Decryption Process:

  1. Use the same key (symmetric) or private key (asymmetric)
  2. Apply the decryption algorithm
  3. Recover original plaintext
  4. Verify integrity (if using authenticated encryption)

# Types of Encryption

3 MAIN TYPES


# 1. Symmetric Encryption SINGLE KEY FAST AES-256

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 - the same key works both ways.

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 COMPARISON

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

# 2. Asymmetric Encryption (Public Key Cryptography) KEY PAIR SECURE DISTRIBUTION RSA/ECC

How It Works: Uses two different keys:

  • Public Key - Can be shared with anyone, used for encryption
  • Private Key - Must be kept secret, used for decryption

Real-World Analogy: Like a mailbox system:

  • Anyone can drop mail into the mailbox (encrypt with public key)
  • Only the owner with the key can open the mailbox (decrypt with private key)
  • The mailbox location is public, but contents are secure

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

PUBLIC KEY ALGORITHMS

Algorithm Key Size Status Use Case Security Level
RSA 2048-4096 bits RECOMMENDED PROVEN Digital signatures, key exchange, TLS/SSL Very High
ECC (Elliptic Curve) 256-521 bits RECOMMENDED MODERN Mobile, IoT (smaller keys, same security) Very High
Ed25519 256 bits RECOMMENDED FAST SSH keys, digital signatures, cryptocurrencies Very High
DSA 1024-3072 bits LEGACY OUTDATED Old systems (use RSA/ECC instead) Medium

# 3. Hybrid Encryption (Best of Both Worlds) INDUSTRY STANDARD HTTPS/TLS BEST PRACTICE

How It Works: Combines symmetric and asymmetric encryption to get the benefits of both:

  • Speed of symmetric encryption for bulk data
  • Security of asymmetric encryption for key exchange
  • No key distribution problem
  • Best of both worlds

The Process - Step by Step:

STEP 1 Generate a random symmetric key (e.g., AES-256 key)

  • This is a temporary "session key" used just for this message

STEP 2 Encrypt the actual data with the symmetric key (fast)

  • Symmetric encryption is 100-1000x faster for large data

STEP 3 Encrypt the symmetric key with the recipient's public key (secure)

  • Public key can be shared openly, no security risk

STEP 4 Send both the encrypted data and encrypted key

  • Recipient can decrypt the session key with their private key, then decrypt the data
┌─────────────┐
│   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:

EVERYWHERE

  • HTTPS/TLS: Every secure website you visit right now
  • Email Encryption: PGP/GPP, S/MIME
  • Messaging Apps: Signal, WhatsApp, Telegram
  • Cloud Storage: Encrypted file storage (Dropbox, Google Drive)
  • VPN: Secure tunnel establishment (OpenVPN, WireGuard)
  • Payment Processing: Credit card transactions
  • File Sharing: Encrypted file transfers
  • Password Managers: 1Password, LastPass, Bitwarden

# Encryption Modes of Operation

CRITICAL CHOICE

What is a Mode of Operation?

Block ciphers like AES encrypt data in fixed-size blocks (16 bytes for AES). But what happens when you need to encrypt:

  • Files larger than 16 bytes? (almost everything)
  • Multiple blocks of data?
  • Data that needs integrity verification?

Modes of operation define how to apply the block cipher to data larger than one block.

# 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

How It Works: Each block is encrypted independently with the same key.

Critical Problems:

  • Same plaintext block → same ciphertext block (pattern leakage)
  • Patterns in plaintext visible in ciphertext
  • No integrity protection
  • Attackers can rearrange encrypted blocks
  • Reveals information about data structure

The Famous ECB Penguin Example:

VISUAL PROOF ECB IS BROKEN

Original Image → ECB Encryption → Pattern Still Visible! ❌
(Penguin bitmap)    (ECB mode)      (Penguin outline visible in "encrypted" image!)

When you encrypt an image with ECB, you can still see the outline of the image in the encrypted version! This demonstrates that ECB preserves patterns, which is catastrophic for security.

ABSOLUTELY NEVER USE ECB MODE IN ANY APPLICATION!

USE GCM INSTEAD

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

CHOOSING THE RIGHT MODE

Mode Security Speed Parallel Authenticated Padding Recommended
GCM High Very Fast Yes Yes No USE THIS
CBC Good Medium No No Yes OK
CTR Good Very Fast Yes No No OK
ECB BROKEN Fast Yes No Yes NEVER

# Key Management

CRITICAL MOST COMMON FAILURE POINT

# Key Generation

USE SECURE RANDOMNESS

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 COMMON MISTAKE

Bad Practices - NEVER DO THIS:

# ❌ CATASTROPHICALLY BAD: Hardcoded in source
SECRET_KEY = "my-secret-key-12345"  # Hardcoded in source
API_KEY = "sk_live_abc123xyz"       # Committed to Git
DATABASE_PASSWORD = "admin123"       # Everyone can see this!

# ❌ CATASTROPHICALLY BAD: Hardcoded in function
def encrypt_data(data):
    key = b"0123456789abcdef"  # Hardcoded key
    cipher = AES.new(key, AES.MODE_GCM)
    return cipher.encrypt(data)

# ❌ CATASTROPHICALLY BAD: Keys in config files in repo
# config.json (committed to Git)
{
  "encryption_key": "supersecretkey12345",
  "api_secret": "sk_live_xxxxx"
}

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

AVOID THESE

# 1. Using ECB Mode CRITICAL ERROR

# ❌ 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 TAMPERING RISK

# ❌ WRONG: No integrity check
cipher = AES.new(key, AES.MODE_CBC)
ciphertext = cipher.encrypt(plaintext)
# Attacker can modify ciphertext without detection!

# ✅ CORRECT: Use authenticated encryption
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
# Tag detects tampering - decryption fails if data modified

# 3. Reusing IV/Nonce CATASTROPHIC

# ❌ CATASTROPHICALLY WRONG: Reusing IV
iv = b"1234567890123456"  # Fixed IV - DISASTER!
cipher = AES.new(key, AES.MODE_CBC, iv)
# Same plaintext → same ciphertext!
# Attacker can XOR ciphertexts to get XOR of plaintexts

# ✅ CORRECT: Generate random IV for each encryption
cipher = AES.new(key, AES.MODE_CBC)  # Random IV generated automatically
# Must store cipher.iv with the ciphertext to decrypt later!

# 4. Weak Keys PREDICTABLE

# ❌ CATASTROPHICALLY WRONG: Weak key derivation
key = hashlib.md5(password.encode()).digest()  # BROKEN!
# MD5 is fast = attacker can try millions of passwords per second

# ✅ CORRECT: Proper key derivation with PBKDF2
from Crypto.Protocol.KDF import PBKDF2
salt = get_random_bytes(32)  # Random salt
key = PBKDF2(password, salt, dkLen=32, count=1000000)  # 1M iterations
# Slow derivation = brute force takes years instead of hours

# 5. Not Handling Padding Correctly PADDING ORACLE

# ❌ WRONG: Manual padding (insecure)
plaintext += b"\x00" * (16 - len(plaintext) % 16)  # Wrong!

# ✅ CORRECT: Use proper PKCS7 padding
from Crypto.Util.Padding import pad, unpad
padded = pad(plaintext, AES.block_size)  # PKCS7 padding
# When decrypting:
plaintext = unpad(decrypted, AES.block_size)

# 6. Encrypting Without MAC NO INTEGRITY

# ❌ WRONG: Encryption only (no integrity protection)
ciphertext = encrypt(plaintext)
send(ciphertext)
# Attacker can:
# - Modify ciphertext
# - Inject malicious data
# - Trigger padding oracle attacks

# ✅ CORRECT Option 1: Encrypt-then-MAC
ciphertext = encrypt(plaintext)
mac = hmac(key_mac, ciphertext)  # HMAC over ciphertext
send(ciphertext + mac)

# ✅ CORRECT Option 2: Use GCM (includes authentication)
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
# Tag provides authentication automatically

# Encryption Best Practices

FOLLOW THESE RULES

# For Developers

Use standard libraries (don't roll your own crypto - it WILL have vulnerabilities) Use AES-256-GCM for symmetric encryption (industry standard, hardware accelerated) Use RSA-2048+ or ECC-256+ for asymmetric encryption (RSA-4096 or ECC-384 for high security) Generate random IV/nonce for each encryption (NEVER reuse!) Use authenticated encryption (GCM, ChaCha20-Poly1305) to detect tampering Use key derivation functions (PBKDF2, Argon2, scrypt) for passwords, not plain hashing Rotate keys regularly (every 90 days for high-security applications) Use HTTPS/TLS for ALL network communication (no exceptions!) Store keys securely (KMS, HSM, encrypted vault - never in code or config) Keep cryptographic libraries updated (new vulnerabilities discovered regularly) Use constant-time comparisons for MACs and hashes (prevent timing attacks) Test encryption implementation (encrypt/decrypt tests, tamper tests, fuzzing)

Never use ECB mode (broken, leaks patterns - use GCM instead) Never reuse IV/nonce (catastrophic security failure - can break encryption) Never hardcode keys in source code (WILL be leaked on GitHub) Never use MD5 or SHA1 for security (completely broken, use SHA-256+) Never implement your own encryption algorithm (professionals take decades to design secure crypto) Never store passwords encrypted (hash them with Argon2/bcrypt instead) Never use encryption without authentication (allows tampering - use GCM or add HMAC) Never use weak keys (<128 bits - use 256 bits for symmetric, 2048+ for RSA) Never trust user input for cryptographic operations (validate and sanitize everything) Never use the same key for multiple purposes (encryption key ≠ MAC key) Never ignore library errors (failed decryption = tampered data, handle securely) Never use predictable random numbers (random() is NOT crypto-secure!)

# 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"
)

# Frequently Asked Questions (FAQ)

COMMON QUESTIONS

Use Symmetric (AES) when:

  • Both parties can securely share a key
  • Encrypting large amounts of data (files, databases, disk encryption)
  • Performance is critical - need fast encryption/decryption
  • Encrypting data at rest (backups, archives)

Use Asymmetric (RSA/ECC) when:

  • Sharing keys securely is difficult or impossible
  • Digital signatures are needed (authentication, non-repudiation)
  • Key exchange in untrusted networks (initial connection)
  • One-way communication (encrypt for recipient who isn't online)

Use Hybrid (BEST CHOICE) when:

  • Encrypting data for transmission (HTTPS, VPN, messaging)
  • Need both security and performance
  • Building any production system
  • This is what the real world uses!

Recommended Key Sizes (2025):

SYMMETRIC ENCRYPTION

  • AES-128: 128 bits - Secure for most uses
  • AES-192: 192 bits - Extra security margin
  • AES-256: 256 bits - Maximum security, government use RECOMMENDED

ASYMMETRIC ENCRYPTION (RSA)

  • RSA-1024: Broken - can be cracked
  • RSA-2048: Minimum for 2025 MINIMUM
  • RSA-3072: Good security margin
  • RSA-4096: High security, slower HIGH SECURITY

ASYMMETRIC ENCRYPTION (ECC)

  • ECC-256: Equivalent to RSA-3072 RECOMMENDED
  • ECC-384: Equivalent to RSA-7680
  • ECC-521: Maximum security

Rule of thumb: Longer keys = more security, but slower performance. For symmetric encryption, 256 bits is the sweet spot. For asymmetric, RSA-2048 or ECC-256 is the minimum.

Aspect Encryption Hashing
Reversible Yes (with key) No (one-way function)
Purpose Confidentiality (hide data) Integrity & Authentication (verify data)
Output Size Variable (≈ same as input) Fixed size (e.g., 256 bits)
Use Case Protecting data you need to read later Passwords, checksums, digital signatures
Example AES, RSA, ChaCha20 SHA-256, Argon2, bcrypt
Key Required Yes - needed to decrypt No - one-way transformation
Speed Moderate Very fast (SHA-256) or intentionally slow (Argon2)

When to use each:

  • Encryption: Credit cards, files, messages, data you need to decrypt later
  • Hashing: Passwords, file integrity, blockchain, digital signatures

Yes and No:

THREATENED BY QUANTUM

  • RSA - Shor's algorithm can break RSA in polynomial time
  • ECC - Also vulnerable to Shor's algorithm
  • Diffie-Hellman - Key exchange compromised
  • DSA/ECDSA - Digital signatures broken

SAFE FROM QUANTUM

  • AES-256 - Quantum reduces to ~128-bit security (still secure)
  • SHA-256 - Hash functions remain secure (Grover's algorithm only gives quadratic speedup)
  • ChaCha20 - Symmetric ciphers remain strong

THE SOLUTION

Post-Quantum Cryptography (PQC):

  • NIST standardizing new quantum-resistant algorithms
  • Kyber - Quantum-safe key exchange (standardized 2024)
  • Dilithium - Quantum-safe digital signatures
  • SPHINCS+ - Stateless hash-based signatures

MUST ENCRYPT (ALWAYS)

Critical Data - NEVER store unencrypted:

  • Passwords - Hash with Argon2/bcrypt, NEVER encrypt!
  • Credit card numbers - PCI-DSS requires encryption
  • Social Security Numbers - Required by law (GDPR, HIPAA)
  • Personal health information - HIPAA compliance
  • Authentication tokens - Session tokens, JWTs with sensitive data
  • API keys & secrets - Exposed keys = system compromise
  • Private communications - Messages, emails, chat logs
  • Private encryption keys - Encrypt with master key or KMS
  • Database credentials - Use secret managers
  • Biometric data - Fingerprints, facial recognition data

SHOULD ENCRYPT (RECOMMENDED)

Sensitive Data - Strong recommendation:

  • User personal data - Names, addresses, phone numbers, emails
  • Business documents - Contracts, financial records, strategic plans
  • Database backups - Contains all sensitive data
  • Log files with sensitive info - May contain PII, tokens, errors
  • User activity data - Browsing history, usage patterns
  • Email archives - May contain sensitive conversations
  • Cloud storage - Files uploaded by users

MAY NOT NEED ENCRYPTION

Public/Non-Sensitive Data:

  • Public website content - Already publicly accessible
  • Non-sensitive configuration - Public settings, feature flags
  • Public documentation - User guides, FAQs
  • :icon-chart: Anonymized analytics - No PII, aggregated only
  • Cached public data - Already public information

Decision Framework:

Ask yourself:

  1. Would a breach harm users? → Encrypt
  2. Is it regulated data (PII, PHI, PCI)? → Encrypt
  3. Would competitors benefit from seeing it? → Encrypt
  4. Would you be embarrassed if it leaked? → Encrypt
  5. Is it publicly available already? → Maybe don't encrypt

# 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