Hash Functions Explained
Hash functions are the mathematical backbone of password security, digital signatures, and data integrity verification. You will learn how MD5, SHA-1, SHA-256, bcrypt, and Argon2 work, why some are considered broken for password storage, and what properties make a hash function suitable for security applications. Understanding the difference between a fast hash and a slow hash is critical for both attackers and defenders.
Learning Objectives — Deep Dive
Each objective below is broken down into plain English so you know exactly what you are learning, why it matters, and what you will be able to do when you are done. Mark each one complete as you work through the lesson.
Generate hashes with different algorithms
Run 'echo -n "password123" | md5sum' to generate an MD5 hash, 'echo -n "password123" | sha256sum' for SHA-256, and 'echo -n "password123" | sha512sum' for SHA-512. The -n flag prevents echo from appending a newline, which would change the hash value. Notice that all three algorithms produce a fixed-length output regardless of input length — MD5 produces 32 hex characters (128 bits), SHA-256 produces 64 hex characters (256 bits), and SHA-512 produces 128 hex characters (512 bits). Run each command multiple times with the same input and confirm the output is always identical — this deterministic property is what makes hashes useful for password verification.
Observe hash lengths
Compare the output lengths from the previous step: MD5 is 32 characters, SHA-256 is 64 characters, and SHA-512 is 128 characters. Now run 'echo -n "a" | md5sum' and 'echo -n "$(python3 -c "print('a'*1000)")" | md5sum' and observe that both produce a 32-character hash — this is the fixed-length property of hash functions. Also run 'echo -n "password123" | md5sum' and 'echo -n "password124" | md5sum' and compare the outputs — completely different hashes from inputs that differ by only one character, demonstrating the avalanche effect that makes hash functions unpredictable.
Generate a bcrypt hash
Run 'python3 -c "import bcrypt; print(bcrypt.hashpw(b'password123', bcrypt.gensalt(rounds=12)).decode())"' to generate a bcrypt hash with a cost factor of 12. Notice that the output starts with '$2b$12$' — the $2b$ identifies bcrypt and 12 is the cost factor (work factor). Run the command twice and observe that the hash is different each time even though the password is the same — bcrypt automatically generates a random salt and embeds it in the hash output. The cost factor controls how many iterations are performed: rounds=12 means 2^12 = 4,096 iterations, making each hash attempt significantly slower than MD5 or SHA-256.
Compare cracking speed
Run 'hashcat --benchmark -m 0' for MD5, 'hashcat --benchmark -m 1800' for SHA-512-crypt, and 'hashcat --benchmark -m 3200' for bcrypt to compare cracking speeds on your hardware. A modern GPU can crack billions of MD5 hashes per second, millions of SHA-512-crypt hashes per second, but only thousands of bcrypt hashes per second. This speed difference is the entire point of password hashing algorithms designed for security — bcrypt, scrypt, and Argon2 are intentionally slow to make brute force attacks computationally infeasible. Document the benchmark results and use them to explain to clients why algorithm choice matters as much as password complexity.