An explanation of Whirlpool C implementation - or the general algorithm Note: Question originally asked on StackOverflow - was directed here

Anyone got a tutorial on the designers concept implementation of Whirlpool in C, or the Whirlpool algorithm in general? I find the source code hard to understand, mostly because I do not know anything about the algorithm. I am not a cryptographer, nor am I a good mathematician, so their documentation kind of went over my head...

The implementation and other documentation can be found here.

Initially I was just searching for a free (public domain) implementation to use in a project, but I figured I better know something about it as well... Currently I do not even know how to use it.
In the following, I try to minimize the background knowledge needed to understand the answer. If you need more details, I suggest taking a look at section 12.2 of Cryptography and Network Security Principles and Practices, Fourth Edition. (Though it requires a fair knowledge of crypto.)



First take a look at Merkle–Damgård construction. Virtually all hash functions follow such construction. Informally, it applies a compression function iteratively to reduce the input size to get some fixed-length output. For instance, you can hash a whole DVD (~ 4.3 GB) and get a 128-bit code.

Let $M$ be the input to an MD-based hash function. The MD construction appends a pad and the length of $M$ to it, so as to prevent several attacks.

Whirlpool uses a compression function named $W$. $W$ is similar to a block cipher named Rijndael, which is now standardized under the name AES (Advanced Encryption Standard). Rijndael has 3 variants: 128-bit, 192-bit, and 256-bit. The inventors of Whirlpool decided that no Rijndael variant is secure enough to be used as the compression function for a hash. Thus, $W$ is designed so as it accepts 512-bit inputs, and produces 512-bit outputs. The key size of $W$ is 512 bits as well.

Whirlpool works as follows. Let $M$ be the input. $M$ is divided into 512-bit segments: $M=(M_0,M_1,\ldots,M_t)$. Let $h_0$ be some initial value (fixed by Whirlpool standard). 

For $i=1,2,\ldots,t$, apply $W$ iteratively as follows:

$h_i = W(h_{i-1},m_i) \oplus h_{i-1} \oplus m_i$

where the first input to $W$ is a block-to-be-encrypted, and its second input is the encryption key. $\oplus$ denotes the XOR operation.

$h_t$ is considered as the output of the Whirlpool hash function.

The whole complexity lies in designing $W$. As pointed out before, it is similar to Rijndael, so you can understand it if you get familiar with Rijndael, on which I have designed a set of slides. The slides are self-contained and do not assume any math background beyond high school.