EngineeringAI Assisted

Cryptographic Hash Functions Explained: SHA-256, Merkle-Damgård Construction, and Collision Resistance

Learn how cryptographic hash functions work under the hood. Explore SHA-256, Merkle-Damgård block processing, the avalanche effect, and HMAC integrity.

JJ
Joey Jazwinski
September 21, 20266 min read
Cryptographic Hash Functions Explained: SHA-256, Merkle-Damgård Construction, and Collision Resistance

Modern internet security rests on an asymmetry: turning arbitrary gigabytes of data into a fixed 256-bit string takes a fraction of a millisecond, but finding the original input from that hash takes longer than the lifespan of the universe.

From Git commit hashes and TLS certificates to blockchain ledgers and password authentication, cryptographic hash functions verify data integrity across every layer of computing.

In this guide, you will learn the three mathematical guarantees of secure hash functions, how the Merkle-Damgård construction processes variable-length streams, why length-extension attacks occur, and how to verify cryptographic digests in code.

💡 Key Takeaways#

  • Three Essential Properties: A cryptographic hash must satisfy Pre-image resistance (one-way), Second Pre-image resistance (tamper-evident), and Collision resistance (no two identical outputs).
  • The Birthday Paradox: Finding any collision among random outputs requires roughly 2^(N/2) operations. For a 256-bit digest, an attacker needs 2^128 evaluations.
  • Merkle-Damgård Processing: SHA-256 splits inputs into 512-bit chunks, running each through 64 rounds of compression operations (bitwise rotations, majority functions, modulo addition).
  • The Avalanche Effect: Changing a single input bit flips approximately 50% of the output bits in the final digest.
  • HMAC Defense: Merkle-Damgård hashes are vulnerable to length extension attacks when used naively for authentication (hash(secret || message)). HMAC solves this through nested hashing.

1. The Three Mathematical Guarantees#

Not all hash functions are cryptographic. Non-cryptographic algorithms like CRC32 or MurmurHash detect accidental hardware transmission corruption quickly, but attackers can reverse or forge them easily.

A cryptographic hash function H(m) must fulfill three security properties:

The Birthday Attack Threshold#

Because the output space is finite (e.g. 2^256 possible outputs for SHA-256) while the input space is infinite, collisions mathematically exist.

However, according to the Birthday Paradox, the work required to find any collision is the square root of the total states:

AlgorithmOutput Bits (N)Pre-Image Work ($2^N$)Collision Resistance Work ($2^$)Modern Status
MD5128 bits$2^$$2^$ (Broken: 2004)Insecure for security
SHA-1160 bits$2^$$2^$ (Broken: 2017)Deprecated
SHA-256256 bits$2^$$2^$Industry Standard
SHA-512512 bits$2^$$2^$High-Security Standard

Finding a collision in SHA-256 requires $2^$ operations. Even if a supercomputer computed 1 trillion hashes per second, finding a collision would require more than $10^$ years.


2. The Merkle-Damgård Construction#

SHA-256 uses the Merkle-Damgård design, which converts a fixed-size compression function into a processor for arbitrary-length inputs.

The 4 Steps of SHA-256 Processing:#

  1. Padding: The input message is appended with a single bit 1, followed by 0 bits until the length is congruent to 448 (mod 512).
  2. Length Encoding: A 64-bit integer representing the original message bit-length is appended, ensuring the total length is an exact multiple of 512 bits.
  3. Initialization: Eight 32-bit working state registers ($a$ through $h$) are initialized using fractional parts of the square roots of the first 8 prime numbers (2, 3, 5, 7, 11, 13, 17, 19).
  4. Compression Rounds: Each 512-bit block is expanded into 64 words and run through 64 iterative mixing rounds using modular addition and bitwise functions:
    • Ch(x, y, z) = (x AND y) XOR (NOT x AND z) (Choice)
    • Maj(x, y, z) = (x AND y) XOR (x AND z) XOR (y AND z) (Majority)
    • Right-rotations and shifts ($\Sigma_0, \Sigma_1, \sigma_0, \sigma_1$)

3. The Avalanche Effect: Proving Chaos#

A core trait of cryptographic hashing is the avalanche effect. If you flip even a single bit in the original payload, the output changes unpredictably.

Let us test two nearly identical strings:

text
Input 1: "The quick brown fox jumps over the lazy dog"
SHA-256: d7a8fbb307d7809469ca9abb6b60c413c2f4abdc9f02a0a8e2440f852e91ddf8

Input 2: "The quick brown fox jumps over the lazy dog." (added period)
SHA-256: ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c

Notice that adding a single period at the end completely transformed every hex byte in the output. On average, exactly 50% of the output bits flip, making reverse statistical analysis impossible.


4. Length Extension Attacks & Why We Need HMAC#

Because the Merkle-Damgård construction passes internal state directly from block to block, an architectural vulnerability exists in naive message authentication:

text
// Flawed authentication pattern:
token = SHA256(secret_key + message)

If an attacker intercepts message and token, they can initialize the compression function with token as the initial vector, append malicious commands, and compute a valid signature without knowing secret_key.

The Solution: HMAC (RFC 2104)#

Hash-based Message Authentication Codes (HMAC) prevent length extension attacks by hashing the key and message in two nested passes:

text
HMAC(K, m) = H((K' XOR opad) || H((K' XOR ipad) || m))

Because the inner hash is enveloped and re-hashed with the outer padded key, an attacker cannot extend the payload.


5. Computing Hashes and HMAC in TypeScript#

Here is a clean implementation using Node.js built-in crypto module to compute digests and HMAC signatures:

typescript
import crypto from 'crypto';

export interface HashSummary {
  input: string;
  sha256: string;
  sha512: string;
  hmacSha256: string;
}

export function computeCryptographicHashes(
  payload: string,
  secretKey: string
): HashSummary {
  // 1. Standard SHA-256 digest
  const sha256 = crypto
    .createHash('sha256')
    .update(payload, 'utf8')
    .digest('hex');

  // 2. High-security SHA-512 digest
  const sha512 = crypto
    .createHash('sha512')
    .update(payload, 'utf8')
    .digest('hex');

  // 3. Length-extension-resistant HMAC-SHA256
  const hmacSha256 = crypto
    .createHmac('sha256', secretKey)
    .update(payload, 'utf8')
    .digest('hex');

  return {
    input: payload,
    sha256,
    sha512,
    hmacSha256,
  };
}

// Example usage:
const results = computeCryptographicHashes(
  'Transaction: Transfer $50 to Alice',
  'super-secure-secret-key-32-bytes!'
);

console.log('SHA-256:', results.sha256);
console.log('HMAC-SHA256:', results.hmacSha256);

6. Interactive Developer Tool: Test & Generate Hashes Locally#

Want to inspect SHA-256, SHA-512, MD5, and HMAC digests in real time, or verify file checksum integrity without uploading bytes to any cloud server?

Test text inputs and local files in your browser:

👉 Try the Interactive Hash & HMAC Generator

The tool runs 100% in your local browser runtime using the Web Cryptography API.


Conclusion: When to Use Which Hash#

  1. Data Integrity & Checksums: Use SHA-256 or SHA-512. Avoid MD5 and SHA-1.
  2. API Request Signing: Use HMAC-SHA256 to prevent tampering and length extension attacks.
  3. Password Storage: Never use raw SHA-256. Use memory-hard key derivation functions like Argon2id or bcrypt with appropriate cost factors.
JJ

Joey Jazwinski

Hi, I'm Joey — a software engineer building modern applications, exploring artificial intelligence, and sharing my journey through code. 🚀

Comments