package kornell.core.util; import java.util.ArrayList; /** * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as * defined in FIPS PUB 180-1 Version 2.1a Copyright Paul Johnston 2000 - * 2002. Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet * Distributed under the BSD License See http://pajhome.org.uk/crypt/md5 for * details. */ /** * translated into java from javascript by Brandon Donnelson Nov 2008 * Goal is to be able to use this both on the server and client side easily * * TODO Need to test more * TODO clean up comments and code * * @author Brandon Donnelson * */ public class Sha1 { /** * hex output format. 0 - lowercase; 1 - * uppercase */ private boolean hexcase = false; /** * base-64 pad character. "=" for strict RFC * compliance */ private String b64pad = ""; /** * bits per input character. 8 - ASCII; 16 - Unicode */ private int chrsz = 8; /** * constructor */ public Sha1() { } /** * get sha1 hash * * works * * @param s * @return */ public String hex_sha1(String s) { return binb2hex(core_sha1(str2binb(s), s.length() * chrsz)); } public String b64_sha1(String s) { return binb2b64(core_sha1(str2binb(s), s.length() * chrsz)); } public String str_sha1(String s) { return binb2str(core_sha1(str2binb(s), s.length() * chrsz)); } /** * get hexidemical sha1 hash - using salt * * @param key - salt * @param data - what to hash with the salt * @return */ public String hex_hmac_sha1(String key, String data) { return binb2hex(core_hmac_sha1(key, data)); } /** * get a base 64 sha1 hash - using salt * * @param key - salt * @param data - what to hash with the salt * @return */ public String b64_hmac_sha1(String key, String data) { String s = null; try { s = binb2b64(core_hmac_sha1(key, data)); } catch (Exception e) { e.printStackTrace(); } return s; } /** * get sha1 using some salt * * @param key - salt * @param data - what to hash with the salt * @return */ public String str_hmac_sha1(String key, String data) { return binb2str(core_hmac_sha1(key, data)); } /** * Perform a simple self-test to see if the VM is working * * working!!! yippie 11/15/08 * working!!! yahoo 11/23/08 * * @return */ public boolean test_Sha1() { String aa = hex_sha1("abc"); String bb = "a9993e364706816aba3e25717850c26c9cd0d89d"; if (aa.equals(bb)) { return true; } return false; } /** * create larger int array - especially for the sha-core method * * @param a - int[] array * @param size - create new array to this size * @param len - stick this value at the end of the array, which copies the javascript function * @return */ private int[] pad(int[] a, int size, int len) { // change to based on zero ordinal size++; if (a.length > size) { return a; } int[] c = new int[size]; for(int i=0; i < size; i++) { if (i < a.length) { c[i] = a[i]; } } if (len > 0) { c[c.length-1] = len; } return c; } /** * Calculate the SHA-1 of an array of big-endian words, and a bit length * * tested 11/15/08 - works, moving to next, then coming back to do more padding tests * tested 11/23/08 - works, padding works, sha signing works. * * @param x * @param len * @return */ private int[] core_sha1(int[] x, int len) { int key = len >> 5; x = pad(x, key, 0); // size the array if need be x[len >> 5] |= 0x80 << (24 - len % 32); int key2 = ((len + 64 >> 9) << 4) + 15; x = pad(x, key2, len); // size the array if need be int[] w = new int[80]; int a = 1732584193; int b = -271733879; int c = -1732584194; int d = 271733878; int e = -1009589776; for (int i = 0; i < x.length; i += 16) { int olda = a; int oldb = b; int oldc = c; int oldd = d; int olde = e; for (int j = 0; j < 80; j++) { if (j < 16) { w[j] = x[i + j]; } else { w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1); } int t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), safe_add(safe_add(e, w[j]), sha1_kt(j))); e = d; d = c; c = rol(b, 30); b = a; a = t; } a = safe_add(a, olda); b = safe_add(b, oldb); c = safe_add(c, oldc); d = safe_add(d, oldd); e = safe_add(e, olde); } // debug - for comparing the javascript results //System.out.println("a: " + a + ""); //System.out.println("b: " + b + ""); //System.out.println("c: " + c + ""); //System.out.println("d: " + d + ""); //System.out.println("e: " + e + ""); return new int[] { a, b, c, d, e }; } /** * Perform the appropriate triplet combination private for the current * iteration */ private int sha1_ft(int t, int b, int c, int d) { if (t < 20) return (b & c) | ((~b) & d); if (t < 40) return b ^ c ^ d; if (t < 60) return (b & c) | (b & d) | (c & d); return b ^ c ^ d; } /** * Determine the appropriate additive constant for the current iteration */ private int sha1_kt(int t) { return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514; } /** * Calculate the HMAC-SHA1 of a key and some data * * works * * Modified: had to add a array joining method * * @param key * @param data * @return */ private int[] core_hmac_sha1(String key, String data) { int[] bkey = str2binb(key); if (bkey.length > 16) { bkey = core_sha1(bkey, key.length() * chrsz); } // pad array if needed, so that loop below doesn't error if (bkey.length < 16) { bkey = pad(bkey, 15, 0); } int[] ipad = new int[16]; int[] opad = new int[16]; for (int i = 0; i < 16; i++) { ipad[i] = bkey[i] ^ 0x36363636; opad[i] = bkey[i] ^ 0x5C5C5C5C; } // modified with join method int[] hash = core_sha1(join(ipad,str2binb(data)), 512 + data.length() * chrsz); // modified with join method return core_sha1(join(opad,hash), 512 + 160); } /** * join two int[] arrays into one int[] array * * @param a int[] array 1 * @param b int[] array 2 * @return joined int[] array */ private int[] join(int[] a, int[] b) { int[] c = new int[a.length + b.length]; int index = 0; for(int i=0; i < a.length; i++) { c[index] = a[i]; index++; } for (int i=0; i< b.length; i++) { c[index] = b[i]; index++; } return c; } /** * Add integers, wrapping at 2^32. This uses 16-bit operations internally to * work around bugs in some JS interpreters. */ private int safe_add(int x, int y) { int lsw = (x & 0xFFFF) + (y & 0xFFFF); int msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); } /** * Bitwise rotate a 32-bit number to the left. */ private int rol(int num, int cnt) { return (num << cnt) | (num >>> (32 - cnt)); } /** * Convert an 8-bit or 16-bit string to an array of big-endian words In * 8-bit private, characters >255 have their hi-byte silently ignored. * * I had to modify the unicode and adding to an array. This was a big rewrite for this method * * @param str * @return */ private int[] str2binb(String str) { int mask = (1 << chrsz) - 1; int index = 0; int bin = 0; ArrayList<Integer> ar = new ArrayList<Integer>(); for (int i = 0; i < str.length() * chrsz; i += chrsz) { int pos = (i / chrsz) & mask; byte unicode = (byte) str.substring(pos, pos+1).charAt(0); int cal = unicode << (32 - chrsz - i % 32); if (i>>5 > index) { index++; ar.add(bin); bin = 0; } bin |= cal; // debug //System.out.println("i:" + i + " mask: " + mask + " pos:" + pos + " charUnicode:" + unicode + " cal:" + cal + " bin:" + bin); } ar.add(bin); int[] rtn = new int[ar.size()]; for (int i=0; i < ar.size(); i++) { rtn[i] = ar.get(i); } return rtn; } /** * Convert an array of big-endian words to a string * * @param bin * @return */ private String binb2str(int[] bin) { String str = ""; int mask = (1 << chrsz) - 1; for (int i = 0; i < bin.length * 32; i += chrsz) { str += (char) ((bin[i >> 5] >>> (32 - chrsz - i % 32)) & mask); } return str; } /** * Convert an array of big-endian words to a hex string. * * seems to be working 11/15/2008 * * @param binarray * @return */ private String binb2hex(int[] binarray) { String hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; //debug //System.out.println("hex_tab: " + hex_tab); String str = ""; for (int i = 0; i < binarray.length * 4; i++) { //int a = (binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF; //int b = (binarray[i >> 2] >> ((3 - i % 4) * 8)) & 0xF; char aa = hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF); char bb = hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8)) & 0xF); str += aa +""+ bb; //System.out.println(""+ aa + bb); // was //str += hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF) + hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8)) & 0xF); } // debug //System.out.println("binb2hex(): " + str); return str; } /** * Convert an array of big-endian words to a base-64 string */ private String binb2b64(int[] binarray) { String tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; String str = ""; // correct binnary array size, do we need a bigger array? int arlen = binarray.length; for (int i = 0; i < binarray.length * 4; i += 3) { int index1 = i >> 2; int index2 = i + 1 >> 2; int index3 = i + 2 >> 2; if (index1 > arlen) { arlen = index1; } if (index2 > arlen) { arlen = index1; } if (index3 > arlen) { arlen = index1; } } if (arlen >= binarray.length) { binarray = pad(binarray, arlen, 0); } for (int i = 0; i < binarray.length * 4; i += 3) { int triplet = (((binarray[i >> 2] >> 8 * (3 - i % 4)) & 0xFF) << 16) | (((binarray[i + 1 >> 2] >> 8 * (3 - (i + 1) % 4)) & 0xFF) << 8) | ((binarray[i + 2 >> 2] >> 8 * (3 - (i + 2) % 4)) & 0xFF); for (int j = 0; j < 4; j++) { if (i * 8 + j * 6 > binarray.length * 32) { str += b64pad; } else { str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3F); } } } return str; } }