/** * The Whirlpool hashing function. * * <P> * <b>References</b> * * <P> * The Whirlpool algorithm was developed by * <a href="mailto:pbarreto@scopus.com.br">Paulo S. L. M. Barreto</a> and * <a href="mailto:vincent.rijmen@cryptomathic.com">Vincent Rijmen</a>. * * See * P.S.L.M. Barreto, V. Rijmen, * ``The Whirlpool hashing function,'' * First NESSIE workshop, 2000 (tweaked version, 2003), * <https://www.cosic.esat.kuleuven.ac.be/nessie/workshop/submissions/whirlpool.zip> * * @author Paulo S.L.M. Barreto * @author Vincent Rijmen. * * @version 3.0 (2003.03.12) * * ============================================================================= * * Differences from version 2.1: * * - Suboptimal diffusion matrix replaced by cir(1, 1, 4, 1, 8, 5, 2, 9). * * ============================================================================= * * Differences from version 2.0: * * - Generation of ISO/IEC 10118-3 test vectors. * - Bug fix: nonzero carry was ignored when tallying the data length * (this bug apparently only manifested itself when feeding data * in pieces rather than in a single chunk at once). * * Differences from version 1.0: * * - Original S-box replaced by the tweaked, hardware-efficient version. * * ============================================================================= * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package org.pokenet.client.network; import java.util.Arrays; public class Whirlpool { /** * The message digest size (in bits) */ public static final int DIGESTBITS = 512; /** * The message digest size (in bytes) */ public static final int DIGESTBYTES = DIGESTBITS >>> 3; /** * The number of rounds of the internal dedicated block cipher. */ protected static final int R = 10; /** * The substitution box. */ private static final String sbox = "\u1823\uc6E8\u87B8\u014F\u36A6\ud2F5\u796F\u9152" + "\u60Bc\u9B8E\uA30c\u7B35\u1dE0\ud7c2\u2E4B\uFE57" + "\u1577\u37E5\u9FF0\u4AdA\u58c9\u290A\uB1A0\u6B85" + "\uBd5d\u10F4\ucB3E\u0567\uE427\u418B\uA77d\u95d8" + "\uFBEE\u7c66\udd17\u479E\ucA2d\uBF07\uAd5A\u8333" + "\u6302\uAA71\uc819\u49d9\uF2E3\u5B88\u9A26\u32B0" + "\uE90F\ud580\uBEcd\u3448\uFF7A\u905F\u2068\u1AAE" + "\uB454\u9322\u64F1\u7312\u4008\uc3Ec\udBA1\u8d3d" + "\u9700\ucF2B\u7682\ud61B\uB5AF\u6A50\u45F3\u30EF" + "\u3F55\uA2EA\u65BA\u2Fc0\udE1c\uFd4d\u9275\u068A" + "\uB2E6\u0E1F\u62d4\uA896\uF9c5\u2559\u8472\u394c" + "\u5E78\u388c\ud1A5\uE261\uB321\u9c1E\u43c7\uFc04" + "\u5199\u6d0d\uFAdF\u7E24\u3BAB\ucE11\u8F4E\uB7EB" + "\u3c81\u94F7\uB913\u2cd3\uE76E\uc403\u5644\u7FA9" + "\u2ABB\uc153\udc0B\u9d6c\u3174\uF646\uAc89\u14E1" + "\u163A\u6909\u70B6\ud0Ed\ucc42\u98A4\u285c\uF886"; private static long[][] C = new long[8][256]; private static long[] rc = new long[R + 1]; static { for (int x = 0; x < 256; x++) { char c = sbox.charAt(x/2); long v1 = ((x & 1) == 0) ? c >>> 8 : c & 0xff; long v2 = v1 << 1; if (v2 >= 0x100L) { v2 ^= 0x11dL; } long v4 = v2 << 1; if (v4 >= 0x100L) { v4 ^= 0x11dL; } long v5 = v4 ^ v1; long v8 = v4 << 1; if (v8 >= 0x100L) { v8 ^= 0x11dL; } long v9 = v8 ^ v1; /* * build the circulant table C[0][x] = S[x].[1, 1, 4, 1, 8, 5, 2, 9]: */ C[0][x] = (v1 << 56) | (v1 << 48) | (v4 << 40) | (v1 << 32) | (v8 << 24) | (v5 << 16) | (v2 << 8) | (v9 ); /* * build the remaining circulant tables C[t][x] = C[0][x] rotr t */ for (int t = 1; t < 8; t++) { C[t][x] = (C[t - 1][x] >>> 8) | ((C[t - 1][x] << 56)); } } /* for (int t = 0; t < 8; t++) { System.out.println("static const u64 C" + t + "[256] = {"); for (int i = 0; i < 64; i++) { System.out.print(" "); for (int j = 0; j < 4; j++) { String v = Long.toHexString(C[t][4*i + j]); while (v.length() < 16) { v = "0" + v; } System.out.print(" LL(0x" + v + "),"); } System.out.println(); } System.out.println("};"); System.out.println(); } System.out.println(); //*/ /* * build the round constants: */ rc[0] = 0L; /* not used (assigment kept only to properly initialize all variables) */ for (int r = 1; r <= R; r++) { int i = 8*(r - 1); rc[r] = (C[0][i ] & 0xff00000000000000L) ^ (C[1][i + 1] & 0x00ff000000000000L) ^ (C[2][i + 2] & 0x0000ff0000000000L) ^ (C[3][i + 3] & 0x000000ff00000000L) ^ (C[4][i + 4] & 0x00000000ff000000L) ^ (C[5][i + 5] & 0x0000000000ff0000L) ^ (C[6][i + 6] & 0x000000000000ff00L) ^ (C[7][i + 7] & 0x00000000000000ffL); } /* System.out.println("static const u64 rc[R + 1] = {"); for (int r = 0; r <= R; r++) { String v = Long.toHexString(rc[r]); while (v.length() < 16) { v = "0" + v; } System.out.println(" LL(0x" + v + "),"); } System.out.println("};"); System.out.println(); //*/ } /** * Global number of hashed bits (256-bit counter). */ protected byte[] bitLength = new byte[32]; /** * Buffer of data to hash. */ protected byte[] buffer = new byte[64]; /** * Current number of bits on the buffer. */ protected int bufferBits = 0; /** * Current (possibly incomplete) byte slot on the buffer. */ protected int bufferPos = 0; /** * The hashing state. */ protected long[] hash = new long[8]; protected long[] K = new long[8]; // the round key protected long[] L = new long[8]; protected long[] block = new long[8]; // mu(buffer) protected long[] state = new long[8]; // the cipher state public Whirlpool() { } /** * The core Whirlpool transform. */ protected void processBuffer() { /* * map the buffer to a block: */ for (int i = 0, j = 0; i < 8; i++, j += 8) { block[i] = (((long)buffer[j ] ) << 56) ^ (((long)buffer[j + 1] & 0xffL) << 48) ^ (((long)buffer[j + 2] & 0xffL) << 40) ^ (((long)buffer[j + 3] & 0xffL) << 32) ^ (((long)buffer[j + 4] & 0xffL) << 24) ^ (((long)buffer[j + 5] & 0xffL) << 16) ^ (((long)buffer[j + 6] & 0xffL) << 8) ^ (((long)buffer[j + 7] & 0xffL) ); } /* * compute and apply K^0 to the cipher state: */ for (int i = 0; i < 8; i++) { state[i] = block[i] ^ (K[i] = hash[i]); } /* * iterate over all rounds: */ for (int r = 1; r <= R; r++) { /* * compute K^r from K^{r-1}: */ for (int i = 0; i < 8; i++) { L[i] = 0L; for (int t = 0, s = 56; t < 8; t++, s -= 8) { L[i] ^= C[t][(int)(K[(i - t) & 7] >>> s) & 0xff]; } } for (int i = 0; i < 8; i++) { K[i] = L[i]; } K[0] ^= rc[r]; /* * apply the r-th round transformation: */ for (int i = 0; i < 8; i++) { L[i] = K[i]; for (int t = 0, s = 56; t < 8; t++, s -= 8) { L[i] ^= C[t][(int)(state[(i - t) & 7] >>> s) & 0xff]; } } for (int i = 0; i < 8; i++) { state[i] = L[i]; } } /* * apply the Miyaguchi-Preneel compression function: */ for (int i = 0; i < 8; i++) { hash[i] ^= state[i] ^ block[i]; } } /** * Initialize the hashing state. */ public void NESSIEinit() { Arrays.fill(bitLength, (byte)0); bufferBits = bufferPos = 0; buffer[0] = 0; // it's only necessary to cleanup buffer[bufferPos]. Arrays.fill(hash, 0L); // initial value } /** * Delivers input data to the hashing algorithm. * * @param source plaintext data to hash. * @param sourceBits how many bits of plaintext to process. * * This method maintains the invariant: bufferBits < 512 */ public void NESSIEadd(byte[] source, long sourceBits) { /* sourcePos | +-------+-------+------- ||||||||||||||||||||| source +-------+-------+------- +-------+-------+-------+-------+-------+------- |||||||||||||||||||||| buffer +-------+-------+-------+-------+-------+------- | bufferPos */ int sourcePos = 0; // index of leftmost source byte containing data (1 to 8 bits). int sourceGap = (8 - ((int)sourceBits & 7)) & 7; // space on source[sourcePos]. int bufferRem = bufferBits & 7; // occupied bits on buffer[bufferPos]. int b; // tally the length of the added data: long value = sourceBits; for (int i = 31, carry = 0; i >= 0; i--) { carry += (bitLength[i] & 0xff) + ((int)value & 0xff); bitLength[i] = (byte)carry; carry >>>= 8; value >>>= 8; } // process data in chunks of 8 bits: while (sourceBits > 8) { // at least source[sourcePos] and source[sourcePos+1] contain data. // take a byte from the source: b = ((source[sourcePos] << sourceGap) & 0xff) | ((source[sourcePos + 1] & 0xff) >>> (8 - sourceGap)); if (b < 0 || b >= 256) { throw new RuntimeException("LOGIC ERROR"); } // process this byte: buffer[bufferPos++] |= b >>> bufferRem; bufferBits += 8 - bufferRem; // bufferBits = 8*bufferPos; if (bufferBits == 512) { // process data block: processBuffer(); // reset buffer: bufferBits = bufferPos = 0; } buffer[bufferPos] = (byte)((b << (8 - bufferRem)) & 0xff); bufferBits += bufferRem; // proceed to remaining data: sourceBits -= 8; sourcePos++; } // now 0 <= sourceBits <= 8; // furthermore, all data (if any is left) is in source[sourcePos]. if (sourceBits > 0) { b = (source[sourcePos] << sourceGap) & 0xff; // bits are left-justified on b. // process the remaining bits: buffer[bufferPos] |= b >>> bufferRem; } else { b = 0; } if (bufferRem + sourceBits < 8) { // all remaining data fits on buffer[bufferPos], and there still remains some space. bufferBits += sourceBits; } else { // buffer[bufferPos] is full: bufferPos++; bufferBits += 8 - bufferRem; // bufferBits = 8*bufferPos; sourceBits -= 8 - bufferRem; // now 0 <= sourceBits < 8; furthermore, all data is in source[sourcePos]. if (bufferBits == 512) { // process data block: processBuffer(); // reset buffer: bufferBits = bufferPos = 0; } buffer[bufferPos] = (byte)((b << (8 - bufferRem)) & 0xff); bufferBits += (int)sourceBits; } } /** * Get the hash value from the hashing state. * * This method uses the invariant: bufferBits < 512 */ public void NESSIEfinalize(byte[] digest) { // append a '1'-bit: buffer[bufferPos] |= 0x80 >>> (bufferBits & 7); bufferPos++; // all remaining bits on the current byte are set to zero. // pad with zero bits to complete 512N + 256 bits: if (bufferPos > 32) { while (bufferPos < 64) { buffer[bufferPos++] = 0; } // process data block: processBuffer(); // reset buffer: bufferPos = 0; } while (bufferPos < 32) { buffer[bufferPos++] = 0; } // append bit length of hashed data: System.arraycopy(bitLength, 0, buffer, 32, 32); // process data block: processBuffer(); // return the completed message digest: for (int i = 0, j = 0; i < 8; i++, j += 8) { long h = hash[i]; digest[j ] = (byte)(h >>> 56); digest[j + 1] = (byte)(h >>> 48); digest[j + 2] = (byte)(h >>> 40); digest[j + 3] = (byte)(h >>> 32); digest[j + 4] = (byte)(h >>> 24); digest[j + 5] = (byte)(h >>> 16); digest[j + 6] = (byte)(h >>> 8); digest[j + 7] = (byte)(h ); } } /** * Delivers string input data to the hashing algorithm. * * @param source plaintext data to hash (ASCII text string). * * This method maintains the invariant: bufferBits < 512 */ public void NESSIEadd(String source) { if (source.length() > 0) { byte[] data = new byte[source.length()]; for (int i = 0; i < source.length(); i++) { data[i] = (byte)source.charAt(i); } NESSIEadd(data, 8*data.length); } } private static String display(byte[] array) { char[] val = new char[2*array.length]; String hex = "0123456789ABCDEF"; for (int i = 0; i < array.length; i++) { int b = array[i] & 0xff; val[2*i] = hex.charAt(b >>> 4); val[2*i + 1] = hex.charAt(b & 15); } return String.valueOf(val); } private static final int LONG_ITERATION = 100000000; /** * Generate the NESSIE test vector set for Whirlpool. * * The test consists of: * 1. hashing all bit strings containing only zero bits * for all lengths from 0 to 1023; * 2. hashing all 512-bit strings containing a single set bit; * 3. the iterated hashing of the 512-bit string of zero bits a large number of times. */ public static void makeNESSIETestVectors() { Whirlpool w = new Whirlpool(); byte[] digest = new byte[64]; byte[] data = new byte[128]; Arrays.fill(data, (byte)0); System.out.println("Message digests of strings of 0-bits and length L:"); for (int i = 0; i < 1024; i++) { w.NESSIEinit(); w.NESSIEadd(data, i); w.NESSIEfinalize(digest); String s = Integer.toString(i); s = " ".substring(s.length()) + s; System.out.println(" L =" + s + ": " + display(digest)); } System.out.println("Message digests of all 512-bit strings S containing a single 1-bit:"); data = new byte[512/8]; Arrays.fill(data, (byte)0); for (int i = 0; i < 512; i++) { // set bit i: data[i/8] |= 0x80 >>> (i % 8); w.NESSIEinit(); w.NESSIEadd(data, 512); w.NESSIEfinalize(digest); System.out.println(" S = " + display(data) + ": " + display(digest)); // reset bit i: data[i/8] = 0; } for (int i = 0; i < digest.length; i++) { digest[i] = 0; } for (int i = 0; i < LONG_ITERATION; i++) { w.NESSIEinit(); w.NESSIEadd(digest, 512); w.NESSIEfinalize(digest); } System.out.println("Iterated message digest computation (" + LONG_ITERATION + " times): " + display(digest)); } /** * Generate the ISO/IEC 10118-3 test vector set for Whirlpool. */ public static void makeISOTestVectors() { Whirlpool w = new Whirlpool(); byte[] digest = new byte[DIGESTBYTES]; byte[] data = new byte[1000000]; Arrays.fill(data, (byte)0); System.out.println("1. In this example the data-string is the empty string, i.e. the string of length zero.\n"); w.NESSIEinit(); w.NESSIEfinalize(digest); System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); System.out.println("2. In this example the data-string consists of a single byte, namely the ASCII-coded version of the letter 'a'.\n"); w.NESSIEinit(); w.NESSIEadd("a"); w.NESSIEfinalize(digest); System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); System.out.println("3. In this example the data-string is the three-byte string consisting of the ASCII-coded version of 'abc'.\n"); w.NESSIEinit(); w.NESSIEadd("abc"); w.NESSIEfinalize(digest); System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); System.out.println("4. In this example the data-string is the 14-byte string consisting of the ASCII-coded version of 'message digest'.\n"); w.NESSIEinit(); w.NESSIEadd("message digest"); w.NESSIEfinalize(digest); System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); System.out.println("5. In this example the data-string is the 26-byte string consisting of the ASCII-coded version of 'abcdefghijklmnopqrstuvwxyz'.\n"); w.NESSIEinit(); w.NESSIEadd("abcdefghijklmnopqrstuvwxyz"); w.NESSIEfinalize(digest); System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); System.out.println("6. In this example the data-string is the 62-byte string consisting of the ASCII-coded version of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.\n"); w.NESSIEinit(); w.NESSIEadd("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); w.NESSIEfinalize(digest); System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); System.out.println("7. In this example the data-string is the 80-byte string consisting of the ASCII-coded version of eight repetitions of '1234567890'.\n"); w.NESSIEinit(); w.NESSIEadd("12345678901234567890123456789012345678901234567890123456789012345678901234567890"); w.NESSIEfinalize(digest); System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); System.out.println("8. In this example the data-string is the 32-byte string consisting of the ASCII-coded version of 'abcdbcdecdefdefgefghfghighijhijk'.\n"); w.NESSIEinit(); w.NESSIEadd("abcdbcdecdefdefgefghfghighijhijk"); w.NESSIEfinalize(digest); System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); Arrays.fill(data, (byte)'a'); System.out.println("9. In this example the data-string is the 1000000-byte string consisting of the ASCII-coded version of 'a' repeated 10^6 times.\n"); w.NESSIEinit(); w.NESSIEadd(data, 8*1000000); w.NESSIEfinalize(digest); System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); } public static void main(String[] args) { //makeNESSIETestVectors(); if (args[0].equals("-h")) { // Hash generator // initialize the hasher Whirlpool hasher = new Whirlpool(); hasher.NESSIEinit(); // add the plaintext argument to it hasher.NESSIEadd(args[1]); // create an array to hold the hashed bytes byte[] hashed = new byte[64]; // run the hash hasher.NESSIEfinalize(hashed); // this stuff basically turns the byte array into a hexstring java.math.BigInteger bi = new java.math.BigInteger(hashed); String hashedStr = bi.toString(16); // 120ff0 if (hashedStr.length() % 2 != 0) { // Pad with 0 hashedStr = "0"+hashedStr; } // send the hash to the server System.out.println("PG HASH STRING:\t\t" + hashedStr); System.out.println("DIGEST HASH STRING:\t" + display(hashed)); } else { makeISOTestVectors(); } } }