Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Applied Cryptography for Developers  Module 4 – Hash functions and their uses

Hash functions: SHA-2, SHA-3 and BLAKE2

Preimage, second-preimage and collision resistance, SHA-2, SHA-3 and BLAKE2 checked against official values, hashing in chunks, and replacing MD5 and SHA-1.

  • Beginner
  • 25 minutes
  • Examples run with Node.js 24.21.0, Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free lesson, Pro answers: 25 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Explain preimage, second-preimage and collision resistance with examples
  • Compute SHA-256, SHA-3 and BLAKE2 digests in the browser, Node and Python and check them against official values
  • Hash large inputs incrementally where the API allows it
  • Replace MD5 and SHA-1 in security uses and find where they still appear

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain preimage, second-preimage and collision resistance with examples
  • Compute SHA-256, SHA-3 and BLAKE2 digests in the browser, Node and Python and check them against official values
  • Hash large inputs incrementally where the API allows it
  • Replace MD5 and SHA-1 in security uses and find where they still appear

Before you start

On this page

Flip one bit of a message and about half of the 256 bits of its SHA-256 digest flip with it: over the 1,000 one-bit changes this lesson measures, 127.7 on average. That behaviour, and three precise promises behind it, is what lets a 32-byte digest stand for a file of any size, a release, a stored token or a signed document.

This lesson makes the promises exact. You see what preimage, second-preimage and collision resistance mean and what each costs an attacker, compute SHA-2, SHA-3 and BLAKE2 digests in JavaScript and Python and check them against the official values, hash large inputs in pieces, and see where MD5 and SHA-1 still turn up and what replaces them. A two-step code lab at the end hashes a stream and finds a collision of a deliberately short hash.

Three promises: preimage, second-preimage and collision resistance

A cryptographic hash function: A function that maps input of any length to a fixed-size digest, quickly, and resists preimages, second preimages and collisions. SHA-256, SHA-3 and BLAKE2 are current examples. maps input of any length to a digest of fixed length, quickly. The Handbook of Applied Cryptography lists three further properties, each a promise that some search is infeasible:

Promise The attacker has The attacker must find Generic cost for n bits For SHA-256
preimage resistance: The property that, given a digest, finding any input that hashes to it is infeasible: about 2^n work for an n-bit hash. Also called one-wayness. a digest any input with that digest about 2ⁿ hashes 2²⁵⁶
second-preimage resistance: The property that, given one input, finding a different input with the same digest is infeasible: about 2^n work for an n-bit hash. an input a different input with the same digest about 2ⁿ 2²⁵⁶
collision resistance: The property that finding any two different inputs with the same digest is infeasible. The birthday bound limits it to about 2^(n/2) work for an n-bit hash, 128 bits for SHA-256. nothing any two inputs with the same digest about 2^(n/2) 2¹²⁸

Each matters somewhere. A database that stores the SHA-256 of each reset token needs preimage resistance, so that a leaked table does not give back the tokens. A download page that publishes a file’s digest needs second-preimage resistance, so that nobody can make another file to match it. A signature on a digest needs collision resistance too: someone who can choose the message could prepare two that collide, have one signed and use the signature for the other. The Handbook proves that collision resistance implies second-preimage resistance (Fact 9.18).

Collisions are the cheap one because of the birthday bound: The rule that random values of b bits start to repeat after about 2^(b/2) of them. It is why a 256-bit hash gives about 128 bits of collision resistance and why random nonces have usage limits. More in “Security levels, key sizes and attack cost”: every pair of inputs tried is a chance, and pairs grow with the square of the inputs. Cut SHA-256 down to 16 bits and every search finishes in a moment, so the costs can be measured:

The three searches on SHA-256 cut down to 16 bits Python · truncated/truncated.py
"""SHA-256 cut down to its first 16 bits, so that the three generic attacks finish in a moment: a collision, a second
preimage and a preimage, each searched 10 times with numbered messages, so the counts repeat on every run."""
import hashlib

BITS = 16


def h(message):
    """The first BITS bits of SHA-256: a toy hash that is weak only because it is short."""
    return int.from_bytes(hashlib.sha256(message).digest()[:4], "big") >> (32 - BITS)


def collision(run):
    """Hash new messages until two of them share a value; return how many were hashed."""
    seen = {}
    i = 0
    while True:
        value = h(f"run {run} message {i}".encode())
        if value in seen:
            return i + 1
        seen[value] = i
        i += 1


def matching(target, run):
    """Hash new messages until one has the given value; return how many were hashed."""
    i = 0
    while True:
        i += 1
        if h(f"run {run} try {i}".encode()) == target:
            return i


runs = range(10)
collisions = [collision(r) for r in runs]
second = [matching(h(f"the original file, version {r}".encode()), r) for r in runs]
preimage = [matching(r * 6553 % 2**BITS, r) for r in runs]

average = lambda xs: sum(xs) / len(xs)
print(f"a {BITS}-bit hash has {2**BITS:,} values")
print(f"collision:       {average(collisions):>9,.0f} hashes on average; about 1.25 x 2^{BITS // 2} = {1.25 * 2 ** (BITS / 2):,.0f}")
print(f"second preimage: {average(second):>9,.0f} hashes on average; about 2^{BITS} = {2**BITS:,}")
print(f"preimage:        {average(preimage):>9,.0f} hashes on average; about 2^{BITS} = {2**BITS:,}")
print("for the full 256 bits: collision about 2^128, second preimage and preimage about 2^256")

Output

