A cryptographic hash function is a one-way street designed to be incredibly difficult to reverse.

Imagine you have a digital document, and you run it through a hash function. What you get back is a fixed-size string of characters, a "digest." This digest is like a unique fingerprint for that document. Even a tiny change in the original document will result in a completely different fingerprint. This is the core idea, but what makes a hash function secure? It’s a combination of five key properties.

Let’s see this in action. We’ll use SHA-256, a very common and secure hash function, via a simple Python script.

import hashlib

def hash_data(data):
  """Computes the SHA-256 hash of the input data."""
  sha256 = hashlib.sha256()
  sha256.update(data.encode('utf-8'))
  return sha256.hexdigest()

# Example 1: Hashing a simple string
data1 = "Hello, world!"
hash1 = hash_data(data1)
print(f"Data: '{data1}'")
print(f"Hash: {hash1}\n")

# Example 2: Hashing a slightly modified string
data2 = "hello, world!" # lowercase 'h'
hash2 = hash_data(data2)
print(f"Data: '{data2}'")
print(f"Hash: {hash2}\n")

# Example 3: Hashing a much longer string
data3 = "This is a much longer piece of text that will also be hashed using SHA-256 to demonstrate the fixed output size."
hash3 = hash_data(data3)
print(f"Data: '{data3}'")
print(f"Hash: {hash3}\n")

Running this code gives us output like:

Data: 'Hello, world!'
Hash: 315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd6

Data: 'hello, world!'
Hash: dff7e3b4449c5121449b4f966845613e18312a365826a99a60509b1a66160881

Data: 'This is a much longer piece of text that will also be hashed using SHA-256 to demonstrate the fixed output size.'
Hash: 78f017c3d529b861a4054c3338d96b093241d676c4294e658964788a00f0185c

Notice how changing "Hello" to "hello" completely alters the hash. Also, regardless of the input string’s length, the output hash is always 64 hexadecimal characters long (256 bits). This illustrates the core behavior, but security is built on specific properties.

The first property is pre-image resistance. This means that given a hash value H, it should be computationally infeasible to find any message M such that hash(M) = H. Think of it as trying to find the original document given only its fingerprint. It’s practically impossible. This is why you can safely store password hashes instead of the actual passwords. If a database is breached, attackers only get the hashes, and they can’t easily recover the original passwords.

Next is second pre-image resistance. This is slightly different: given an input message M1, it should be computationally infeasible to find a different message M2 such that hash(M1) = hash(M2). This prevents someone from taking a document you’ve signed with its hash and creating a different document that has the exact same hash, thereby falsely appearing to be signed by you.

The third property is collision resistance. This is the strongest of the resistance properties. It means it should be computationally infeasible to find any two distinct messages, M1 and M2, such that hash(M1) = hash(M2). This is harder than second pre-image resistance because you don’t get to pick one of the messages; you have to find any pair that collides. If collisions are easy to find, the integrity of the hash function is compromised, as two different pieces of data could be considered "the same" by the hash.

The fourth property is deterministic output. As we saw in the Python example, for any given input, the hash function must always produce the exact same output. If you hash "Hello, world!" today, you must get the same hash as you would if you hashed it a year from now. This predictability for a given input is crucial for consistency and verification. Imagine if your fingerprint changed every time you showed it!

Finally, the fifth property is avalanche effect. This property states that a small change in the input (like changing a single bit) should produce a significant and unpredictable change in the output hash. Ideally, about half of the output bits should flip. This is why even a minor alteration to a file results in a drastically different hash, making it easy to detect tampering.

The magic of the avalanche effect lies in how the internal operations of the hash function (like bitwise operations, modular additions, and permutations) are designed to propagate changes throughout the entire input block. Each bit of the input influences multiple bits of the intermediate state, and these influences are amplified and mixed across rounds of computation. This ensures that no part of the input remains "unchanged" in the output’s structure, making it impossible to predict the effect of a small input change without performing the full hash computation.

These five properties—pre-image resistance, second pre-image resistance, collision resistance, deterministic output, and the avalanche effect—collectively ensure that a cryptographic hash function can be trusted for its intended security applications, such as data integrity verification, digital signatures, and password storage.

The next hurdle in understanding cryptographic primitives is often exploring how these hash functions are used in digital signatures.

Want structured learning?

Take the full Cryptography course →