JustAppSec

Data Protection and Encryption

Encryption at rest, in transit, and the practical choices developers actually make.

0:00

Data is the ultimate target. Attackers compromise systems to reach data — credentials, PII, financial records, health information. This lesson covers the practical choices developers make when protecting data at rest and in transit.

Encryption in transit

TLS everywhere

All data in transit must be encrypted with TLS. No exceptions, including:

  • Browser to server
  • Server to database
  • Service to service (even within a private network)
  • Server to third-party APIs

"It's internal" is not a valid reason to skip TLS. Internal networks get compromised. Service meshes, VPNs, and private subnets reduce risk but do not eliminate it.

TLS configuration

  • Use TLS 1.2 or 1.3. Disable TLS 1.0 and 1.1.
  • Use strong cipher suites. Prefer AEAD ciphers (AES-GCM, ChaCha20-Poly1305).
  • Enable HSTS (HTTP Strict Transport Security) to prevent protocol downgrade attacks.
  • Use valid certificates from a trusted CA. Automate renewal with Let's Encrypt or your cloud provider's certificate manager.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

Certificate pinning

Certificate pinning binds your application to a specific certificate or public key, preventing man-in-the-middle attacks even if a CA is compromised. However, pinning is operationally dangerous — if you rotate certificates without updating the pins, your application breaks.

For most applications, standard TLS with a trusted CA is sufficient. Reserve pinning for high-security contexts (banking, healthcare) and ensure you have a pin rotation plan.

Encryption at rest

Database encryption

Most cloud databases support transparent encryption at rest:

  • AWS RDS — AES-256 encryption enabled at instance creation
  • GCP Cloud SQL — encrypted by default
  • Azure SQL — Transparent Data Encryption (TDE) enabled by default

This protects against physical media theft and some storage-level attacks, but it does not protect against application-level data access. If an attacker compromises your application or database credentials, they can read decrypted data through the application layer.

Application-level encryption

For sensitive fields (SSNs, credit card numbers, health data), encrypt at the application level before storing:

from cryptography.fernet import Fernet

key = Fernet.generate_key()  # Store securely — not in code
cipher = Fernet(key)

# Encrypt before storing
encrypted_ssn = cipher.encrypt(ssn.encode())

# Decrypt when needed
ssn = cipher.decrypt(encrypted_ssn).decode()

Application-level encryption means even a database administrator or a SQL injection attacker sees only ciphertext.

Choosing encryption algorithms

Use caseAlgorithmNotes
Symmetric encryption (general)AES-256-GCMAuthenticated encryption — provides confidentiality and integrity
Symmetric encryption (alternative)ChaCha20-Poly1305Good performance on devices without AES hardware acceleration
Asymmetric encryptionRSA-OAEP (2048+ bit) or ECIESFor key exchange, digital signatures
Hashing (non-reversible)SHA-256, SHA-3For checksums, integrity verification
Password hashingArgon2id, bcrypt, scryptDeliberately slow; resistant to brute-force

Do not use: DES, 3DES, RC4, MD5 (for security), SHA-1 (for security). These are broken or deprecated.

Key management

The encryption is only as strong as the key management:

  • Never hardcode keys in source code. Use environment variables, secrets managers, or KMS.
  • Rotate keys periodically. Design your encryption layer to support key versioning.
  • Use a KMS (Key Management Service) when possible. AWS KMS, GCP Cloud KMS, Azure Key Vault, and HashiCorp Vault handle key storage, rotation, and access control.
  • Separate encryption keys per purpose. The key that encrypts user data should not be the same key that signs JWTs.

Password storage

Passwords are a special case. They must be hashed, not encrypted, because you should never need to reverse them.

Use the right algorithm

# Argon2id — recommended
from argon2 import PasswordHasher
ph = PasswordHasher()
hash = ph.hash(password)
ph.verify(hash, password)  # Raises exception on mismatch
AlgorithmStatus
Argon2idRecommended — winner of the Password Hashing Competition; resistant to GPU and side-channel attacks
bcryptGood — widely supported, well-tested, max 72-byte input
scryptGood — memory-hard, less widely used than bcrypt
PBKDF2Acceptable — Django's default, but less resistant to GPU attacks than Argon2 or bcrypt
SHA-256 / MD5Never — fast hashes are trivially brute-forced

Salting

All modern password hashing algorithms include a unique salt per password automatically. If you are using argon2, bcrypt, or scrypt through a standard library, salts are handled for you. Do not implement salting manually unless you are building a password hashing library.

Data minimisation

The best protection for data is not collecting it in the first place.

  • Do you actually need this data? If a feature works without a phone number, do not collect one.
  • Retention limits. Define how long you keep data and automate deletion. Old data you do not need is liability, not an asset.
  • Anonymisation and pseudonymisation. For analytics, use anonymised or aggregated data instead of raw PII.
  • Log scrubbing. Do not log sensitive data. Mask or redact fields like passwords, tokens, and credit card numbers before they reach your logging pipeline.

Tokenisation

For data like credit card numbers, replace the sensitive value with a non-reversible token:

Actual: 4111-1111-1111-1111
Token:  tok_abc123def456

The mapping from token to actual value is stored in a secure vault (or by a payment processor like Stripe). Your application never sees or stores the real card number, reducing PCI DSS scope dramatically.

Summary

Encrypt all data in transit with TLS 1.2+. Encrypt sensitive data at rest at the application level for fields that need it, and use transparent encryption for baseline storage protection. Hash passwords with Argon2id (or bcrypt). Manage keys with a KMS — never hardcode them. Minimise the data you collect and define retention limits. Tokenise highly sensitive data when possible. Every layer of encryption reduces the impact of the next breach.


This training content is AI-assisted and reviewed by our team, but issues may be missed and best practices evolve rapidly. Send corrections to [email protected].