5 March 2025 · 5 min read
Hashing, encryption and encoding are three different things
They get used interchangeably in conversation and never in code. Here is the distinction that matters.
Three operations turn data into something that looks different. Only one of them is reversible with a key, only one is reversible by anyone, and only one is not reversible at all. Confusing them produces some of the most common security failures in web software.
Encoding: reversible by anyone
Base64, URL encoding, hex. There is no key. The transformation exists to make bytes survive a channel that expects text. Anyone can undo it, instantly, and it provides no confidentiality whatsoever.
Encryption: reversible with a key
AES, ChaCha20, RSA. Ciphertext can be turned back into plaintext by whoever holds the key, and by nobody else. This is what you want when the data needs to be read again later — a stored access token, a message in transit, a database column of card numbers.
Hashing: not reversible
SHA-256, SHA-512. A fixed-size digest of the input, with no key and no inverse. You cannot recover the input from the digest — but you can test a candidate by hashing it and comparing, which is exactly how password verification works and exactly how password cracking works.
If your requirement is that the data can be read again, you need encryption. If your requirement is that it never needs to be read, only checked, you need hashing.
The password mistake
Storing passwords as SHA-256 digests feels like hashing done correctly. It is not, because SHA-256 is designed to be fast, and fast is the wrong property here. A commodity GPU computes billions of SHA-256 digests per second, so an attacker with your database tries every common password against every row in minutes.
Password hashing needs a function that is deliberately slow and memory-hard: Argon2id, scrypt, or bcrypt. Each also incorporates a per-user salt, so identical passwords produce different digests and one precomputed table cannot attack every row at once.
SHA-256(password) ← wrong: fast, unsalted SHA-256(salt + password) ← better, still far too fast Argon2id(password, salt, …) ← right
Where each belongs
- Checking a downloaded file is intact — a fast hash, SHA-256.
- Storing a password — a slow password hash, Argon2id.
- Proving a message came from someone holding a shared secret — an HMAC.
- Storing an API token you must present again later — encryption, not hashing.
- Putting binary data in a JSON field — encoding, and nothing more is implied.
MD5 and SHA-1
Both are broken for collision resistance: it is practical to construct two different inputs with the same digest. That kills them for signatures and certificates. It does not make MD5 useless as a checksum against accidental corruption, which is why tools still offer it — but anywhere an adversary might choose the input, they are unsafe.
Tools mentioned