Skip to content
Breachfolio
Hero illustration for: Cryptography you can actually use.
LEVEL 2 CYBERSECURITY

Cryptography you can actually use.

Hashing vs encryption, symmetric vs asymmetric, and the three primitives you should reach for before rolling your own anything.

11 min read Daniel A. & Óscar S.

Most working engineers never need to implement a cipher. They need to know which one to use, what the inputs and outputs look like, and where the booby traps are. This article covers exactly that.

Hashing is not encryption

A hash is a one-way function: arbitrary input in, fixed-size pseudo-random output out. You cannot get the input back. Use cases: integrity checks, fingerprinting, password storage (with a slow hash).

$ echo -n "hello" | sha256sum
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824  -

Encryption is reversible: you have a key, you can decrypt. Use cases: transmitting and storing data confidentially.

Rule of thumb

If you can ever need the data back, you want encryption. If you only need to confirm two values are the same (passwords, file integrity), you want hashing.

The two flavours of encryption

Symmetric: one key, the same to encrypt and decrypt. Fast (gigabytes per second on modern CPUs). Hard to get the key to the other person securely.

Asymmetric: a key pair. Public to encrypt or verify, private to decrypt or sign. Slow (kilobytes per second). Easy to share the public half.

In practice almost everything uses both, like TLS does: asymmetric for a handshake that establishes a symmetric session key, then symmetric for the bulk traffic.

Worked example: how TLS actually uses both

The clearest place to see symmetric and asymmetric working together is the padlock in your browser's address bar. When you load a site over HTTPS, a lot happens in the time it takes to blink, and every step maps onto the two flavours above: the full mechanics, including how certificates get issued and audited, are covered in TLS certificates and Certificate Transparency; here we only care about where each type of cryptography does its job.

  1. The server proves who it is. Your browser receives the server's TLS certificate, issued by a certificate authority and containing an asymmetric public key. The server proves it holds the matching private key without ever transmitting that private key anywhere.
  2. Both sides agree on a shared secret, asymmetrically. Using a key-exchange algorithm – modern TLS uses ECDHE, elliptic-curve Diffie-Hellman – each side sends a public value and independently computes the same shared secret. An eavesdropper watching every byte of that exchange still cannot compute the secret; that is the hard problem asymmetric cryptography is built on.
  3. The shared secret becomes a symmetric session key. It gets fed through a key-derivation function to produce a session key for a fast symmetric cipher – in modern TLS, usually AES-256-GCM or ChaCha20-Poly1305.
  4. The rest of the connection is symmetric. Every request and response – HTML, images, API calls – is encrypted and authenticated with that one symmetric key, at line rate, for as long as the connection stays open.

The division of labour is deliberate. Asymmetric cryptography is slow but solves the hard problem of agreeing on a secret over a network between two parties who have never met; symmetric cryptography is fast but needs that secret to already exist. Neither one alone works for the web at scale – asymmetric-only would be too slow to stream video, symmetric-only would require every browser and server to somehow pre-share a key, which does not scale to billions of devices. For the layer underneath all of this – how those packets actually find the server in the first place – see how networks actually work.

The three primitives you should know by name

PrimitiveTypeUse it for
AES-256-GCMSymmetric AEADEncrypting files, sessions, anything bulk
X25519 + Ed25519Asymmetric (ECC)Key exchange and signatures
Argon2idPassword hashStoring user passwords

That is the floor. Reach for these by name in code review and you will be right 95% of the time. The remaining 5% – interoperability with old systems, regulated environments, hardware constraints – needs an expert anyway.

The algorithm cheat sheet: AES, RSA, ECC, SHA-256 and friends

Names like AES and RSA get thrown around as if they were interchangeable options on the same menu. They are not – they solve different problems, and knowing which category each one belongs to is most of the battle.

AlgorithmCategoryKey / output sizeTypical use case
AES (usually AES-256-GCM)Symmetric cipher128/192/256-bit keyEncrypting bulk data – files, database columns, TLS traffic
RSAAsymmetric (integer factorization)2048–4096-bit keyLegacy TLS key exchange, code signing, some JWT signing (RS256); being phased out in favour of ECC for new systems
ECC (X25519 / Ed25519 / P-256)Asymmetric (elliptic curve)~256-bit key, roughly equivalent strength to a 3072-bit RSA keyModern key exchange and signatures – smaller keys and faster operations than RSA at the same security level
SHA-256Cryptographic hash256-bit outputIntegrity checks, digital signatures, commit IDs – never for password storage
ChaCha20-Poly1305Symmetric AEAD256-bit keySame job as AES-GCM; preferred on hardware without AES acceleration, such as many mobile CPUs
Argon2idPassword hash (memory-hard)TunableStoring user passwords – deliberately slow, unlike SHA-256

Two things are worth pulling out of that table. First, RSA and ECC solve the same category of problem – asymmetric key exchange and signatures – but ECC does it with far smaller keys and less CPU, which is why nearly all new protocol design defaults to ECC and treats RSA as the thing you keep around for backward compatibility. Second, SHA-256 and Argon2id are both called "hashes" in casual conversation but are built for opposite goals: SHA-256 is optimized to be fast, which is good for verifying a large file's integrity, while Argon2id is deliberately slow and memory-hungry, which is good for password storage – where speed only ever helps an attacker guessing passwords.

AEAD – the one word that protects you from yourself

Older ciphers like AES-CBC encrypt your data but do not detect tampering. AEAD (Authenticated Encryption with Associated Data) does both at once. AES-GCM and ChaCha20-Poly1305 are AEADs. They produce a ciphertext and a tag; decryption fails loudly if the tag does not check out.

