/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ /**<PRE> * SHA1.java - An implementation of the SHA-1 Algorithm * This version integrated into Freenet by Ian Clarke (02-02-2000) * (i.clarke@dynamicblue.com) from a previous public domain version by * Chuck McManis (cmcmanis@netcom.com) which was public domain * Tweaked by Mr.Tines<tines@windsong.demon.co.uk> for pegwit, June 1997 * - added method 'frob()' which wipes the contents, so as to match * the bizarre behaviour of Pegwit's double barreled hashing. * * Based on the C code that Steve Reid wrote his header * was : * SHA-1 in C * By Steve Reid <steve@edmweb.com> * 100% Public Domain * * Test Vectors (from FIPS PUB 180-1) * "abc" * A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D * "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" * 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 * A million repetitions of "a" * 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F </pre>*/ package freenet.crypt; import java.util.Random; import freenet.support.HexUtil; /** * This is a simple port of Steve Reid's SHA-1 code into Java. * I've run his test vectors through the code and they all pass. */ public final class SHA1 implements Digest { private static boolean alwaysThisOne = false; public static Digest getInstance(boolean needProgressive) { if(alwaysThisOne) needProgressive = true; if(needProgressive) return new SHA1(); else try { return new JavaSHA1(); } catch (java.security.NoSuchAlgorithmException e) { alwaysThisOne = true; return new SHA1(); } catch (Exception e) { return new SHA1(); } } public static Digest getInstance() { return getInstance(false); } public final int digestSize() { return 160; } private int state[] = new int[5]; private long count; private byte[] digestBits; private boolean NSA = true; /** * Retrieves the value of a hash. * @param digest int[] into which to place 5 elements * @param offset index of first of the 5 elements */ public final void extract(int[] digest, int offset) { for(int i=0; i<5; ++i) { digest[i+offset] = ((digestBits[4*i+0]<<24) & 0xFF000000) | ((digestBits[4*i+1]<<16) & 0x00FF0000) | ((digestBits[4*i+2]<< 8) & 0x0000FF00) | ((digestBits[4*i+3] ) & 0x000000FF); } } /** * Variant constructor * @param b true for SHA-1, false for the original SHA-0 */ public SHA1(boolean b) { count = 0; NSA = b; init(); } /** * Simple constructor for SHA-1 */ public SHA1() { this(true); } /* * The following array forms the basis for the transform * buffer. Update puts bytes into this buffer and then * transform adds it into the state of the digest. */ private int block[] = new int[16]; private int blockIndex; /* * These functions are taken out of #defines in Steve's * code. Java doesn't have a preprocessor so the first * step is to just promote them to real methods. * Later we can optimize them out into inline code, * note that by making them final some compilers will * inline them when given the -O flag. */ private final int rol(int value, int bits) { int q = (value << bits) | (value >>> (32 - bits)); return q; } private final int blk0(int i) { block[i] = (rol(block[i],24)&0xFF00FF00) | (rol(block[i],8)&0x00FF00FF); return block[i]; } private final int blk(int i) { block[i&15] = block[(i+13)&15]^block[(i+8)&15]^ block[(i+2)&15]^block[i&15]; if(NSA) { // this makes it SHA-1 block[i&15] = rol(block[i&15], 1); } return (block[i&15]); } private final void R0(int data[], int v, int w, int x , int y, int z, int i) { data[z] += ((data[w] & (data[x] ^ data[y] )) ^ data[y]) + blk0(i) + 0x5A827999 + rol(data[v] ,5); data[w] = rol(data[w], 30); } private final void R1(int data[], int v, int w, int x, int y, int z, int i) { data[z] += ((data[w] & (data[x] ^ data[y])) ^ data[y]) + blk(i) + 0x5A827999 + rol(data[v] ,5); data[w] = rol(data[w], 30); } private final void R2(int data[], int v, int w, int x, int y, int z, int i) { data[z] += (data[w] ^ data[x] ^ data[y]) + blk(i) + 0x6ED9EBA1 + rol(data[v] ,5); data[w] = rol(data[w], 30); } private final void R3(int data[], int v, int w, int x, int y, int z, int i) { data[z] += (((data[w] | data[x]) & data[y]) | (data[w] & data[x])) + blk(i) + 0x8F1BBCDC + rol(data[v] ,5); data[w] = rol(data[w], 30); } private final void R4(int data[], int v, int w, int x, int y, int z, int i) { data[z] += (data[w] ^ data[x] ^ data[y]) + blk(i) + 0xCA62C1D6 + rol(data[v] ,5); data[w] = rol(data[w], 30); } /* * Steve's original code and comments : * * blk0() and blk() perform the initial expand. * I got the idea of expanding during the round function from SSLeay * * #define blk0(i) block->l[i] * #define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ * ^block->l[(i+2)&15]^block->l[i&15],1)) * * (R0+R1), R2, R3, R4 are the different operations used in SHA1 * #define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); * #define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); * #define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); * #define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); * #define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); */ private int dd[] = new int[5]; /** * Hash a single 512-bit block. This is the core of the algorithm. * * Note that working with arrays is very inefficent in Java as it * does a class cast check each time you store into the array. * */ private final void transform() { /* Copy context->state[] to working vars */ dd[0] = state[0]; dd[1] = state[1]; dd[2] = state[2]; dd[3] = state[3]; dd[4] = state[4]; /* 4 rounds of 20 operations each. Loop unrolled. */ R0(dd,0,1,2,3,4, 0); R0(dd,4,0,1,2,3, 1); R0(dd,3,4,0,1,2, 2); R0(dd,2,3,4,0,1, 3); R0(dd,1,2,3,4,0, 4); R0(dd,0,1,2,3,4, 5); R0(dd,4,0,1,2,3, 6); R0(dd,3,4,0,1,2, 7); R0(dd,2,3,4,0,1, 8); R0(dd,1,2,3,4,0, 9); R0(dd,0,1,2,3,4,10); R0(dd,4,0,1,2,3,11); R0(dd,3,4,0,1,2,12); R0(dd,2,3,4,0,1,13); R0(dd,1,2,3,4,0,14); R0(dd,0,1,2,3,4,15); R1(dd,4,0,1,2,3,16); R1(dd,3,4,0,1,2,17); R1(dd,2,3,4,0,1,18); R1(dd,1,2,3,4,0,19); R2(dd,0,1,2,3,4,20); R2(dd,4,0,1,2,3,21); R2(dd,3,4,0,1,2,22); R2(dd,2,3,4,0,1,23); R2(dd,1,2,3,4,0,24); R2(dd,0,1,2,3,4,25); R2(dd,4,0,1,2,3,26); R2(dd,3,4,0,1,2,27); R2(dd,2,3,4,0,1,28); R2(dd,1,2,3,4,0,29); R2(dd,0,1,2,3,4,30); R2(dd,4,0,1,2,3,31); R2(dd,3,4,0,1,2,32); R2(dd,2,3,4,0,1,33); R2(dd,1,2,3,4,0,34); R2(dd,0,1,2,3,4,35); R2(dd,4,0,1,2,3,36); R2(dd,3,4,0,1,2,37); R2(dd,2,3,4,0,1,38); R2(dd,1,2,3,4,0,39); R3(dd,0,1,2,3,4,40); R3(dd,4,0,1,2,3,41); R3(dd,3,4,0,1,2,42); R3(dd,2,3,4,0,1,43); R3(dd,1,2,3,4,0,44); R3(dd,0,1,2,3,4,45); R3(dd,4,0,1,2,3,46); R3(dd,3,4,0,1,2,47); R3(dd,2,3,4,0,1,48); R3(dd,1,2,3,4,0,49); R3(dd,0,1,2,3,4,50); R3(dd,4,0,1,2,3,51); R3(dd,3,4,0,1,2,52); R3(dd,2,3,4,0,1,53); R3(dd,1,2,3,4,0,54); R3(dd,0,1,2,3,4,55); R3(dd,4,0,1,2,3,56); R3(dd,3,4,0,1,2,57); R3(dd,2,3,4,0,1,58); R3(dd,1,2,3,4,0,59); R4(dd,0,1,2,3,4,60); R4(dd,4,0,1,2,3,61); R4(dd,3,4,0,1,2,62); R4(dd,2,3,4,0,1,63); R4(dd,1,2,3,4,0,64); R4(dd,0,1,2,3,4,65); R4(dd,4,0,1,2,3,66); R4(dd,3,4,0,1,2,67); R4(dd,2,3,4,0,1,68); R4(dd,1,2,3,4,0,69); R4(dd,0,1,2,3,4,70); R4(dd,4,0,1,2,3,71); R4(dd,3,4,0,1,2,72); R4(dd,2,3,4,0,1,73); R4(dd,1,2,3,4,0,74); R4(dd,0,1,2,3,4,75); R4(dd,4,0,1,2,3,76); R4(dd,3,4,0,1,2,77); R4(dd,2,3,4,0,1,78); R4(dd,1,2,3,4,0,79); /* Add the working vars back into context.state[] */ state[0] += dd[0]; state[1] += dd[1]; state[2] += dd[2]; state[3] += dd[3]; state[4] += dd[4]; } /** * zero the count and state arrays; used to support * Pegwit's anomalous 2-barrel hashing */ public final void frob() { // Pegwit's little anomaly count=0; state[0] = state[1] = state[2] = state[3] = state[4] = 0; } /** * * Initializes new context */ protected final void init() { /* SHA1 initialization constants */ state[0] = 0x67452301; state[1] = 0xEFCDAB89; state[2] = 0x98BADCFE; state[3] = 0x10325476; state[4] = 0xC3D2E1F0; count = 0; digestBits = new byte[20]; blockIndex = 0; } /** * Add one byte to the digest. When this is implemented * all of the abstract class methods end up calling * this method for types other than bytes. * @param b byte to add */ public final void update(byte b) { int mask = (blockIndex & 3) << 3; count += 8; block[blockIndex >> 2] &= ~(0xff << mask); block[blockIndex >> 2] |= (b & 0xff) << mask; blockIndex++; if (blockIndex == 64) { transform(); blockIndex = 0; } } /** * Add many bytes to the digest. * @param data byte data to add * @param offset start byte * @param length number of bytes to hash */ public final void update(byte[] data, int offset, int length) { for (int i=0; i<length; ++i) update(data[offset+i]); } public final void update(byte[] data) { update(data, 0, data.length); } /** * Write completed digest into the given buffer. * @param reset If true, the hash function is reinitialized */ public final void digest(boolean reset, byte[] buffer, int offset) { finish(); System.arraycopy(digestBits, 0, buffer, offset, digestBits.length); if (reset) init(); } /** * Return completed digest. * @return the byte array result * @param reset If true, the hash function is reinitialized */ public final byte[] digest(boolean reset) { byte[] out = new byte[20]; digest(reset, out, 0); return out; } /** * Return completed digest. * @return the byte array result */ public final byte[] digest() { return digest(true); } /** * Complete processing on the message digest. */ protected final void finish() { byte bits[] = new byte[8]; int i; for (i = 0; i < 8; i++) { bits[i] = (byte)((count >>> (((7 - i) << 3))) & 0xff); } update((byte) 128); while (blockIndex != 56) update((byte) 0); // This should cause a transform to happen. for(i=0; i<8; ++i) update(bits[i]); for (i = 0; i < 20; i++) { digestBits[i] = (byte) ((state[i>>2] >>> ((3-(i & 3)) << 3) ) & 0xff); } } /** * Print out the digest in a form that can be easily compared * to the test vectors. */ protected String digout() { StringBuffer sb = new StringBuffer(); for (int i = 0; i < 20; i++) { char c1, c2; c1 = (char) ((digestBits[i] >>> 4) & 0xf); c2 = (char) (digestBits[i] & 0xf); c1 = (char) ((c1 > 9) ? 'A' + (c1 - 10) : '0' + c1); c2 = (char) ((c2 > 9) ? 'A' + (c2 - 10) : '0' + c2); sb.append(c1); sb.append(c2); } return sb.toString(); } /** * test driver */ public static void main(String[] args) { SHAselfTest(); SHAbenchTest(); } public static void SHAbenchTest() { try { JavaSHA1 s = new JavaSHA1(); genericBenchTest(s); try { JavaSHA1 s1 = (JavaSHA1)(s.clone()); System.err.println("Cloned successfully"); genericBenchTest(s1); } catch (CloneNotSupportedException e) { System.err.println("Couldn't clone"); } } catch (Exception e) { System.err.println("Couldn't run benchmark for JavaSHA1"); } SHA1 sha1 = new SHA1(true); genericBenchTest(sha1); } public static void genericBenchTest(Digest sha1) { System.out.println("\nBegin benchmark"); long size = 4096; long count = 100000; byte data[] = createDummyData((int)size); long startSameUpdate = System.currentTimeMillis(); for (int i = 0; i < count; i++) sha1.update(data); long endSameUpdate = System.currentTimeMillis(); long updateTime = endSameUpdate - startSameUpdate; long speed = 0; long eachTime = 0; if (updateTime > 0) { speed = size*count*1000 / updateTime; eachTime = count / updateTime; } System.out.println("SHA update " + count + " times for the same data block of size " + size + " bytes:"); System.out.println(updateTime + "ms [" + speed + "bytes/second, " + eachTime + "updates/ms]\n"); count = 1000; byte data2[][] = new byte[(int)count][]; for (int i = 0; i < count; i++) data2[i] = createDummyData((int)size); long differentStartTime = System.currentTimeMillis(); for (int i = 0; i < count; i++) sha1.update(data2[i]); long endDifferentUpdate = System.currentTimeMillis(); updateTime = endDifferentUpdate - differentStartTime; speed = 0; eachTime = 0; if (updateTime > 0) { speed = size*count*1000 / updateTime; eachTime = count / updateTime; } System.out.println("SHA update " + count + " times for different data blocks of size " + size + " bytes:"); System.out.println(updateTime + "ms [" + speed + "bytes/second, " + eachTime + "ms/update]"); System.out.println("\nEnd benchmark"); } private static byte[] createDummyData(int size) { Random rand = new Random(); byte buf[] = new byte[size]; rand.nextBytes(buf); return buf; } public static void SHAselfTest() { System.err.println("Testing JavaSHA1:"); try { JavaSHA1 js = new JavaSHA1(); SHAselfTest(js); } catch (Exception e) { System.err.println("Caught exception "+e+ " trying to test JavaSHA1"); } System.err.println("Testing SHA1:"); SHA1 s = new SHA1(true); SHAselfTest(s); } /** * perfoms a self-test - spits out the test vector outputs on System.out */ public static void SHAselfTest(Digest s) { int i; // This line may be safely deleted, its to make it easy to see // the output of the program. System.out.println("SHA-1 Test PROGRAM."); System.out.println("This code runs the test vectors through the code."); /* "abc" A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D */ System.out.println("First test is 'abc'"); String z = "abc"; //s.init(); s.update((byte) 'a'); s.update((byte) 'b'); s.update((byte) 'c'); System.out.println(HexUtil.bytesToHex(s.digest()).toUpperCase()); System.out.println("A9993E364706816ABA3E25717850C26C9CD0D89D"); /* "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 */ System.out.println("Next Test is 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'"); z = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; //s.init(); - initted by digest above for (i=0; i<z.length(); ++i) { s.update((byte) z.charAt(i)); } System.out.println(HexUtil.bytesToHex(s.digest()).toUpperCase()); System.out.println("84983E441C3BD26EBAAE4AA1F95129E5E54670F1"); /* A million repetitions of "a" 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F */ long startTime = 0 - System.currentTimeMillis(); System.out.println("Last test is 1 million 'a' characters."); //s.init(); - initted by digest above for (i = 0; i < 1000000; i++) s.update((byte) 'a'); //s.finish(); - digest() finishes System.out.println(HexUtil.bytesToHex(s.digest()).toUpperCase()); System.out.println("34AA973CD4C4DAA4F61EEB2BDBAD27316534016F"); startTime += System.currentTimeMillis(); double d = startTime/1000.0; System.out.println(" done, elapsed time = "+d); } }