↑ arvau

Wikipedia / Whirlpool (hash function)

In computer science and cryptography, Whirlpool (sometimes styled WHIRLPOOL) is a cryptographic hash function. It was designed by Vincent Rijmen (co-creator of the Advanced Encryption Standard) and Paulo S. L. M. Barreto, who first described it in 2000. It is named after the Whirlpool galaxy in Canes Venatici (M51, or NGC 5194), the first one recognized to have a spiral structure by William Parsons, third Earl of Rosse, in April 1845.

The hash has been recommended by the NESSIE project. It has also been adopted by the International Organization for Standardization (ISO) and the International Electrotechnical Commission (IEC) as part of the joint ISO/IEC 10118-3 international standard.

Design features

Whirlpool is a hash designed after the Square block cipher, and is considered to be in that family of block cipher functions.

Whirlpool is a Miyaguchi–Preneel construction based on a substantially modified Advanced Encryption Standard (AES).

Whirlpool takes a message of any length less than 2256 bits and returns a 512-bit message digest.

The authors have declared that

  1. "WHIRLPOOL is not (and will never be) patented. It may be used free of charge for any purpose."

Version changes

The original Whirlpool will be called Whirlpool-0, the first revision of Whirlpool will be called Whirlpool-T and the latest version will be called Whirlpool in the following test vectors.

  • In the first revision in 2001, the S-box was changed from a randomly generated one with good cryptographic properties to one which has better cryptographic properties and is easier to implement in hardware.

  • In the second revision (2003), a flaw in the diffusion matrix was found that lowered the estimated security of the algorithm below its potential. Changing the 8x8 rotating matrix constants from (1, 1, 3, 1, 5, 8, 9, 5) to (1, 1, 4, 1, 8, 5, 2, 9) solved this issue.

Internal structure

The Whirlpool hash function is a Merkle–Damgård construction based on an AES-like block cipher W in Miyaguchi–Preneel mode.

The block cipher of bytes, for a total of 512 bits.

.

SubBytes

The SubBytes operation applies a non-linear permutation (the S-box) to each byte of the state independently. The 8-bit S-box is composed of 3 smaller 4-bit S-boxes.

ShiftColumns

The ShiftColumns operation cyclically shifts each byte in each column of the state. Column j has its bytes shifted downwards by j positions.

MixBytesInRows

The MixBytesInRows operation is a right-multiplication of each row by an 8×8 matrix. The matrix is chosen such that the branch number (an important property when looking at resistance to differential cryptanalysis) is 9, which is maximal.

AddRoundKey

The AddRoundKey operation uses bitwise xor to add a key calculated by the key schedule to the current state. The key schedule is identical to the encryption itself, except the AddRoundKey function is replaced by an AddRoundConstant function that adds a predetermined constant in each round.

Complete process of the Whirlpool algorithm

Here is the detailed explanation of the Whirlpool algorithm as described in the official release paper.

First, let's define the notation used:

Whirlpool algorithm

that is to be hashed is first padded to ensure its length is a multiple of the block size (512 bits). This is done using the standard padding scheme defined in the ISO/IEC 10118-1 standard (the same used for md5, sha-2, and others):

  1. Append a '1' bit;

  2. );

  3. Append the original length of the message in bits using 256-bit big-endian format.

.

Whirlpool iterates the Miyaguchi-Preneel hashing scheme over these blocks [sections 3.11 and 3.12]:

Where:

  • is the string to matrix conversion function;

  • is the matrix to string conversion function;

  • is the initialization vector, a string of 512 0-bits;

  • is the Whirlpool cipher function.

Cipher W

[section 3.9] operates on and returns an 8x8 matrix:

Where:

  • );

  • is the nth key schedule;

  • is the round function.

into a sequence [section 3.8]:

Where:

  • is the rth round constant matrix.

The round constant for the rth, is a matrix, defined as:

Where:

  • is the S-box.

Round function ρ

[section 3.7] is defined as:

Where:

  • is the non-linear layer;

  • is the cyclical permutation;

  • is the diffusion layer;

  • is the key addition.

Non-linear layer γ (SubBytes)

consists of the parallel application of a non-linear substitution to each bytes of the argument independently [section 3.2]:

Cyclical permutation π (ShiftColumns)

The permutation positions [section 3.3]:

Diffusion layer θ (MixBytesInRows)

is a linear mapping based on the circular matrix [section 3.4]:

A circulant matrix is defined as a matrix where each row is a cyclic shift of the previous row [section 2.2]. Formally, a circulant matrix can be represented as:

For example, circulant matrix is the matrix:

Key addition σ (AddRoundKey)

consists of the bitwise XOR of a key matrix:

Substitution box S (S-Box)

is typically represented as a lookup table, where each input byte is mapped to a corresponding output byte. It can be computed using optimal diffusion mapping generation techniques[section 2.4] but here is a matrix representation of it:

Whirlpool hashes

The Whirlpool algorithm has undergone two revisions since its original 2000 specification.