You should never see plain AES-CBC, AES-CTR, or 3DES in new code in 2026. If you do, replace it.

Password storage, the right way

Three rules:

  1. Hash, never encrypt.
  2. Use a slow hash designed for the job – Argon2id, scrypt, or bcrypt – not SHA-256.
  3. Tune the parameters so a single login takes 100–500 ms on your hardware.
# libargon2 reference settings, 2026
argon2id  memory=64MB  iterations=3  parallelism=4

Why memory? Because attackers crack passwords on GPUs, which have lots of cores but little memory per core. A memory-hard function levels the playing field.

Things that will trip you up

  • Reusing a nonce with AES-GCM destroys confidentiality. Generate a fresh 96-bit random nonce every encryption, or use a counter.
  • Comparing secrets with == leaks timing. Use a constant-time compare (hmac.compare_digest in Python, crypto.timingSafeEqual in Node).
  • Storing a key in the database next to the ciphertext defeats the entire exercise. Keys live in a KMS, an HSM, an env var injected at runtime – anywhere the data is not.
  • Inventing your own protocol by composing primitives. Use a high-level library – libsodium, age, Tink – that already composed them for you.

Those four mistakes account for the great majority of crypto-related breaches you will read about. Get them right and you are ahead of most of the industry.

Why you shouldn't roll your own crypto

"Just use a well-known library" sounds like generic risk-averse advice until you have seen what happens when someone doesn't. None of the following requires a novel attack – these are well-understood failure modes that show up whenever someone composes primitives by hand instead of reaching for a reviewed library.

Weak randomness

Every primitive above assumes its keys and nonces come from a cryptographically secure random number generator (CSPRNG) – one designed so its output is unpredictable even to someone who can observe part of it. General-purpose random functions, such as a language's default random() or anything seeded from the system clock, are built for statistical distribution, not unpredictability, and their internal state can often be reconstructed from a handful of outputs. A key generated this way isn't weakened a little: it can collapse the entire keyspace an attacker has to search, sometimes down to something small enough to brute-force in minutes.

ECB mode and pattern leakage

AES is a block cipher: it encrypts fixed-size chunks one at a time. In ECB (Electronic Codebook) mode, identical plaintext blocks always produce identical ciphertext blocks. That sounds like a minor detail until you encrypt something with repeating structure: a bitmap image is the classic demonstration: encrypt it in ECB mode and the outlines are still visible in the ciphertext, because identically coloured regions of pixels map to identical ciphertext blocks. The same leakage applies to any structured data with repeated patterns, not just images. This is exactly why AEAD modes like GCM exist: they chain each block against the previous state or a nonce, so identical plaintext blocks never produce identical ciphertext.

Timing side-channels

A naive comparison function that checks a MAC, a password hash, or an API token byte by byte and returns as soon as it finds a mismatch leaks information through how long it takes to say no. An attacker who can measure response time precisely enough can recover a secret one byte at a time: try every possible first byte, keep whichever one takes fractionally longer to reject (meaning it matched further before failing), and repeat down the string. It is slow over the open internet but entirely practical on a local network or against anything with stable latency. This is why every serious crypto library ships a constant-time comparison function, and why a plain equality check should never appear in code that handles keys, tokens, or password hashes.

None of these are exotic. They are the standard set of mistakes a competent, well-intentioned engineer makes the first time they build a cipher or a comparison routine from scratch instead of using a battle-tested library. The fix in all three cases is the one from the primitives table earlier: don't hand-roll a randomness source, a block cipher mode, or a comparison function – pull them from libsodium, age, Tink, or your language's own vetted crypto standard library, all of which already got this right.

Frequently asked questions

What is the difference between hashing and encryption?
A hash is a one-way function: input goes in, a fixed-size pseudo-random output comes out, and you cannot get the input back. It is used for integrity checks, fingerprinting, and password storage. Encryption is reversible – with the right key you can decrypt the data back to its original form – and is used for transmitting and storing data confidentially. If you will ever need the original data back, use encryption; if you only need to confirm two values match, use hashing.
What is the difference between symmetric and asymmetric encryption?
Symmetric encryption uses one key to both encrypt and decrypt, and is fast – gigabytes per second on modern CPUs – but that key must somehow reach the other party securely. Asymmetric encryption uses a key pair: a public key to encrypt or verify, and a private key to decrypt or sign. It is much slower but the public half can be shared freely. In practice, protocols like TLS use both: an asymmetric handshake to establish a symmetric session key, then symmetric encryption for the bulk traffic.
What is the right way to store passwords securely?
Always hash passwords, never encrypt them. Use a slow hash designed specifically for password storage – Argon2id, scrypt, or bcrypt – rather than a fast general-purpose hash like SHA-256. Tune the parameters (for example Argon2id with 64MB memory, 3 iterations, parallelism 4) so a single login takes roughly 100-500 ms on your hardware. The memory-hardness matters because it levels the playing field against attackers cracking passwords on GPUs, which have many cores but little memory per core.
What is AEAD encryption and why does it matter?
AEAD (Authenticated Encryption with Associated Data) both encrypts data and detects tampering in a single step, unlike older ciphers such as AES-CBC which encrypt but don't verify integrity. AES-GCM and ChaCha20-Poly1305 are AEADs: they produce a ciphertext and a tag, and decryption fails loudly if that tag doesn't check out. You should not see plain AES-CBC, AES-CTR, or 3DES in new code; AEAD is the modern default.
Who writes this

Daniel A. and Óscar S. run Breachfolio, a small independent site about security and AI. This article was drafted with AI assistance and reviewed by a person before it went live. We write from documentation, vendor sources and published research rather than from original lab benchmarks, and we link a source in the sentence that relies on it. How we work · About us