diff -Nur fast-md5/build.xml fast-md5-new/build.xml --- fast-md5/build.xml 2010-08-19 14:49:44.000000000 +0200 +++ fast-md5-new/build.xml 2010-12-30 10:33:29.000000000 +0100 @@ -349,7 +349,13 @@ - + + + + This will continually test the distribution until an error occurs. A new seed will be printed at the start of each test. @@ -357,7 +363,8 @@ - diff -Nur fast-md5/src/com/twmacinta/util/MD5.java fast-md5-new/src/com/twmacinta/util/MD5.java --- fast-md5/src/com/twmacinta/util/MD5.java 2010-08-19 14:49:44.000000000 +0200 +++ fast-md5-new/src/com/twmacinta/util/MD5.java 2010-12-29 09:08:25.000000000 +0100 @@ -893,5 +893,19 @@ } return true; } + + /** + * Set stored state for resuming MD5 operation. + */ + public void setState(MD5State state) { + this.state = new MD5State(state); + this.finals = null; + } + /** + * @return copy current state of MD5 engine. Can be used for appending more data at later time. + */ + public MD5State getState() { + return new MD5State(state); + } } diff -Nur fast-md5/src/com/twmacinta/util/MD5State.java fast-md5-new/src/com/twmacinta/util/MD5State.java --- fast-md5/src/com/twmacinta/util/MD5State.java 2010-08-19 14:49:44.000000000 +0200 +++ fast-md5-new/src/com/twmacinta/util/MD5State.java 2010-12-30 10:20:38.000000000 +0100 @@ -1,5 +1,7 @@ package com.twmacinta.util; +import java.util.Arrays; + /** * Fast implementation of RSA's MD5 hash generator in Java JDK Beta-2 or higher
* Originally written by Santeri Paavolainen, Helsinki Finland 1996
@@ -34,14 +36,14 @@ * @author Timothy W Macinta (twm@alum.mit.edu) (optimizations and bug fixes) **/ -class MD5State { +public class MD5State { /** * 128-bit state */ int state[]; /** - * 64-bit character count + * 64-bit bytes counter (MD5 algorithm needs bits, we convert this value where needed) */ long count; @@ -50,11 +52,15 @@ */ byte buffer[]; + /** + * Default state corresponding to no digested data. + */ public MD5State() { buffer = new byte[64]; count = 0; state = new int[4]; + // Default values for MD5 algorithm state[0] = 0x67452301; state[1] = 0xefcdab89; state[2] = 0x98badcfe; @@ -76,4 +82,17 @@ this.count = from.count; } -}; + + public String toString() { + return "MD5State{state=[" + state[0] + ", " + state[1] + ", " + state[2] + ", " + state[3] + "], count=" + count + "}"; + } + + public boolean equals(Object obj) { + if (!(obj instanceof MD5State)) { + return false; + } + + MD5State other = (MD5State) obj; + return Arrays.equals(this.state, other.state) && this.count == other.count && Arrays.equals(this.buffer, other.buffer); + } +} diff -Nur fast-md5/src/com/twmacinta/util/MD5StateIO.java fast-md5-new/src/com/twmacinta/util/MD5StateIO.java --- fast-md5/src/com/twmacinta/util/MD5StateIO.java 1970-01-01 01:00:00.000000000 +0100 +++ fast-md5-new/src/com/twmacinta/util/MD5StateIO.java 2010-12-30 10:30:28.000000000 +0100 @@ -0,0 +1,101 @@ +package com.twmacinta.util; + +/** + * Fast implementation of RSA's MD5 hash generator in Java JDK Beta-2 or higher
+ * Originally written by Santeri Paavolainen, Helsinki Finland 1996
+ * (c) Santeri Paavolainen, Helsinki Finland 1996
+ * Some changes Copyright (c) 2002 Timothy W Macinta
+ *

+ * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + *

+ * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more + * details. + *

+ * You should have received a copy of the GNU Library General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + *

+ * See http://www.twmacinta.com/myjava/fast_md5.php for more information on this + * file. + *

+ * This class does serialization and deserialization of MD5State object. + * + * @author Peter Stibrany + **/ + +public class MD5StateIO { + /** + * @return deserialized MD5State from supplied bytes (created by + * {@link #serialize(MD5State))} method). + */ + public static MD5State deserialize(byte[] data) { + if (data.length != 88) { + throw new IllegalArgumentException("Illegal serialized data length"); + } + MD5State s = new MD5State(); + s.state[0] = readInt(data, 0); + s.state[1] = readInt(data, 4); + s.state[2] = readInt(data, 8); + s.state[3] = readInt(data, 12); + + s.count = readLong(data, 16); + + for (int i = 0; i < s.buffer.length; i++) { + s.buffer[i] = data[24 + i]; + } + return s; + } + + /** + * @return representation of this state serialized into 88-bytes array. + * WARNING: don't change this! + */ + public static byte[] serialize(MD5State state) { + byte[] result = new byte[88]; + writeInt(state.state[0], result, 0); + writeInt(state.state[1], result, 4); + writeInt(state.state[2], result, 8); + writeInt(state.state[3], result, 12); + writeLong(state.count, result, 16); + for (int i = 0; i < state.buffer.length; i++) { + result[24 + i] = state.buffer[i]; + } + + return result; + } + + private static void writeInt(int val, byte[] arr, int off) { + arr[off] = (byte) (val >> 24); + arr[off + 1] = (byte) (val >> 16); + arr[off + 2] = (byte) (val >> 8); + arr[off + 3] = (byte) val; + } + + private static int readInt(byte[] arr, int off) { + return arr[off] << 24 | (arr[off + 1] & 0xFF) << 16 + | (arr[off + 2] & 0xFF) << 8 | (arr[off + 3] & 0xFF); + } + + private static void writeLong(long val, byte[] arr, int off) { + arr[off] = (byte) (val >> 56); + arr[off + 1] = (byte) (val >> 48); + arr[off + 2] = (byte) (val >> 40); + arr[off + 3] = (byte) (val >> 32); + arr[off + 4] = (byte) (val >> 24); + arr[off + 5] = (byte) (val >> 16); + arr[off + 6] = (byte) (val >> 8); + arr[off + 7] = (byte) val; + } + + private static long readLong(byte[] arr, int off) { + return (arr[off] & 0xFFL) << 56 | (arr[off + 1] & 0xFFL) << 48 + | (arr[off + 2] & 0xFFL) << 40 | (arr[off + 3] & 0xFFL) << 32 + | (arr[off + 4] & 0xFFL) << 24 | (arr[off + 5] & 0xFFL) << 16 + | (arr[off + 6] & 0xFFL) << 8 | (arr[off + 7] & 0xFFL); + } +} diff -Nur fast-md5/src/com/twmacinta/util/test/MD5Test.java fast-md5-new/src/com/twmacinta/util/test/MD5Test.java --- fast-md5/src/com/twmacinta/util/test/MD5Test.java 1970-01-01 01:00:00.000000000 +0100 +++ fast-md5-new/src/com/twmacinta/util/test/MD5Test.java 2010-12-30 10:33:57.000000000 +0100 @@ -0,0 +1,158 @@ +package com.twmacinta.util.test; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Random; + +import com.twmacinta.util.MD5; +import com.twmacinta.util.MD5State; +import com.twmacinta.util.MD5StateIO; + +public class MD5Test { + public static void main(String[] args) throws Exception { + MD5Test t = new MD5Test(); + + System.out.println("Testing MD5 algorithm"); + t.runBasicTests(new SingleRunDigest()); + + System.out.println("Testing MD5 with resume"); + t.runBasicTests(new ResumableDigest()); + + System.out.println("Running MD5 on random data with default JDK MD5, Fast MD5 and Fast MD5 with resume"); + t.compareThreeMd5Implementations(); + + System.out.println("Testing state serialization/deserialization"); + t.testInitState(); + + System.out.println("Testing state serialization/deserialization after some hashing"); + t.testStateAfterUpdates(); + } + + protected void runBasicTests(Digest d) throws UnsupportedEncodingException { + assertEquals("d41d8cd98f00b204e9800998ecf8427e", runMD5(d, "")); + assertEquals("0cc175b9c0f1b6a831c399e269772661", runMD5(d, "a")); + assertEquals("900150983cd24fb0d6963f7d28e17f72", runMD5(d, "abc")); + assertEquals("f96b697d7cb7938d525a2f31aaf161d0", runMD5(d, "message digest")); + assertEquals("c3fcd3d76192e4007dfb496cca67e13b", runMD5(d, "abcdefghijklmnopqrstuvwxyz")); + assertEquals("d174ab98d277d9f5a5611c2c9f419d9f", runMD5(d, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")); + assertEquals("57edf4a22be3c955ac49da2e2107b67a", runMD5(d, "12345678901234567890123456789012345678901234567890123456789012345678901234567890")); + assertEquals("9e107d9d372bb6826bd81d3542a419d6", runMD5(d, "The quick brown fox jumps over the lazy dog")); + assertEquals("e4d909c290d0fb1ca068ffaddf22cbd0", runMD5(d, "The quick brown fox jumps over the lazy dog.")); + } + + public void compareThreeMd5Implementations() { + Digest d1 = new SunDigest(); + Digest d2 = new SingleRunDigest(); + Digest d3 = new ResumableDigest(); + + Random r = new Random(); + for (int i = 0; i < 32; i++) { + int len = r.nextInt(16*1024*1024); + + byte[] input = new byte[len]; + r.nextBytes(input); + + String r1 = runMD5(d1, input); + String r2 = runMD5(d2, input); + String r3 = runMD5(d3, input); + + if (!r1.equals(r2)) { + throw new IllegalStateException("MD5 checksums don't match: " + r1 + ", " + r2); + } + + if (!r2.equals(r3)) { + throw new IllegalStateException("MD5 checksums don't match: " + r2 + ", " + r3); + } + + System.out.println("* " + i + ": size: " + len + ", hashes: " + r1 + " " + r2 + " " + r3 + " OK"); + } + } + + String runMD5(Digest digest, String str) throws UnsupportedEncodingException { + return runMD5(digest, str.getBytes("ISO-8859-1")); + } + + String runMD5(Digest digest, byte[] data) { + byte[] result = digest.digest(data); + return MD5.asHex(result); + } + + static abstract class Digest { + abstract byte[] digest(byte[] data); + } + + static class SingleRunDigest extends Digest { + byte[] digest(byte[] data) { + MD5 md5 = new MD5(); + md5.Update(data); + return md5.Final(); + } + } + + static class ResumableDigest extends Digest { + byte[] digest(byte[] data) { + int rounds = 0; + Random r = new Random(); + + MD5State state = new MD5State(); + int remaining = data.length; + int ix = 0; + + while (remaining > 0) { + rounds ++; + int len = r.nextInt(remaining) + 1; + + MD5 md5 = new MD5(); + md5.setState(state); + md5.Update(data, ix, len); + + ix += len; + remaining -= len; + state = md5.getState(); + } + + MD5 md5 = new MD5(); + md5.setState(state); + return md5.Final(); + } + } + + static class SunDigest extends Digest { + byte[] digest(byte[] data) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + return digest.digest(data); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + } + + public void testInitState() throws Exception { + byte[] result = MD5StateIO.serialize(new MD5State()); + assertEquals(88, result.length); + assertEquals(new MD5State(), MD5StateIO.deserialize(result)); + } + + public void testStateAfterUpdates() throws Exception { + MD5 md5 = new MD5(); + md5.Update("Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...".getBytes("ISO-8859-1")); + + MD5State state = md5.getState(); + + assertEquals(state, MD5StateIO.deserialize(MD5StateIO.serialize(state))); + } + + public static void assertEquals(int expected, int actual) { + if (expected != actual) { + throw new IllegalStateException("Expected: " + expected + ", got: " + actual); + } + } + + public static void assertEquals(Object expected, Object actual) { + if (!expected.equals(actual)) { + throw new IllegalStateException("Expected: " + expected + ", got: " + actual); + } + } +}