People incorporating Whirlpool will most likely use the most recent revision of Whirlpool; while there are no known security weaknesses in earlier versions of Whirlpool, the most recent revision has better hardware implementation efficiency characteristics, and is also likely to be more secure. As mentioned earlier, it is also the version adopted in the ISO/IEC 10118-3 international standard.

The 512-bit (64-byte) Whirlpool hashes (also termed message digests) are typically represented as 128-digit hexadecimal numbers. The following demonstrates a 43-byte ASCII input (not including quotes) and the corresponding Whirlpool hashes:

Implementations

The authors provide reference implementations of the Whirlpool algorithm, including a version written in C and a version written in Java. These reference implementations have been released into the public domain.

Research on the security analysis of the Whirlpool function however, has revealed that on average, the introduction of 8 random faults is sufficient to compromise the 512-bit Whirlpool hash message being processed and the secret key of HMAC-Whirlpool within the context of Cloud of Things (CoTs). This emphasizes the need for increased security measures in its implementation.

Pseudo-code

Here is an implementation example of the standard Whirlpool algorithm:

S :=  0x18, 0x23, 0xc6, 0xe8, 0x87, 0xb8, 0x01, 0x4f, 0x36, 0xa6, 0xd2, 0xf5, 0x79, 0x6f, 0x91, 0x52, \
      0x60, 0xbc, 0x9b, 0x8e, 0xa3, 0x0c, 0x7b, 0x35, 0x1d, 0xe0, 0xd7, 0xc2, 0x2e, 0x4b, 0xfe, 0x57, \
      0x15, 0x77, 0x37, 0xe5, 0x9f, 0xf0, 0x4a, 0xda, 0x58, 0xc9, 0x29, 0x0a, 0xb1, 0xa0, 0x6b, 0x85, \
      0xbd, 0x5d, 0x10, 0xf4, 0xcb, 0x3e, 0x05, 0x67, 0xe4, 0x27, 0x41, 0x8b, 0xa7, 0x7d, 0x95, 0xd8, \
      0xfb, 0xee, 0x7c, 0x66, 0xdd, 0x17, 0x47, 0x9e, 0xca, 0x2d, 0xbf, 0x07, 0xad, 0x5a, 0x83, 0x33, \
      0x63, 0x02, 0xaa, 0x71, 0xc8, 0x19, 0x49, 0xd9, 0xf2, 0xe3, 0x5b, 0x88, 0x9a, 0x26, 0x32, 0xb0, \
      0xe9, 0x0f, 0xd5, 0x80, 0xbe, 0xcd, 0x34, 0x48, 0xff, 0x7a, 0x90, 0x5f, 0x20, 0x68, 0x1a, 0xae, \
      0xb4, 0x54, 0x93, 0x22, 0x64, 0xf1, 0x73, 0x12, 0x40, 0x08, 0xc3, 0xec, 0xdb, 0xa1, 0x8d, 0x3d, \
      0x97, 0x00, 0xcf, 0x2b, 0x76, 0x82, 0xd6, 0x1b, 0xb5, 0xaf, 0x6a, 0x50, 0x45, 0xf3, 0x30, 0xef, \
      0x3f, 0x55, 0xa2, 0xea, 0x65, 0xba, 0x2f, 0xc0, 0xde, 0x1c, 0xfd, 0x4d, 0x92, 0x75, 0x06, 0x8a, \
      0xb2, 0xe6, 0x0e, 0x1f, 0x62, 0xd4, 0xa8, 0x96, 0xf9, 0xc5, 0x25, 0x59, 0x84, 0x72, 0x39, 0x4c, \
      0x5e, 0x78, 0x38, 0x8c, 0xd1, 0xa5, 0xe2, 0x61, 0xb3, 0x21, 0x9c, 0x1e, 0x43, 0xc7, 0xfc, 0x04, \
      0x51, 0x99, 0x6d, 0x0d, 0xfa, 0xdf, 0x7e, 0x24, 0x3b, 0xab, 0xce, 0x11, 0x8f, 0x4e, 0xb7, 0xeb, \
      0x3c, 0x81, 0x94, 0xf7, 0xb9, 0x13, 0x2c, 0xd3, 0xe7, 0x6e, 0xc4, 0x03, 0x56, 0x44, 0x7f, 0xa9, \
      0x2a, 0xbb, 0xc1, 0x53, 0xdc, 0x0b, 0x9d, 0x6c, 0x31, 0x74, 0xf6, 0x46, 0xac, 0x89, 0x14, 0xe1, \
      0x16, 0x3a, 0x69, 0x09, 0x70, 0xb6, 0xd0, 0xed, 0xcc, 0x42, 0x98, 0xa4, 0x28, 0x5c, 0xf8, 0x86

C :=  0x01, 0x01, 0x04, 0x01, 0x08, 0x05, 0x02, 0x09, \
      0x09, 0x01, 0x01, 0x04, 0x01, 0x08, 0x05, 0x02, \
      0x02, 0x09, 0x01, 0x01, 0x04, 0x01, 0x08, 0x05, \
      0x05, 0x02, 0x09, 0x01, 0x01, 0x04, 0x01, 0x08, \
      0x08, 0x05, 0x02, 0x09, 0x01, 0x01, 0x04, 0x01, \
      0x01, 0x08, 0x05, 0x02, 0x09, 0x01, 0x01, 0x04, \
      0x04, 0x01, 0x08, 0x05, 0x02, 0x09, 0x01, 0x01, \
      0x01, 0x04, 0x01, 0x08, 0x05, 0x02, 0x09, 0x01

