diff --git a/src/com/cypherx/xauth/Whirlpool.java b/src/com/cypherx/xauth/Whirlpool.java index b7df248..a249aa0 100644 --- a/src/com/cypherx/xauth/Whirlpool.java +++ b/src/com/cypherx/xauth/Whirlpool.java @@ -1,11 +1,82 @@ -package com.cypherx.xauth; +/** + * The Whirlpool hashing function. + * + *

+ * References + * + *

+ * The Whirlpool algorithm was developed by + * Paulo S. L. M. Barreto and + * Vincent Rijmen. + * + * See + * P.S.L.M. Barreto, V. Rijmen, + * ``The Whirlpool hashing function,'' + * First NESSIE workshop, 2000 (tweaked version, 2003), + * + * + * @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. + * + */ import java.util.Arrays; 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" + @@ -45,17 +116,43 @@ class Whirlpool { 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(); + //*/ - rc[0] = 0L; + /* + * 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] = @@ -68,21 +165,59 @@ class Whirlpool { (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]; + protected long[] K = new long[8]; // the round key protected long[] L = new long[8]; - protected long[] block = new long[8]; - protected long[] state = new long[8]; + protected long[] block = new long[8]; // mu(buffer) + protected long[] state = new long[8]; // the cipher state - public Whirlpool() {} + 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) ^ @@ -94,12 +229,19 @@ class Whirlpool { (((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) { @@ -110,7 +252,9 @@ class Whirlpool { 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) { @@ -121,24 +265,50 @@ class Whirlpool { 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; - Arrays.fill(hash, 0L); + 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) { - int sourcePos = 0; - int sourceGap = (8 - ((int)sourceBits & 7)) & 7; - int bufferRem = bufferBits & 7; + /* + 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); @@ -146,38 +316,51 @@ class Whirlpool { carry >>>= 8; value >>>= 8; } - - while (sourceBits > 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 - 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; + 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 - 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); @@ -185,21 +368,33 @@ class Whirlpool { } } + /** + * 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++; + 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); @@ -213,6 +408,13 @@ class Whirlpool { } } + /** + * 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()]; @@ -223,7 +425,7 @@ class Whirlpool { } } - static String display(byte[] array) { + private static String display(byte[] array) { char[] val = new char[2*array.length]; String hex = "0123456789ABCDEF"; for (int i = 0; i < array.length; i++) { @@ -233,4 +435,123 @@ class Whirlpool { } return String.valueOf(val); } -} \ No newline at end of file + + 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(); + makeISOTestVectors(); + } +}