Security
Cryptography basics every backend developer should actually know
You don't need to implement cryptography to write secure code. You do need to recognize the handful of mistakes that show up in almost every codebase.
Last updated September 17, 2026
Most backend developers never implement a cryptographic primitive, and shouldn't — the field is full of subtle mistakes that don't show up until an attacker looks for them. But using cryptography correctly, as a consumer of well-reviewed libraries, still requires knowing which tool solves which problem, and that part is worth understanding regardless of whether you ever write a cipher yourself.
Hashing and encryption solve different problems
Hashing is one-way: it turns input into a fixed-size output with no way back to the original. Encryption is two-way: it turns input into ciphertext that a key can turn back into the original. These get confused because both produce output that looks like noise, but they answer different questions. "Does this password match what I stored" is a hashing question — you never need the original password back, only a way to verify a new input matches. "Store this credit card number so I can retrieve it later for the payment processor" is an encryption question — you genuinely need the original value back at some point, which hashing can never give you.
Using a hash where you need encryption is a design error you'll notice quickly, because you can't get the data back. Using encryption where you needed a hash is a subtler mistake — it works, but it means the original value exists somewhere retrievable, which is a larger attack surface than it needed to be for something like a password that only ever needs to be verified, never retrieved.
Why password hashing needs a slow hash
General-purpose hash functions like SHA-256 are fast by design — that's exactly the wrong property for password storage, because fast means an attacker with a stolen hash database can try billions of guesses per second. Password hashing needs a function that's deliberately slow and, ideally, memory-hard: bcrypt, scrypt, or argon2, which are designed specifically to make brute-forcing expensive even with GPU or ASIC hardware.
# Wrong: SHA-256 is fast, which is exactly the problem
hashlib.sha256(password.encode()).hexdigest()
# Right: bcrypt is deliberately slow and includes salting automatically
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
The salt matters too — without one, two users with the same password produce the same hash, which lets an attacker precompute a lookup table once and use it against every user in the database. bcrypt and its peers handle salting automatically, which removes an entire category of mistake that used to be manual and easy to get wrong.
Symmetric vs asymmetric: same key vs key pairs
Symmetric encryption uses one key for both encrypting and decrypting — fast, and the right choice when both sides already share a secret (encrypting data at rest in your own database, for instance). Asymmetric encryption uses a key pair: a public key that encrypts, a private key that decrypts, useful specifically when the encrypting party and the decrypting party can't safely share a secret in advance — which is the entire basis of TLS, where a server's public key is, by design, public.
A common confusion: reaching for asymmetric encryption for a same-system, both-sides-trusted scenario where a shared symmetric key would be simpler and faster. Asymmetric encryption is solving a specific problem (key distribution to an untrusted or previously-unknown party), and if that's not your actual problem, symmetric encryption is usually the better fit.
The mistake that undoes everything else
The single biggest risk isn't picking the wrong algorithm — it's implementing one from scratch instead of using an established, audited library. Cryptographic primitives fail in ways that are invisible in normal testing and only show up under adversarial analysis: a subtly non-constant-time comparison that leaks timing information, an initialization vector reused where it shouldn't be, a random number generator that isn't actually cryptographically secure. These are the kinds of bugs that took the field years to find in libraries written by cryptography specialists; a homegrown implementation almost certainly has them too, just undiscovered.
The practical takeaway isn't "learn to implement AES." It's: know which primitive solves which problem, use a maintained library for the actual implementation, and treat "we wrote our own encryption" as close to always the wrong answer, even when the person writing it is confident.
Tags
Related posts