# Matrix built from initialization vector
IM := 0, 0, 0, 0, 0, 0, 0, 0, \
      0, 0, 0, 0, 0, 0, 0, 0, \
      0, 0, 0, 0, 0, 0, 0, 0, \
      0, 0, 0, 0, 0, 0, 0, 0, \
      0, 0, 0, 0, 0, 0, 0, 0, \
      0, 0, 0, 0, 0, 0, 0, 0, \
      0, 0, 0, 0, 0, 0, 0, 0, \
      0, 0, 0, 0, 0, 0, 0, 0

R :=  10

func getConstantRoundMatrix(r)
    cr := IM
    for j from 0 to 7
        cr[j] := S[8 * (r - 1) + j]
    endfor
    return cr
endfunc

func whirlpoolRound(matrix, key)
    # Apply the non-linear transformation γ
    for i from 0 to 7
        for j from 0 to 7
            matrix[i * 8 + j] = S[matrix[i * 8 + j]]
        endfor
    endfor

    # Apply cyclical permutation π
    tmp := matrix
    for i from 0 to 7
        for j from 0 to 7
            # '+ 8' to prevent negative indices
            matrix[i * 8 + j] = tmp[((i - j + 8) % 8) * 8 + j]
        endfor
    endfor
    matrix := tmp

    # Apply linear diffusion θ
    matrix := dotProduct(matrix, C)

    # Apply key addition σ[key]
    matrix := matrix xor key
    return matrix
endfunc

func whirlpool(M)
    m, t := pad(M)  # Returns (paddedMessageDividedInChunks, amountOfChunks)
    H := IM

    for i from 0 to t
        W := m[t]
        Kr := H

        W := W xor H
        for r from 1 to R
            cr := getConstantRoundMatrix(r)
            Kr := whirlpoolRound(Kr, cr)
            W := whirlpoolRound(W, Kr)
        endfor

        H := H xor W
        H := H xor m[t]
    endfor
    return matrixToHexString(H)
endfunc

, a matrix multiplication is required. Galois Field arithmetic can be used to write this multiplication algorithm:

func dotProduct(A, B)
    tmp: Matrix

    for i from 0 to 7
        for j from 0 to 7
            tmp[i * 8 + j] := 0

            for k from 0 to 7
                # Galois Field (2^8) multiplication
                a := A[i * 8 + k];
                b := B[k * 8 + j];
                product := 0;

                while b > 0
                    if b & 1 == 1
                        product := product xor a
                    endif
                    if a & 0x80 != 0
                        a := (a << 1) xor 0x11d  # x^8 + x^4 + x^3 + x^2 + 1
                    else
                        a := a << 1
                    endif
                    b := b >> 1
                endwhile
                tmp[i * 8 + j] := tmp[i * 8 + j] xor product
            endfor
        endfor
    endfor
    return tmp
endfunc

Here is an implementation of the 512-bit (64-bits size, big-endian) padding:

func pad(M)
    original_length := len(M)  # In bytes

    # 512 bits (total length) - 256 bits (size length) - 1 bit (padding bit)
    #  64 bytes               -  32 bytes              - 1 byte = 31 bytes
    padding := (31 - original_length) % 64
    padding := (padding + 64) % 64  # Avoid negative padding

    total_length := original_length + 1 + padding + 32  # In bytes
    padded: Byte[total_length]

    # Copy original message
    for i from 0 to original_length - 1
        padded[i] := M[i]
    endfor

    padded[original_length] := 0x80  # Append the '1' bit, then 7 '0' bits
    for i from original_length + 1 to original_length + padding
        padded[i] := 0x00  # Append 8 '0' bits
    endfor

    for i from 0 to 31
        padded[total_length - 32 + i] := (original_length * 8) >> (8 * (31 - i)) & 0xff
    endfor

    chunk_amount := total_length / 64
    divided := Byte[chunk_amount][64]
    for i from 0 to chunk_amount - 1
        for j from 0 to 63
            divided[i][j] := padded[i * 64 + j]
        endfor
    endfor
    return divided, chunk_amount
endfunc

And here is an example of a matrix-to-string conversion:

func matrixToHexString(matrix)
    HEX := "0123456789abcdef"
    result: Byte[128]
    for i from 0 to 63
        byte := matrix[i]
        result[i * 2] := HEX[byte >> 4]
        result[i * 2 + 1] := HEX[byte & 0xf]
    endfor
    return result
endfunc

Adoption

Two of the first widely used mainstream cryptographic programs that started using Whirlpool were FreeOTFE, followed by TrueCrypt in 2005.

VeraCrypt (a fork of TrueCrypt) included Whirlpool (the final version) as one of its supported hash algorithms.

See also

References