Skip to main content
Cipher Visualizers home
Hash Functions Advanced

The Whirlpool Algorithm

Whirlpool is a 512-bit hash function built from an AES-like block cipher that works on an 8 by 8 matrix of bytes. Learn how its S-box, column shift, and diffusion matrix work, how the Miyaguchi-Preneel mode turns the cipher into a hash, and how it compares with SHA-2.

Pashalis Laoutaris 16 min read

Try it

Interactive Whirlpool Hash Visualizer

🔐 Whirlpool Hash

6
Enter text and click a button to start!
–

The Whirlpool Algorithm

Introduction

Most hash functions are built from scratch, with their own bit-twiddling rounds. Whirlpool takes a different path. It builds a hash from a block cipher, and the cipher looks a lot like AES. The state is a matrix of bytes, each round has an S-box, a shift, a mixing step, and a key addition, and the whole thing runs for 10 rounds.

The result is a hash with a 512-bit digest, which is 64 bytes or 128 hexadecimal digits. Whirlpool was designed by Vincent Rijmen, one of the two designers of AES, and Paulo Barreto. It is recommended by the NESSIE project and included in the ISO/IEC 10118-3 standard. If you know how AES works, much of this guide will feel familiar. The FAQ explains how Whirlpool relates to AES and what changed between its three versions.

Table of Contents

History and Standards

Rijmen and Barreto first described Whirlpool in 2000. The design has three versions. The first, now called Whirlpool-0, appeared in 2000. In 2001, the designers replaced the S-box with one that has better cryptographic properties and is easier to build in hardware. That version is called Whirlpool-T. In 2003, they changed the constants of the diffusion matrix to fix a weakness that lowered the estimated security. This final version is the one called Whirlpool today.

The NESSIE project recommended Whirlpool. ISO and IEC standardized it in ISO/IEC 10118-3, where the 2004 edition lists it as one of the dedicated hash functions, with RIPEMD-160 and the SHA-1 and SHA-2 family. The authors say that Whirlpool is not patented and that anyone can use it for any purpose. Its cipher is based on Square, an earlier design by Rijmen and Joan Daemen. AES is a descendant of Square as well.

From a Cipher to a Hash

A block cipher takes a key and a block. A hash has no key. Miyaguchi-Preneel mode closes that gap. It uses the chaining value as the key of the cipher and the message block as the plaintext:

H(0) = 0 (512 zero bits)
H(i) = W[ H(i-1) ]( m(i) )  xor  H(i-1)  xor  m(i)

Here W[K](m) is the cipher with key K applied to the block m. After the last block, H is the digest. The two XORs matter. Without them, an attacker could run the cipher backwards from a known output, and the function would not be one-way.

This is still a Merkle-Damgard construction. The message is cut into 64-byte blocks, each block updates the chaining value, and the chaining value is the whole output. Both the key and the block of the cipher are 512 bits, which is why Whirlpool needs a wide cipher.

Padding

Whirlpool pads like SHA-2, but with a longer length field:

  1. Append one byte 0x80, which is a one bit followed by zeros.
  2. Append zero bytes until the length is 32 bytes short of a multiple of 64.
  3. Append the original message length in bits, as a 32-byte big-endian number.

A message of 31 bytes or fewer needs one block. A message of 32 to 95 bytes needs two. The 256-bit length field means that a message can be nearly 2^256 bits long, far more than anyone could ever store.

The Cipher W

The cipher W has a 512-bit key, a 512-bit block, and 10 rounds. Both the key and the block are arranged as an 8 by 8 matrix of bytes. The 64 bytes fill the matrix row by row, so bytes 0 to 7 form row 0, bytes 8 to 15 form row 1, and so on.

One round has four operations, and each of them works on the whole matrix:

  1. SubBytes is called gamma by the designers. Every byte goes through the S-box.
  2. ShiftColumns is called pi by the designers. Column j is moved down by j places, with wrap-around.
  3. MixRows is called theta by the designers. Every row is multiplied by a fixed matrix over the field GF(2^8).
  4. AddRoundKey is called sigma by the designers. The matrix is XORed with the round key.