a 16-bit hash has 65,536 values
collision:             388 hashes on average; about 1.25 x 2^8 = 320
second preimage:    56,171 hashes on average; about 2^16 = 65,536
preimage:           60,564 hashes on average; about 2^16 = 65,536
for the full 256 bits: collision about 2^128, second preimage and preimage about 2^256

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 truncated.py

On ten runs each, a collision took 388 hashes on average, close to the predicted 320, while a second preimage took 56,171 and a preimage 60,564, close to 2¹⁶ = 65,536. The same arithmetic at full length is why SHA-256 gives 128 bits of collision resistance and 256 of preimage resistance, the two numbers of NIST SP 800-57’s Table 3 in the security-levels lesson. ASVS 5.0 turns that into a rule in requirement 11.4.3: an output of at least 256 bits where collision resistance is needed, and at least 128 bits where only second preimages matter.

Key idea

A digest of n bits resists preimages and second preimages up to about 2ⁿ work but collisions only up to 2^(n/2), so size a hash for the collisions whenever an attacker chooses what gets hashed.

Checkpoint

Check: The three resistance properties

  1. Question 1 of 2 A project publishes the SHA-256 digest of each release next to the download link. Which property stops someone from making a different file that matches a published digest?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Second-preimage resistance

    With the release file given, the question is whether another input with the same digest can be found: second- preimage resistance, about 2²⁵⁶ work for SHA-256. Note that a digest from the same server as the file protects only against accidents, a point the next lesson takes up.

  2. Question 2 of 2 SHA-1 has a 160-bit digest. By the birthday bound, a generic collision search takes about 2 to the power of what?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 80 (the exponent)

    A collision of an n-bit hash takes about 2^(n/2) hashes, here 2⁸⁰. The public SHA-1 collision needed far less than that, which is why SHA-1 counts as broken for collisions.

SHA-2, SHA-3 and BLAKE2, checked against their official values

Three families cover nearly every modern use. SHA-2, in FIPS 180-4, has six functions: SHA-224 and SHA-256, SHA-384 and SHA-512, and two truncated forms of SHA-512, SHA-512/224 and SHA-512/256. SHA-3, in FIPS 202, has four hash functions, SHA3-224 to SHA3-512, and two extendable-output function (XOF): A hash function that gives as many output bytes as you ask for, such as SHAKE128 and SHAKE256 of FIPS 202., SHAKE128 and SHAKE256, all built on the Keccak permutation. BLAKE2, in RFC 7693, has BLAKE2b with digests of 1 to 64 bytes and BLAKE2s with 1 to 32, and can take a key directly, which makes it a MAC as well. ASVS 5.0’s Appendix C lists SHA-256 and longer, the 256-bit and longer SHA-3 functions, SHAKE256, BLAKE2b, BLAKE2s and BLAKE3 as acceptable for general use.

A digest you compute is only trusted once it matches a published value. NIST publishes example values for each of its algorithms, and the RFC gives its own. Web Crypto’s digest() supports SHA-1, SHA-256, SHA-384 and SHA-512, and this program checks all four against NIST:

Web Crypto digests against NIST's example values JavaScript · digests-js/digests.mjs
// Web Crypto's digest() for SHA-1, SHA-256, SHA-384 and SHA-512, checked against the example values NIST publishes
// for the message "abc" and for the 448-bit two-block message.
const ABC = new TextEncoder().encode('abc');
const TWO_BLOCK = new TextEncoder().encode('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq');
const hex = (buffer) => Array.from(new Uint8Array(buffer), (b) => b.toString(16).padStart(2, '0')).join('');

