The logic of a hash function is simple. The function takes the input, produces a fixed-length output, and there’s no way back. You type “CWSpeciaL,” SHA-256 returns a 64-character string, but you can’t get back to “CWSpeciaL” from that string. In theory, of course.
Let’s open our terminal and try these commands:
echo -n "CWSpeciaL" | sha256sum
ad8b4978e1917914a098cdb111fb1da61c171c3a1cdfd63e8a3e8122af2917e6
Now let’s change the last letter:
echo -n "CWSpecial" | sha256sum
586bfda1916a136380374be58b069ef57d2958b9fe3c6c430878f0530a205cb7
We changed a single character and the result changed completely. This is what’s called the avalanche effect, and it removes predictability completely.
In practice, things are actually a bit more complicated.
Take MD5, for example. We still run into it in a lot of places.
echo -n "password" | md5sum
5f4dcc3b5aa765d61d8327deb882cf99
If you search for this hash value, even on Google, you’ll see it gives “password” as the result. There used to be an academic view that producing a collision for MD5 is possible in theory but nobody can do it in practice, but now it’s used in attacks. SHA-1 is similar. Google demonstrated this in 2017 with SHAttered, and browsers now reject SHA-1 certificates. This is exactly where the rainbow table comes in. The hashes of billions of common passwords are computed in advance and put into a table, and you find a direct match from the computed hash.
If you have Hashcat installed, you can try this:
hashcat -m 0 5f4dcc3b5aa765d61d8327deb882cf99 /usr/share/wordlists/rockyou.txt
- Filename..: /usr/share/wordlists/rockyou.txt
- Passwords.: 14344392
- Keyspace..: 14344385
- Runtime…: 0 secs
5f4dcc3b5aa765d61d8327deb882cf99:password
It didn’t even take 1 second to crack. Salt partly solves this. You add a random value to the password before it’s hashed, so the same password produces a different hash for each user and the precomputed table doesn’t work.
user1: password + salt “xK9mA”
echo -n "passwordxK9mA" | sha256sum
5bee3d4a8b09c6233e68af7f988c67f3d3765638a765ee7fda52b5d937d3fdb6
user2: same password + different salt “pQ2nZ”
echo -n "passwordpQ2nZ" | sha256sum
3a0d5656e70c29f1e9e587a17d1782ae1d38829dbd0795a5c81fc095e1e13688
Same password, two different hashes. At this point the rainbow table doesn’t work. But SHA-256 + salt still isn’t considered good enough, because GPUs are incredibly fast at this. That’s exactly why bcrypt and Argon2 are deliberately designed to be slow.
A bcrypt hash looks like this:
$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/lewdBengvkqHkIR0K
$2b$ is the bcrypt version. $12$ is the cost factor. The higher this value, the slower the computation. The attack takes longer too.
Finally, let me add this: you shouldn’t confuse encoding and encryption with hashing. A hash is one-way. They’re not interchangeable.