Assume, for a moment, that an attacker gets in. The question that decides how bad it is: what's the data worth to them once they have it? Encryption is how you make the answer "not much." Done right, a database dump is a pile of ciphertext and a stolen connection is unreadable. This lesson is about the three places that matters: in transit, at rest, and the special case of passwords.
In transit
TLS everywhere. Browser to server, server to database, service to service, and yes, across the internal network too, because "internal" stopped meaning "trusted" a long time ago.
- TLS 1.2 or 1.3 only. Disable 1.0 and 1.1.
- Strong ciphers: AES-GCM, ChaCha20-Poly1305.
- HSTS, so the connection can't be silently downgraded.
- Automate certificates with Let's Encrypt so they don't lapse.
At rest
There are two layers here, and they protect against different attackers.
Database-level encryption is what cloud databases give you transparently. It protects against someone walking off with the physical disk. It does nothing against an attacker who reaches your data through the application, because to the app the data is already decrypted.
Application-level encryption is what protects the genuinely sensitive fields, the SSNs, card numbers, and health data. You encrypt before storing, so a rogue DBA or a successful SQL injection sees only ciphertext.
from cryptography.fernet import Fernet
cipher = Fernet(key) # Key from a secrets manager, never hardcoded
encrypted = cipher.encrypt(data.encode())
Pick the algorithm to match the job:
| Use case | Algorithm |
|---|---|
| Symmetric | AES-256-GCM |
| Asymmetric | RSA-OAEP 2048+ |
| Passwords | Argon2id, bcrypt |
And steer well clear of DES, RC4, MD5, and SHA-1.
The keys are the whole game. Never hardcode them, manage them through a KMS, rotate them periodically, and use separate keys for separate purposes so one leak doesn't unlock everything.
Passwords are a special case
Hash passwords, don't encrypt them. Encryption is reversible, and you should never have a reason to reverse a password. A one-way hash means even a full breach doesn't hand over the plaintext.
from argon2 import PasswordHasher
ph = PasswordHasher()
hash = ph.hash(password)
ph.verify(hash, password)
Argon2id is the current recommendation; bcrypt and scrypt are solid choices too. What you must never do is hash passwords with SHA-256 or MD5, which are built for speed and therefore trivial to brute-force.
Collect less in the first place
The data you never stored is the data that can't leak, so data minimisation is a security control in its own right. Don't collect what you don't need. Define a retention period and automate deletion. Use anonymised data for analytics. And keep passwords and tokens out of your logs entirely.