const NIST = [
  ['SHA-1', ABC, 'a9993e364706816aba3e25717850c26c9cd0d89d'],
  ['SHA-256', ABC, 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'],
  ['SHA-384', ABC, 'cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7'],
  ['SHA-512', ABC, 'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f'],
  ['SHA-1', TWO_BLOCK, '84983e441c3bd26ebaae4aa1f95129e5e54670f1'],
  ['SHA-256', TWO_BLOCK, '248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1'],
];

for (const [algorithm, message, expected] of NIST) {
  const digest = hex(await crypto.subtle.digest(algorithm, message));
  const which = message === ABC ? '"abc"' : 'two-block';
  console.log(`${algorithm.padEnd(8)}${which.padEnd(11)}${digest.slice(0, 16)}… ${digest.length * 4} bits, matches NIST: ${digest === expected}`);
}

Output

SHA-1   "abc"      a9993e364706816a… 160 bits, matches NIST: true
SHA-256 "abc"      ba7816bf8f01cfea… 256 bits, matches NIST: true
SHA-384 "abc"      cb00753f45a35e8b… 384 bits, matches NIST: true
SHA-512 "abc"      ddaf35a193617aba… 512 bits, matches NIST: true
SHA-1   two-block  84983e441c3bd26e… 160 bits, matches NIST: true
SHA-256 two-block  248d6a61d20638b8… 256 bits, matches NIST: true

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node digests.mjs

Every line matches, for abc and for the 448-bit message, which takes two blocks once it is padded. Python’s hashlib adds SHA-3, BLAKE2 and SHAKE, which Web Crypto does not offer in browsers. Run it, then change data to your own bytes:

SHA-256, SHA3-256, BLAKE2b and SHAKE128 in hashlib Python · tour/hashlib_tour.py
"""SHA-256, SHA3-256, BLAKE2b and SHAKE128 of the same input with hashlib, two checked against their standards, and a
large input hashed in pieces."""
import hashlib

SHA3_256_ABC = "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"  # NIST's example value
BLAKE2B_512_ABC = (  # RFC 7693, Appendix A
    "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1"
    "7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"
)

data = b"abc"
sha256 = hashlib.sha256(data).hexdigest()
sha3 = hashlib.sha3_256(data).hexdigest()
blake2b_512 = hashlib.blake2b(data).hexdigest()
print(f"sha256:              {sha256}")
print(f"sha3_256:            {sha3}")
print(f"blake2b, 32 bytes:   {hashlib.blake2b(data, digest_size=32).hexdigest()}")
print(f"shake_128, 32 bytes: {hashlib.shake_128(data).hexdigest(32)}")
print(f"SHA3-256 as in FIPS 202's example: {sha3 == SHA3_256_ABC}")
print(f"BLAKE2b-512 as in RFC 7693 Appendix A: {blake2b_512 == BLAKE2B_512_ABC}")

big = bytes(range(256)) * 4096  # 1 MiB
pieces = hashlib.sha256()
for start in range(0, len(big), 65_536):
    pieces.update(big[start : start + 65_536])
print(f"1 MiB in 16 pieces equals 1 MiB at once: {pieces.hexdigest() == hashlib.sha256(big).hexdigest()}")

Output

sha256:              ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
sha3_256:            3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532
blake2b, 32 bytes:   bddd813c634239723171ef3fee98579b94964e3bb1cb3e427262c8c068d52319
shake_128, 32 bytes: 5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc8
SHA3-256 as in FIPS 202's example: True
BLAKE2b-512 as in RFC 7693 Appendix A: True
1 MiB in 16 pieces equals 1 MiB at once: True

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 hashlib_tour.py

SHA3-256 matches the FIPS 202 example and BLAKE2b-512 the value in RFC 7693’s Appendix A, and the three 256-bit digests share nothing although the input is the same. SHAKE128 gave 32 bytes because the call asked for 32: an extendable-output function gives as many bytes as you ask for. The last property worth seeing is the avalanche effect: The property that changing one bit of the input changes about half of the output bits, so that a digest gives no hint of how its input differs from another., the flip of about half the output for one changed input bit:

One flipped bit, half the digest changed Python · avalanche/avalanche.py
"""The avalanche effect, measured: flip one bit of a message and count how many of SHA-256's 256 output bits change."""
import hashlib


def bits(data):
    return int.from_bytes(hashlib.sha256(data).digest(), "big")


changes = []
for i in range(1000):
    message = bytearray(f"order {i}: pay 500 to account 1234".encode())
    position = i % (len(message) * 8)
    before = bits(bytes(message))
    message[position // 8] ^= 1 << (position % 8)  # flip one bit
    changes.append(bin(before ^ bits(bytes(message))).count("1"))

mean = sum(changes) / len(changes)
print(f"1,000 one-bit flips: on average {mean:.1f} of 256 output bits changed")
print(f"fewest {min(changes)}, most {max(changes)}; between 112 and 144 in {sum(112 <= c <= 144 for c in changes) / 10:.1f} % of cases")
print(f"one example: {changes[0]} bits changed when one bit of 'order 0: …' flipped")

Output

1,000 one-bit flips: on average 127.7 of 256 output bits changed
fewest 101, most 153; between 112 and 144 in 96.7 % of cases
one example: 131 bits changed when one bit of 'order 0: …' flipped

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 avalanche.py

Over 1,000 one-bit flips, 127.7 of the 256 output bits changed on average, never fewer than 101 and never more than 153, and 96.7 % of the flips changed between 112 and 144. A digest that changed in only a few places would leak what changed in the input; this one gives no hint.

Test with known answers first

Before any hashing code ships, run it on the published vectors: abc and the two-block message for SHA-2, the FIPS 202 examples for SHA-3 and RFC 7693’s Appendix A for BLAKE2. A wrong encoding, a missing byte or a hash of a string instead of its bytes shows up at once.

Checkpoint

Check: SHA-2, SHA-3 and BLAKE2

  1. Question 1 of 2 Which of these can crypto.subtle.digest() compute in a browser?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: SHA-256, SHA-384 and SHA-512, and SHA-1, which MDN says not to use

    Web Crypto's digest() offers the SHA-2 functions SHA-256, SHA-384 and SHA-512, plus SHA-1 for old formats. SHA-3, SHAKE and BLAKE2 need another implementation, such as Python's hashlib.

  2. Question 2 of 2 How many hex characters does hashlib.shake_256(data).hexdigest(20) return?

    Type a number.

    A hint

    The argument of an extendable-output function is a length in bytes.

    Show the answer: Question 2 of 2

    Answer: 40 characters

    SHAKE256 gives as many bytes as asked, here 20, and hex writes two characters per byte, so 40. The lesson's shake_128 call asked for 32 bytes and printed 64 characters.

Large inputs: hash in pieces, where the API allows it

SHA-256 never needs the whole input at once. It pads the message, cuts it into 512-bit blocks and mixes them one at a time into a running state, so the next block can wait until it has been read. Step through it for abc:

Interactive · stage by stage How SHA-256 reads "abc", block by block Step through the padding, the blocks and the running state. The last step shows what the running state means for anyone who knows a digest.
How SHA-256 reads "abc", block by block, step 6 of 6. Showing The message, Padding, 512-bit blocks, Compression function, The running state, Digest = final state. In focus: Digest = final state.the next blockThe message"abc": 3 bytes, 24 bitsPadding0x80, zeros, the length512-bit blocks"abc" fills one blockCompression function64 rounds per blockThe running stateeight 32-bit wordsDigest = final stateba7816bf … f20015adHMAC, SHA-3 and SHA-512/256 avoid this

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Bytes in. SHA-256 reads bytes, here the three bytes of "abc", 24 bits. It works on whole blocks of 512 bits, so the message is padded first.
  2. Padding. FIPS 180-4 appends one 1 bit, which is the byte 0x80 here, then zeros, then the message length as a 64-bit number, 24, so that the total is a multiple of 512 bits.
  3. Blocks. "abc" and its padding fill exactly one 64-byte block. A file of 1 MiB makes 16,385 blocks: 16,384 for the data and one more for the padding and the length.
  4. One block into the state. The state starts as eight fixed 32-bit words. The compression function mixes one block into it in 64 rounds, and the result is the new state.
  5. The loop. A longer message sends the new state and the next block through the same function, block after block. That running state is why update() can take a file piece by piece and still give the digest of the whole file.
  6. The digest is the state. After the last block the digest is the state itself: ba7816bf … f20015ad for "abc". Anyone who knows a digest and the message length can therefore keep hashing from it, which is length extension, and why SHA-256(secret ∥ message) is not a MAC.

Whether you can use that depends on the API. Web Crypto’s digest() takes one buffer, and MDN notes that it does not support streaming input, so the whole input must be in memory. node:crypto has createHash(), whose update() takes the data piece by piece, and crypto.hash() for a one-shot digest, which is no longer experimental from Node 24.13.1. Python’s hashlib documents that repeated update() calls equal a single call on the joined data, and hashlib.file_digest(), from Python 3.11, reads a file for you; from 3.14 it raises BlockingIOError for a file opened in non-blocking mode. This program hashes 10 MiB all three ways:

10 MiB hashed in pieces and at once JavaScript · streaming-js/streaming.mjs
// Hashing a large input in pieces with node:crypto, and why Web Crypto's digest() needs the whole input at once.
import { createHash, hash } from 'node:crypto';

const big = new Uint8Array(10 * 1024 * 1024); // 10 MiB
for (let i = 0; i < big.length; i++) big[i] = (i * 31) & 0xff;

const pieces = createHash('sha256');
for (let start = 0; start < big.length; start += 1 << 20) pieces.update(big.subarray(start, start + (1 << 20)));
const streamed = pieces.digest('hex');

const oneShot = hash('sha256', big); // the one-shot helper of node:crypto
const web = Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', big)), (b) => b.toString(16).padStart(2, '0')).join('');

console.log(`10 MiB in 10 pieces with createHash().update(): ${streamed.slice(0, 16)}…`);
console.log(`the same with crypto.hash() in one call:        ${oneShot === streamed}`);
console.log(`the same with Web Crypto digest(), all at once: ${web === streamed}`);
console.log(`createHash keeps ${pieces.constructor.name === 'Hash' ? 'a Hash object' : 'state'} between pieces; digest() takes one buffer and has no update()`);

Output

10 MiB in 10 pieces with createHash().update(): 50a082ea7b8c57f5…
the same with crypto.hash() in one call:        true
the same with Web Crypto digest(), all at once: true
createHash keeps a Hash object between pieces; digest() takes one buffer and has no update()

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node streaming.mjs

All three digests agree. For a file of several gigabytes, createHash().update() or hashlib keeps the memory use to one piece; Web Crypto would need the whole file as one buffer.

The running state has a second consequence, shown in the last step of the figure. A SHA-256 digest is the complete state after the last block, so anyone who knows the digest of a message and its length can go on hashing more blocks from it without knowing the message. This is called length extension: Continuing a hash from a known digest without knowing the message, possible with SHA-256 and SHA-512 because their digest is the full internal state. It is why SHA-256(secret ∥ message) is not a MAC., and it is why a tag made as SHA-256(secret ∥ message) can be extended to a longer message with a valid tag. HMAC, SHA-3 and SHA-512/256 are not open to it, and the next module builds message authentication on HMAC.

A hash with a secret in front is not a MAC

Wrong: tag = sha256(api_secret + body) to sign webhooks. Length extension lets anyone who saw one tag make a tag for the body with more bytes appended. Right: hmac.new(api_secret, body, "sha256"), or the language’s own HMAC, which the next module explains.

Checkpoint

Check: Hashing in pieces

  1. Question 1 of 2 A browser page must hash a 4 GB file the user selected. What does MDN's note on digest() mean for that?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: digest() needs the whole input in one buffer, so the page needs an incremental implementation instead

    Hashing in pieces needs an API with update(), such as node:crypto's createHash() or hashlib. Web Crypto's digest() is one-shot, so in a browser a large file needs an incremental hash library.

  2. Question 2 of 2 What does this print?

    Read the code, then choose one answer.

    import hashlib
    
    a = hashlib.sha256()
    a.update(b"ab")
    a.update(b"c")
    b = hashlib.sha256(b"abc")
    print(a.hexdigest() == b.hexdigest())
    Show the answer: Question 2 of 2

    Answer: True

    update(b"ab") then update(b"c") feeds the same bytes into the same running state as update(b"abc"), so the digests are equal, ba7816bf … for both.

MD5 and SHA-1: retired for security, still met in old code

MD5 and SHA-1 were once the default choices, and both now fail at the promise that matters most for signatures. Their history in standards, from first publication to retirement:

Timeline · 7 events in order From MD5 to SHA-3, and the retirement of MD5 and SHA-1 Walk through the standards in order. The tags say which are retired for security and which are current.
  1. 1992

    MD5 retired

    RFC 1321 describes MD5, a hash function with a 128-bit digest, published as an informational document.

  2. 1995

    SHA-1 retired

    FIPS 180-1 adds SHA-1, a 160-bit hash, to NIST's Secure Hash Standard.

  3. 2002

    SHA-2 current

    FIPS 180-2 adds SHA-256, SHA-384 and SHA-512. Today's FIPS 180-4 also has SHA-224, SHA-512/224 and SHA-512/256.

  4. 2011

    MD5 ruled out for signatures guidance

    RFC 6151 rules MD5 out wherever collision resistance is needed, digital signatures included, and advises new protocols against HMAC-MD5.

  5. 2015

    SHA-3 and BLAKE2 current

    FIPS 202 standardises SHA-3, four hash functions and two extendable-output functions built on Keccak, and RFC 7693 describes BLAKE2b and BLAKE2s.

  6. 2017

    The first SHA-1 collision broken

    Researchers from CWI Amsterdam and Google publish two different PDF files with the same SHA-1 digest, after about 6,500 CPU-years and 110 GPU-years of computation.

  7. 2022

    SHA-1's end date guidance

    NIST sets the end of 2030 as the date by which SHA-1 is to be phased out in favour of SHA-2 and SHA-3; modules that still use it after 2030 may not be bought by the US federal government.

The timeline has 7 events, from 1992 to 2022.

RFC 6151 rules MD5 out wherever collision resistance is needed, digital signatures among them. For SHA-1 the first collision was demonstrated publicly: two different PDF files with the same SHA-1 digest, after about 6,500 CPU-years and 110 GPU-years of work, and NIST has set the end of 2030 for phasing SHA-1 out in favour of SHA-2 and SHA-3. ASVS 5.0 is blunter: requirement 11.4.1 says disallowed hash functions such as MD5 must not be used for any cryptographic purpose, and its Appendix C lists MD4, MD5 and every CRC as disallowed and SHA-1 as legacy, unsuitable for HMAC, key derivation, random bit generation and signatures.

You will still meet both. Download pages print MD5 or SHA-1 checksums, which catch a damaged download but never a deliberate swap. Older protocols use HMAC-SHA-1 or HMAC-MD5; RFC 6151 sees no practical attack on HMAC-MD5 as a MAC yet says new protocols should not use it. And code uses MD5 for cache keys and file names, where nothing is defended. Python makes that last use explicit: every hashlib constructor takes usedforsecurity=False, which tells a restricted build that the hash is not used in a security context.

SHA-1 to deduplicate uploaded files

Wrong: storing uploads by their SHA-1 digest, so that a new file whose digest is already known is not stored again. Two different files with the same SHA-1 digest exist, so one user’s file can take the place of another’s. Right: SHA-256 or longer for any identity that someone else’s input could imitate.

Checkpoint

Check: MD5 and SHA-1 retired

  1. Question 1 of 2 A download page lists an MD5 checksum next to each file. What does a matching MD5 tell you?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: That the download was not damaged by accident, and nothing about whether someone replaced the file on purpose

    MD5 is fine against accidents and useless against a deliberate attacker, who can create collisions and who may control the page that shows the checksum. Authenticity needs a signature or a trusted channel, the subject of the next lesson.

  2. Question 2 of 2 Which statements about SHA-1 follow the lesson's sources?

    Choose every answer that is right.

    Show the answer: Question 2 of 2

    Answer:

    • Two different PDF files with the same SHA-1 digest have been published
    • NIST plans for SHA-1 to be phased out by the end of 2030

    SHA-1 is broken for collisions, retired by NIST with an end date of 2030 and listed as legacy by ASVS 5.0. New code uses SHA-256 or longer, or SHA-3.

Practise: hash a stream, find a short collision

Two exercises, with sample tests that run on this page, put the lesson to work in Python. The first hashes a stream of chunks with an allowed algorithm, held to NIST’s values split at awkward places, and refuses MD5 and SHA-1. The second finds a collision of SHA-256 cut down to 12, 16 and 20 bits within the birthday bound, where the starter code wastes its time on a second-preimage search:

Code lab · 2 steps

Build a streaming hash and test the birthday bound

Hash a stream of chunks with an allowed algorithm, then find a collision of a short hash in about the square root of its size.

  1. Hash a stream of chunks with an allowed hash To do
  2. A collision of a short hash, at the birthday bound To do

Exercise · Easy · Python

Hash a stream of chunks with an allowed hash

Write hash_chunks(chunks, algorithm="sha256") in chunks.py. It returns the hex digest of all the chunks joined together, hashing them one at a time, so that a file of any size can be hashed as it is read:

  • chunks is any iterable of bytes, such as a list or a generator that reads a file; it can be read only once, and it may be empty.
  • algorithm is a hashlib name. Accept only sha256, sha384, sha512, sha3_256, sha3_384, sha3_512, blake2b and blake2s, and raise ValueError for anything else, md5 and sha1 included.

The sample tests check your digests against NIST's example values for abc and for the 448-bit two-block message, split at awkward places. The starter code creates a new hash object for every chunk, so only the last chunk counts.

Starter code · chunks.py

import hashlib


def hash_chunks(chunks, algorithm="sha256"):
    """The hex digest of all chunks joined together, hashed one chunk at a time."""
    for chunk in chunks:
        h = hashlib.new(algorithm)  # a new hash for every chunk: only the last one counts
        h.update(chunk)
    return h.hexdigest()
The sample tests · test_chunks.py
import hashlib

from chunks import hash_chunks

ABC = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"  # SHA-256("abc"), NIST
TWO_BLOCK = b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
TWO_BLOCK_SHA256 = "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
SHA3_ABC = "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"  # FIPS 202 example


def raises_value_error(call):
    try:
        call()
    except ValueError:
        return True
    return False


def test_abc_split_every_way():
    """'abc' in one chunk, in two and in three gives NIST's SHA-256 value"""
    for chunks in [[b"abc"], [b"a", b"bc"], [b"ab", b"c"], [b"a", b"b", b"c"], [b"", b"abc", b""]]:
        assert hash_chunks(chunks) == ABC, chunks


def test_two_block_message_split_at_block_edges():
    """the 448-bit message split at 1, 55, 56 and 63 bytes"""
    for cut in [1, 55, 56, 63]:
        assert hash_chunks([TWO_BLOCK[:cut], TWO_BLOCK[cut:]]) == TWO_BLOCK_SHA256, cut


def test_a_generator_read_once():
    """1 MiB from a generator of 4,096 chunks equals hashing it at once"""
    data = bytes(range(256)) * 4096
    pieces = (data[i : i + 256] for i in range(0, len(data), 256))
    assert hash_chunks(pieces) == hashlib.sha256(data).hexdigest()


def test_empty_input():
    """no chunks at all is the digest of the empty message"""
    assert hash_chunks([]) == hashlib.sha256(b"").hexdigest()


def test_other_allowed_hashes():
    """SHA3-256 of 'abc' as in FIPS 202, and BLAKE2b"""
    assert hash_chunks([b"ab", b"c"], "sha3_256") == SHA3_ABC
    assert hash_chunks([b"a", b"bc"], "blake2b") == hashlib.blake2b(b"abc").hexdigest()


def test_retired_hashes_are_refused():
    """md5, sha1, and names that are not hashes"""
    for name in ["md5", "sha1", "MD5", "crc32", ""]:
        assert raises_value_error(lambda: hash_chunks([b"abc"], name)), name
A hint

Create the hash object once, before the loop, with hashlib.new(algorithm), call update(chunk) for every chunk, and call hexdigest() after the loop. Check the name against a set of allowed names before anything else.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Exercise · Medium · Python

A collision of a short hash, at the birthday bound

Show on a hash you own why output length decides collision resistance. short_hash(message, bits) in collision.py keeps the first bits bits of SHA-256. Write find_collision(bits), for bits from 8 to 32:

  • Hash the messages b"0", b"1", b"2" … in that order, and stop at the first message whose short hash equals the short hash of an earlier one.
  • Return (earlier, later, hashed): the two messages and the number of messages hashed, the later one included.
  • Raise ValueError for bits outside 8 to 32.

By the birthday bound a repeat comes after about 1.25 × 2^(bits / 2) messages; the sample tests allow up to four times that. The starter code fixes the first message and waits for a second one to match it, a second-preimage search that needs about 2^bits tries.

Starter code · collision.py

import hashlib


def short_hash(message, bits):
    """The first `bits` bits of SHA-256, as a number: a toy hash that is weak only because it is short."""
    return int.from_bytes(hashlib.sha256(message).digest()[:4], "big") >> (32 - bits)


def find_collision(bits):
    """Two different messages with the same short hash, and how many messages were hashed."""
    first = b"0"
    target = short_hash(first, bits)
    i = 0
    while True:  # waits for a match with one fixed message: about 2**bits tries
        i += 1
        message = str(i).encode()
        if short_hash(message, bits) == target:
            return first, message, i + 1
The sample tests · test_collision.py
import hashlib

from collision import find_collision, short_hash


def reference_short_hash(message, bits):
    """The first `bits` bits of SHA-256, computed here, independently of the code under test."""
    return int(hashlib.sha256(message).hexdigest(), 16) >> (256 - bits)


def raises_value_error(call):
    try:
        call()
    except ValueError:
        return True
    return False


def check(bits):
    earlier, later, hashed = find_collision(bits)
    assert earlier != later, "two different messages"
    assert reference_short_hash(earlier, bits) == reference_short_hash(later, bits), "with the same short hash"
    assert hashed <= 4 * 1.25 * 2 ** (bits / 2), f"{hashed:,} messages: about {1.25 * 2 ** (bits / 2):,.0f} should do"
    return hashed


def test_the_short_hash():
    """the first 16 bits of SHA-256('abc') are 0xba78"""
    assert short_hash(b"abc", 16) == 0xBA78


def test_12_bits():
    """a 12-bit hash repeats within about 80 messages"""
    check(12)


def test_16_and_20_bits():
    """16 and 20 bits: four more output bits, four times the work"""
    check(16)
    check(20)


def test_messages_in_order():
    """the messages are b'0', b'1', b'2' … and the count includes the later one"""
    earlier, later, hashed = find_collision(12)
    assert later == str(hashed - 1).encode()
    assert int(earlier) < int(later)


def test_bits_out_of_range():
    """7 and 33 bits are refused"""
    for bits in [7, 33]:
        assert raises_value_error(lambda: find_collision(bits)), bits
A hint

Keep a dictionary from short hash to the message that gave it. For each new message, look its short hash up first: if it is there, you have found two messages; if not, store it and go on.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Hash Generator Compute SHA-256, SHA-3 and BLAKE2 digests of your own text and compare them with the values above.

Finish with practice over the four ideas: your weakest idea is asked first, each right answer leads to a harder question, and the ones you miss return at the end:

Practice that adapts to you

Practise this lesson

12 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 12 A database stores SHA-256 digests of random reset tokens instead of the tokens. Which property keeps a leaked table from giving back the tokens?

    Choose one answer.

    Show the answer: Question 1 of 12

    Answer: Preimage resistance

    Given only digests, finding inputs is the preimage problem, about 2²⁵⁶ work for SHA-256. Because the tokens are random and 128 bits or more, no list of likely inputs shortens it, which is why this works for tokens and not for passwords.

  2. Question 2 of 12 A service signs the SHA-256 digest of contracts that customers upload. Why must the hash be collision resistant, not only second-preimage resistant?

    Choose one answer.

    Show the answer: Question 2 of 12

    Answer: A customer chooses the contract, so they could prepare two that collide, get one signed, and present the other

    Signatures on digests need collision resistance whenever someone else can influence what is signed. That is why MD5 and SHA-1, both with practical collisions, are out of digital signatures.

  3. Question 3 of 12 A design cuts SHA-256 down to 128 bits to name uploaded files, so that equal files share one copy. Is 128 bits enough?

    Choose one answer.

    Show the answer: Question 3 of 12

    Answer: Not if users choose the files: collisions then take only about 2⁶⁴ work, under the 128-bit floor; keep 256 bits, as ASVS 11.4.3 asks where collisions matter

    Collision resistance is half the digest length. Where users choose inputs and a collision has consequences, keep at least 256 bits; ASVS 5.0 requirement 11.4.3 says so in as many words.

  4. Question 4 of 12 Which document specifies BLAKE2b and BLAKE2s?

    Choose one answer.

    Show the answer: Question 4 of 12

    Answer: RFC 7693

    SHA-2 lives in FIPS 180-4, SHA-3 and SHAKE in FIPS 202, and BLAKE2 in RFC 7693, which also notes that BLAKE2 can take a key directly and act as a MAC.

  5. Question 5 of 12 Your code computes SHA-256 of "abc" and gets a value other than ba7816bf … f20015ad. What is the likely cause?

    Choose one answer.

    Show the answer: Question 5 of 12

    Answer: Different bytes went in, such as a trailing newline, another encoding or a string turned into its hex text

    A known-answer test that fails nearly always points at the input: check the exact bytes, as the bytes lesson taught, before suspecting the hash.

  6. Question 6 of 12 A hash under test changes only 3 output bits when one input bit flips. Why would that make it unfit for security?

    Choose one answer.

    Show the answer: Question 6 of 12

    Answer: Small input changes would show in the output, so digests would leak how inputs relate, and near matches could be steered

    The avalanche effect is a basic sanity property: about half of the output bits flip for any one input bit. Structure in the output is a shortcut for an attacker that no digest length makes up for.

  7. Question 7 of 12 In Python 3.14, which call hashes a large file without reading all of it into memory at once?

    Choose one answer.

    Show the answer: Question 7 of 12

    Answer: hashlib.file_digest(f, "sha256") on a file opened with open(path, "rb")

    file_digest(), from Python 3.11, reads a binary file in blocking mode piece by piece; repeated update() calls on one hash object give the same digest.

  8. Question 8 of 12 How many 512-bit blocks does SHA-256 process for a 100-byte message?

    Type a number.

    A hint

    Padding adds at least one byte, 0x80, and the 8-byte length.

    Show the answer: Question 8 of 12

    Answer: 2 blocks

    100 bytes plus at least 1 byte of padding and 8 bytes of length make 109 bytes or more, which fits in two 64-byte blocks, 128 bytes, but not in one.

  9. Question 9 of 12 A partner signs webhooks with tag = SHA-256(secret + body), and sends the tag in a header. What is wrong, and what do you ask for?

    Choose one answer.

    Show the answer: Question 9 of 12

    Answer: Length extension: whoever saw one tag can compute a valid tag for the body with bytes appended; ask for HMAC-SHA-256

    SHA-256(secret ∥ message) is open to length extension because the digest is the internal state. HMAC, RFC 2104, is built to avoid exactly that, and the next module's lessons build on it.

  10. Question 10 of 12 What does RFC 6151 say about MD5?

    Choose one answer.

    Show the answer: Question 10 of 12

    Answer: It is unfit wherever collision resistance is needed, digital signatures included

    MD5 fails at collision resistance, so it is out wherever collisions matter. For MACs RFC 6151 suggests HMAC-SHA256 or AES-CMAC in new designs.

  11. Question 11 of 12 What is hashlib.md5(data, usedforsecurity=False) for?

    Choose one answer.

    Show the answer: Question 11 of 12

    Answer: Marking a non-security use, such as a cache key, so that a restricted build allows MD5 there

    usedforsecurity=False documents in code that a weak hash is used where nothing is defended. Every security use takes SHA-256 or longer instead.

  12. Question 12 of 12 An old service stores uploads under their SHA-1 digest and skips any upload whose digest it already has. What is the risk, and the fix?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: Two different files with the same SHA-1 digest exist, so one user's upload can be served for another's; key the store by SHA-256

    Identity by digest is a collision-resistance use. With SHA-1 broken for collisions, an attacker can upload one of a colliding pair and have its twin taken for it. SHA-256 restores 128 bits of collision resistance.

Interview questions

Warm-up (fresher to mid level): how do second-preimage resistance and collision resistance differ?

Both are about two inputs with the same digest, but the attacker starts from different places. For a second preimage, the first input is given, say a published release file, and the attacker must find a different input with the same digest; for an n-bit hash that takes about 2ⁿ work. For a collision the attacker may choose both inputs freely, and the birthday bound brings the work down to about 2^(n/2), so SHA-256 gives 128 bits of collision resistance and 256 of second-preimage resistance. Collision resistance matters whenever an attacker can influence what gets hashed and signed, as with certificates and documents; that is exactly where MD5 and SHA-1 failed. Collision resistance also implies second-preimage resistance, so a hash that has it covers both.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Senior to staff

    Your company signs firmware images by computing their SHA-1 digest and signing it with RSA; 200,000 devices in the field verify those signatures. Several partner teams submit images for signing. Assess the risk and plan the move to a safe hash without bricking devices.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to senior

    A design proposes document IDs made of the first 64 bits of the SHA-256 of each document's content, for up to a billion documents. Evaluate the chance of a clash, consider users who choose their documents, and recommend a length.

    Model answer, Pro. First step and follow-ups

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What are the three promises of a cryptographic hash, and what does each cost an attacker at n bits?

    Preimage resistance (given a digest, find an input) and second-preimage resistance (given an input, find a different one with the same digest) cost about 2^n hashes. Collision resistance (find any two inputs with one digest) costs about 2^(n/2), by the birthday bound. For SHA-256 that is 2^256, 2^256 and 2^128.

  2. Which promise does each use need: a stored hash of a reset token, a published file digest, a signature over a digest?

    A stored token hash needs preimage resistance, so a leaked table does not give back the tokens. A published file digest needs second-preimage resistance, so nobody can make another file to match. A signature needs collision resistance: someone who chooses the message could prepare two that collide, have one signed and reuse the signature.

  3. On SHA-256 cut down to 16 bits, how many hashes did a collision take compared with a preimage?

    About 388 on average for a collision (320 predicted), against 56,171 for a second preimage and 60,564 for a preimage, close to 2^16 = 65,536. ASVS 5.0 (11.4.3) turns this into a rule: an output of at least 256 bits where collision resistance is needed, at least 128 bits where only second preimages matter.

  4. Which functions make up SHA-2, SHA-3 and BLAKE2, and what is special about SHAKE and BLAKE2?

    SHA-2 (FIPS 180-4): SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224 and SHA-512/256. SHA-3 (FIPS 202): SHA3-224 to SHA3-512 and the extendable-output functions SHAKE128 and SHAKE256, which give as many bytes as you ask for. BLAKE2 (RFC 7693): BLAKE2b (1 to 64 bytes) and BLAKE2s (1 to 32), which can take a key and so also work as a MAC.

  5. What is the avalanche effect, and how large was it for SHA-256?

    Flipping one input bit flips about half of the output bits. Over 1,000 one-bit flips, 127.7 of the 256 bits changed on average, never fewer than 101 or more than 153. A digest that changed in only a few places would leak what changed in the input.

  6. How do you hash a large file in Web Crypto, Node.js and Python?

    Web Crypto's digest() takes one buffer and does not support streaming, so the whole input must be in memory. In Node.js, createHash() with update() takes it piece by piece, and crypto.hash() is the one-shot call. In Python repeated update() calls equal one call on the joined data, and hashlib.file_digest() (3.11 and later) reads a file for you.

  7. What is length extension, and why is SHA-256(secret ∥ message) not a MAC?

    A SHA-256 digest is the complete state after the last block, so anyone who knows a message's digest and length can go on hashing more blocks without knowing the message. A tag made as SHA-256(secret ∥ message) can therefore be extended to a longer message. HMAC, SHA-3 and SHA-512/256 are not open to it; use the language's HMAC.

  8. What is the status of MD5 and SHA-1, and where are they still met?

    RFC 6151 rules MD5 out wherever collision resistance is needed. SHA-1 has a public collision (two PDFs, found with 110 GPU-years and 6,500 CPU-years of work), and NIST has set the end of 2030 for phasing it out. Still met: download checksums (they catch damage, never a swap), old HMAC-SHA-1 protocols, and MD5 cache keys, where Python's usedforsecurity=False says nothing is defended. Never deduplicate uploads by SHA-1.

Key takeaways

  • A cryptographic hash gives preimage and second-preimage resistance up to about 2ⁿ work and collision resistance up to 2^(n/2), as the 16-bit searches measured: 388 hashes for a collision against about 60,000 for a preimage.
  • SHA-2 (FIPS 180-4), SHA-3 and SHAKE (FIPS 202) and BLAKE2 (RFC 7693) are current, and every implementation is checked against the published values, as the programs here matched NIST’s and the RFC’s.
  • Good hashes show the avalanche effect: one flipped input bit changed 127.7 of 256 output bits on average.
  • SHA-256 keeps a running state, so createHash().update() and hashlib hash large inputs in pieces while Web Crypto’s digest() needs the whole input; the same running state is why SHA-256(secret ∥ message) is not a MAC.
  • MD5 and SHA-1 are retired for security: RFC 6151 excludes MD5 where collisions matter, SHA-1 has a public collision, and NIST phases it out by the end of 2030.

Terms in this lesson

avalanche effect
The property that changing one bit of the input changes about half of the output bits, so that a digest gives no hint of how its input differs from another.
collision resistance
The property that finding any two different inputs with the same digest is infeasible. The birthday bound limits it to about 2^(n/2) work for an n-bit hash, 128 bits for SHA-256.
cryptographic hash function
A function that maps input of any length to a fixed-size digest, quickly, and resists preimages, second preimages and collisions. SHA-256, SHA-3 and BLAKE2 are current examples.
extendable-output function (XOF)
A hash function that gives as many output bytes as you ask for, such as SHAKE128 and SHAKE256 of FIPS 202.
length extension
Continuing a hash from a known digest without knowing the message, possible with SHA-256 and SHA-512 because their digest is the full internal state. It is why SHA-256(secret ∥ message) is not a MAC.
preimage resistance
The property that, given a digest, finding any input that hashes to it is infeasible: about 2^n work for an n-bit hash. Also called one-wayness.
second-preimage resistance
The property that, given one input, finding a different input with the same digest is infeasible: about 2^n work for an n-bit hash.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.