Hash Functions: From Theory to Real-World Attacks

A hash function turns input into a fixed-length output with no way back; from MD5 to bcrypt, the avalanche effect, rainbow tables and salt are explained with real attacks.

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.