What MD5 actually does (and why it isn't encryption)
This snippet shows up in code review all the time, and on its face it looks entirely reasonable: public User register(String email, String rawPassword) { User user = new User(); user.setEmail(email); user.
This snippet shows up in code review all the time, and on its face it looks entirely reasonable:
public User register(String email, String rawPassword) {
User user = new User();
user.setEmail(email);
user.setPassword(DigestUtils.md5Hex(rawPassword));
return userRepository.save(user);
}
The comment above it said the password was "encrypted before storing". Three things in that snippet are wrong, and pulling them apart is a good way to nail down a distinction that still trips up working developers: encoding, hashing and encryption are three different operations, and only two of them are reversible.
Three operations, three purposes
Encoding changes the representation of data so it can survive transport. It is reversible by design and involves no secret. Base64 is the standard example: it turns arbitrary bytes into a safe subset of ASCII so they can travel through systems that expect text. Anyone can decode it.
Hashing reduces input of any length to a fixed-size digest. It is one-way by design and involves no key. There is nothing to reverse, because the digest is not a compressed copy of the input. It is a summary that many different inputs could have produced.
Encryption transforms data so that only a holder of the key can read it. It is reversible, and the key is the entire point.
The practical rule that follows: if you need the data back, you want encryption. If you only need to check that data has not changed, you want a hash. If you need to move data through a text-only channel, you want encoding.
What MD5 is, concretely
MD5 takes any input and returns 128 bits, conventionally written as 32 lowercase hex characters.
// Node.js
const crypto = require('crypto');
crypto.createHash('md5').update('hello').digest('hex');
// '5d41402abc4b2a76b9719d911017c592'
# Python
import hashlib
hashlib.md5(b'hello').hexdigest()
# '5d41402abc4b2a76b9719d911017c592'
printf 'hello' | md5sum
# 5d41402abc4b2a76b9719d911017c592 -
Three properties matter here. It is deterministic: the same input always produces the same digest. It is fixed-length: one character and a 10 GB disk image both produce 32 hex characters. And it has a strong avalanche effect: changing one bit of input changes about half the output bits.
crypto.createHash('md5').update('hello').digest('hex');
// '5d41402abc4b2a76b9719d911017c592'
crypto.createHash('md5').update('Hello').digest('hex');
// '8b1a9953c4611296a827abf8c47804d7'
One capital letter, and the two digests share nothing. That is exactly what you want when checking whether a file changed, and exactly why you cannot use a hash as storage for something you need to read back.
Why "MD5 decrypt" is a contradiction
Search for "MD5 decrypt" and you will find dozens of sites that will happily decrypt a hash for you. None of them are reversing the function.
What they are actually doing is a lookup. 5f4dcc3b5aa765d61d8327deb882cf99 is the MD5 of password, and that fact has been sitting in public tables for twenty years. These services hash a huge dictionary of candidate inputs, store the results, and hand you back whatever matched.
The distinction matters because the two failure modes are different. You cannot reverse MD5. You can guess the input, and when the input is a human-chosen password, guessing is cheap. MD5 was designed to be fast, and in this context fast is a liability.
MD5 has been broken since 2004
A collision is a pair of different inputs that produce the same hash. For a hash to be useful in any security-sensitive setting, collisions have to be hard to find.
For MD5 they are not. Practical collision attacks were demonstrated in 2004, and by 2008 researchers had used them to forge a certificate that a certificate authority would sign, which is about as serious as a hash break gets.
Practical guidance that follows:
- Never hash passwords with MD5. Use bcrypt, scrypt or Argon2id, all of which are deliberately slow and salted.
- Never rely on MD5 for signatures or certificates. Use SHA-256 or SHA-3.
- Do not treat MD5 as a security boundary. If an attacker has a reason to forge a value, MD5 is the wrong tool.
// Don't
user.setPassword(DigestUtils.md5Hex(password));
// Do
user.setPassword(new BCryptPasswordEncoder().encode(password));
Where MD5 is still perfectly fine
Checksums, cache keys, and detecting accidental corruption. In all three cases the threat model excludes a deliberate attacker who controls the input, and MD5 is fast and universally available.
The most common real-world use is verifying a download:
# macOS
md5 yourfile.iso
# Linux
md5sum yourfile.iso
# Windows
certutil -hashfile yourfile.iso MD5
Compare the output against the value the publisher lists. If it matches, the file arrived intact. If it does not, the download was truncated or swapped, and you should not run it.
The nuance worth keeping in mind: a match proves you have the file the publisher hashed, not that the publisher is trustworthy. And because collisions are cheap, it is a weak guarantee against tampering. For anything adversarial, checksums published over HTTPS with SHA-256 are the better choice.
The short version
Encoding is reversible and takes no key. Encryption is reversible and needs a key. Hashing is neither, and that is the feature, not a limitation.
MD5 is a hash. It is fine for checking that a file survived a download, and unfit for anything an attacker has a reason to forge. If you have MD5 anywhere near passwords, that is a bug worth fixing this week.
I put together a browser-based MD5 hash generator for the legitimate uses. It computes the digest locally, so whatever you paste never leaves your machine, which is useful when the string you are hashing is a token or a key you would rather not paste into someone else's server.
I build small, privacy-first developer tools that run entirely in the browser. Most of them exist because I kept wanting a tool that wouldn't ask me to upload a config file or create an account first.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.