The cipher starts with a key addition, too. The initial state is the message block XORed with the key, which is the chaining value. Then it runs 10 rounds. The same round function, with a round constant instead of a key, also makes the round keys.

The S-box

The S-box replaces one byte by another. Whirlpool builds it from three small boxes that work on 4 bits, which makes it cheap in hardware:

E    = 1 B 9 C D 6 F 3 E 8 7 4 A 2 5 0
E^-1 = the inverse of E
R    = 7 C B D E 4 9 F 6 3 8 A 2 5 1 0

To compute S(x), split the byte into a high half u and a low half l, and then follow these steps:

u' = E(u)        l' = E^-1(l)
t  = R(u' xor l')
high half of S(x) = E(u' xor t)
low half of S(x)  = E^-1(l' xor t)

For x = 0x00 the walk is short. We get u' = 1 and l' = f, so t = R(u' xor l') = 1. The high half is E(u' xor t) = 1 and the low half is E^-1(l' xor t) = 8, so S(0x00) = 18. The first 16 entries of the S-box are 18 23 c6 e8 87 b8 01 4f 36 a6 d2 f5 79 6f 91 52.

Shift and Mix

ShiftColumns is simple, because the byte in row i and column j moves to row (i + j) mod 8. Column 0 stays, column 1 moves down by one place, column 2 moves down by two, and so on. This spreads the bytes of every row over all eight rows.

MixRows works on each row as a vector of 8 bytes. It multiplies the row by an 8 by 8 circulant matrix. The first row of the matrix is:

01 01 04 01 08 05 02 09

Every next row is the previous row shifted one place to the right, so the second row is 09 01 01 04 01 08 05 02. The multiplication is in GF(2^8), the field of AES, with one difference. Whirlpool reduces by the polynomial x^8 + x^4 + x^3 + x^2 + 1, which is 0x11D. Addition in the field is XOR. This matrix has the highest possible branch number, which means that a change in one byte of a row changes all eight bytes of the row after the mix, and the shift spreads those changes over all rows. After two rounds, every byte of the state depends on every byte of the input.

The Key Schedule

The key schedule is the cipher’s own round function. The first round key is the chaining value. Each next round key comes from the previous one:

K(r) = rho[ c(r) ]( K(r-1) )

That is, SubBytes, ShiftColumns, and MixRows, followed by an XOR with the round constant c(r). The constant for round r has zeros everywhere except in row 0, where it holds the eight S-box outputs S[8(r-1)] to S[8(r-1)+7]. For round 1 that is 1823c6e887b8014f, and for round 10 it is ca2dbf07ad5a8333. Because the S-box itself makes the constants, no extra numbers need to be stored.

Interactive Visualizer

The visualizer above runs this exact algorithm. Type any message of up to 95 bytes and click Hash. The log shows the padding, each block, and for every one of the 10 rounds, the round key and the state after each of the four operations. Every value is one row of the matrix, 8 bytes wide. When it ends, the page tells you how many of the 512 digest bits change when one bit of your message is flipped.

A Worked Example

This follows the message abc, which is one of the ISO test vectors.

Padding. The padded block is 64 bytes. Row 0 is 6162638000000000, which is 61 62 63 80 and zeros. Rows 1 to 6 are zero, and row 7 is 0000000000000018, where 0x18 is the length 24 bits. The chaining value is zero, so the first round key is zero, and the state is the block itself. Row 0 is 6162638000000000.

Round key 1. Applying the round function to 64 zero bytes first turns every byte into S(0) = 18. The shift changes nothing, because all bytes are equal. In the mix, the eight matrix entries XOR to 03, so every byte becomes 18 times 03, which is 28. Adding the constant 1823c6e887b8014f in row 0 gives K1 with row 0 equal to 300beec0af902967. Every other row stays 2828282828282828.

SubBytes. In row 0, the byte 61 becomes 0f, 62 becomes d5, 63 becomes 80, and 80 becomes 97. The zeros all become 18, so the row is 0fd5809718181818. Row 1 is 1818181818181818.

ShiftColumns. Row 0 of the new matrix keeps its first byte. The other bytes of the old row 0 move down to rows 1, 2, and 3, and 18 bytes arrive from the other rows. Row 0 becomes 0f18181818181818, and row 1 becomes 18d5181818181818.

MixRows. Take the row 0f18181818181818. Multiplying each input byte by its entry in the first column of the matrix gives the products 0f d8 30 78 c0 18 60 18. Their XOR is 3f, the first output byte. The whole row becomes 3f3f743f90630687.

AddRoundKey. XORing with K1 gives row 0 of the state after round 1, 0f349aff3ff32fe0.

After the fifth round, row 0 is 06a6ba1805548d33. After the tenth round, it is 2f462b24c6f486bb. The cipher output is then XORed with the chaining value (zero) and the message block. Row 0 of the block is 6162638000000000, which turns 2f462b24c6f486bb into 4e2448a4c6f486bb, the first eight bytes of the digest. The complete digest is:

4e2448a4c6f486bb16b6562c73b4020b
f3043e3a731bce721ae1b303d97e6d4c
7181eebdb6c57e277d0e34957114cbd6
c797fc9d95d8b582d225292076d4eef5

Avalanche. Flipping one bit of abc changes 233 of the 512 digest bits. The sentences The quick brown fox jumps over the lazy dog and ... lazy cog give two digests that differ in 257 bits. The empty message gives 19fa61d75522a4669b44e39c1d2e1726c530232130d407f89afee0964997f7a73e83be698b288febcf88e3e03c4f0757ea8964e59b63d93708b138cc42a66eb3.

Python Implementation

This is a complete Whirlpool. It follows the description above and keeps every step as its own small function. The function encrypt returns the state after each round, so you can print the steps shown above.

# whirlpool.py
#
# Whirlpool, designed by Vincent Rijmen and Paulo Barreto (final version, 2003).
# A 512-bit hash built from a dedicated block cipher, W, that works like AES on
# an 8 x 8 matrix of bytes, with 10 rounds. The cipher is plugged into the
# Miyaguchi-Preneel mode: the chaining value is the key, the message block is the
# plaintext, and the output is xored with both.

# the 4-bit mini boxes that build the 8-bit S-box
E = [0x1, 0xB, 0x9, 0xC, 0xD, 0x6, 0xF, 0x3, 0xE, 0x8, 0x7, 0x4, 0xA, 0x2, 0x5, 0x0]
E_INV = [E.index(i) for i in range(16)]
R = [0x7, 0xC, 0xB, 0xD, 0xE, 0x4, 0x9, 0xF, 0x6, 0x3, 0x8, 0xA, 0x2, 0x5, 0x1, 0x0]

# first row of the circulant diffusion matrix
MATRIX = [1, 1, 4, 1, 8, 5, 2, 9]

ROUNDS = 10


def sbox_entry(x):
    """Two E boxes, an E inverse box, and the R box, arranged like a small Feistel network."""
    u, l = E[x >> 4], E_INV[x & 15]
    w = R[u ^ l]
    return (E[u ^ w] << 4) | E_INV[l ^ w]


SBOX = [sbox_entry(x) for x in range(256)]


def gmul(a, b):
    """Multiply in GF(2^8) with the polynomial x^8 + x^4 + x^3 + x^2 + 1."""
    result = 0
    while b:
        if b & 1:
            result ^= a
        a <<= 1
        if a & 0x100:
            a ^= 0x11D
        b >>= 1
    return result


def sub_bytes(s):
    return [SBOX[x] for x in s]


def shift_columns(s):
    """Column j moves down by j places. The state is 8 rows of 8 bytes, stored row by row."""
    out = [0] * 64
    for i in range(8):
        for j in range(8):
            out[8 * ((i + j) % 8) + j] = s[8 * i + j]
    return out


def mix_rows(s):
    """Multiply every row by the circulant matrix: row k of the matrix is MATRIX rotated right by k."""
    out = []
    for i in range(8):
        for j in range(8):
            v = 0
            for k in range(8):
                v ^= gmul(s[8 * i + k], MATRIX[(j - k) % 8])
            out.append(v)
    return out


def xor(a, b):
    return [x ^ y for x, y in zip(a, b)]


def round_constant(r):
    """Round r (1 to 10): the first row is eight S-box outputs, all other rows are zero."""
    return SBOX[8 * (r - 1):8 * r] + [0] * 56


def round_function(s, key):
    return xor(mix_rows(shift_columns(sub_bytes(s))), key)


def encrypt(key, block):
    """The dedicated cipher W. Returns the state after each round, so the steps can be shown."""
    state = xor(block, key)
    states = [state]
    k = key
    for r in range(1, ROUNDS + 1):
        k = round_function(k, round_constant(r))
        state = round_function(state, k)
        states.append(state)
    return states


def pad(message):
    """Append a one bit, zeros up to 32 bytes short of a multiple of 64, and the 256-bit big-endian bit length."""
    padded = message + b"\x80" + bytes((31 - len(message)) % 64)
    return padded + (8 * len(message)).to_bytes(32, "big")


def whirlpool(message):
    h = [0] * 64
    padded = pad(message)
    for i in range(0, len(padded), 64):
        block = list(padded[i:i + 64])
        h = xor(xor(encrypt(h, block)[-1], h), block)
    return bytes(h)


if __name__ == "__main__":
    for text in (b"", b"abc", b"The quick brown fox jumps over the lazy dog"):
        print("Whirlpool(%r) = %s" % (text.decode(), whirlpool(text).hex()))

Running it produces this output:

Whirlpool('') = 19fa61d75522a4669b44e39c1d2e1726c530232130d407f89afee0964997f7a73e83be698b288febcf88e3e03c4f0757ea8964e59b63d93708b138cc42a66eb3
Whirlpool('abc') = 4e2448a4c6f486bb16b6562c73b4020bf3043e3a731bce721ae1b303d97e6d4c7181eebdb6c57e277d0e34957114cbd6c797fc9d95d8b582d225292076d4eef5
Whirlpool('The quick brown fox jumps over the lazy dog') = b97de512e91e3828b40d2b0fdce9ceb3c4a71f9bea8d88e75c4fa854df36725fd2b52eb6544edcacd6f8beddfea403cb55ae31f03ad62a5ef54e42ee82c3fb35

I checked this code against several independent sources before writing it up. The S-box and the diffusion matrix reproduce all 2,048 table entries and the 10 round constants of the public-domain reference code, as it appears in the Linux kernel. The digests match the NESSIE test vectors in the kernel, the ISO test vectors, which include one million repetitions of the letter a, and Botan’s test file, which has messages up to 4,096 bytes long. The code is not fast. Checking the million-letter vector in pure Python takes about half a minute.

Security Status

No practical attack on the full Whirlpool is known. The best published attacks on the hash reach only about half of the rounds. In 2009, Florian Mendel, Christian Rechberger, Martin Schläffer, and Søren Thomsen introduced the rebound attack. They found collisions for 4.5 rounds of the hash at a cost of about 2^120 compression calls, and near-collisions for 7.5 rounds of the compression function.

A longer journal version by Mario Lamberger and colleagues, published in 2015, goes further. It gives collisions for 5.5 rounds and near-collisions for 7.5 rounds of the hash function. For the compression function, where the attacker may choose the chaining value, it gives collisions for 7.5 rounds and near-collisions for 9.5 rounds. It also gives the first distinguishers for the full 10-round compression function and the full block cipher W. A distinguisher shows that the function does not behave like a random one in some respect. It is not a collision or a preimage, so it does not break the hash. These results say a lot about the design, but the real hash still has a large margin.

The digest has 512 bits, so a birthday search needs about 2^256 hashes to find a collision. That is a very large security margin. The weak points are practical, not mathematical. Whirlpool has no standing in modern protocols. Also, it is a Merkle-Damgard hash, so it allows length extension. Use HMAC when you need a keyed hash.

Whirlpool is not part of the NIST Secure Hash Standard. It appears in ISO/IEC 10118-3, and VeraCrypt supports it. New systems usually pick SHA-2, SHA-3, or BLAKE2.

FAQ

Both come from the same family of designs, and Vincent Rijmen helped to design both. Whirlpool has a byte matrix, an S-box, a mixing step, and key addition, like AES. The differences are the 8 by 8 size, the S-box made of mini boxes, the different diffusion matrix, and the use as a hash with Miyaguchi-Preneel.

What changed between the three versions?

Whirlpool-0 was the first version from 2000. Whirlpool-T, in 2001, replaced the S-box. The final Whirlpool, in 2003, changed the constants of the diffusion matrix from 1, 1, 3, 1, 5, 8, 9, 5 to 1, 1, 4, 1, 8, 5, 2, 9. Only the final version is standardized, so the digests in this guide belong to it.

Why is the digest 512 bits long?

The cipher has a 512-bit block, and Miyaguchi-Preneel mode gives an output of the same size. A longer digest costs time and storage, but it gives a large margin against collisions. Some libraries, such as the Linux kernel, also offer 256-bit and 384-bit truncations for systems that need a shorter value.

Is Whirlpool still used?

It remains an option in disk encryption software such as VeraCrypt, and in many libraries. It is not used in modern protocols such as TLS, where SHA-2 and SHA-3 are used instead.

Does Whirlpool resist length extension?

No, because the digest is the whole chaining value, so someone who knows a digest can continue hashing from it. Use HMAC-Whirlpool, or another keyed construction, to authenticate messages.

References

  1. Rijmen, V. and Barreto, P. “The WHIRLPOOL Hash Function.” Submission to the NESSIE project, 2001, with the final version of 2003.

  2. ISO/IEC 10118-3. “Information technology, Security techniques, Hash-functions, Part 3: Dedicated hash-functions.” 2004.

  3. Mendel, F., Rechberger, C., Schläffer, M., and Thomsen, S. “The Rebound Attack: Cryptanalysis of Reduced Whirlpool and Grøstl.” Fast Software Encryption (FSE) 2009, Lecture Notes in Computer Science 5665, Springer.

  4. Lamberger, M., Mendel, F., Rechberger, C., Rijmen, V., and Schläffer, M. “The Rebound Attack and Subspace Distinguishers: Application to Whirlpool.” Journal of Cryptology 28, 2015. Available at: https://eprint.iacr.org/2010/198

  5. Daemen, J. and Rijmen, V. “The Design of Rijndael.” Springer, 2002.

  6. NESSIE project. “New European Schemes for Signatures, Integrity and Encryption.” Available at: https://www.cosic.esat.kuleuven.be/nessie/

  7. Wikipedia. “Whirlpool (hash function).” Available at: https://en.wikipedia.org/wiki/Whirlpool_(hash_function)

Hash Functions Advanced

The Streebog Algorithm

Streebog, or GOST R 34.11-2012, is the Russian hash function standard. It hashes with a 12-round AES-like cipher, a length counter, and a checksum. Learn how its S-box, byte transposition, and linear map work, with every round of the RFC 6986 example checked.

19 min read

Hash Functions Intermediate

The RIPEMD-160 Algorithm

RIPEMD-160 is a 160-bit hash function that runs two independent lines of 80 steps over every block and then merges them. Learn how its padding, rounds, and combine step work, why Bitcoin still uses it, and how it compares with SHA-1.

18 min read

Hash Functions Advanced

The Skein Algorithm

Skein is a hash function built from the Threefish block cipher and a tweak that marks every block. It was one of the five finalists of the NIST SHA-3 competition. Learn how Unique Block Iteration, the configuration block, and the output stage work, with every digest checked.

18 min read