diff --git a/core/sha1.js b/core/sha1.js new file mode 100644 index 0000000..73d05b1 --- /dev/null +++ b/core/sha1.js @@ -0,0 +1,165 @@ +/** @fileOverview Javascript SHA-1 implementation. + * + * Based on the implementation in RFC 3174, method 1, and on the SJCL + * SHA-256 implementation. + * + * @author Quinn Slack + */ + +/** + * Context for a SHA-1 operation in progress. + * @constructor + * @class Secure Hash Algorithm, 160 bits. + */ +sjcl.hash.sha1 = function (hash) { + if (hash) { + this._h = hash._h.slice(0); + this._buffer = hash._buffer.slice(0); + this._length = hash._length; + } else { + this.reset(); + } +}; + +/** + * Hash a string or an array of words. + * @static + * @param {bitArray|String} data the data to hash. + * @return {bitArray} The hash value, an array of 5 big-endian words. + */ +sjcl.hash.sha1.hash = function (data) { + return (new sjcl.hash.sha1()).update(data).finalize(); +}; + +sjcl.hash.sha1.prototype = { + /** + * The hash's block size, in bits. + * @constant + */ + blockSize: 512, + + /** + * Reset the hash state. + * @return this + */ + reset:function () { + this._h = this._init.slice(0); + this._buffer = []; + this._length = 0; + return this; + }, + + /** + * Input several words to the hash. + * @param {bitArray|String} data the data to hash. + * @return this + */ + update: function (data) { + if (typeof data === "string") { + data = sjcl.codec.utf8String.toBits(data); + } + var i, b = this._buffer = sjcl.bitArray.concat(this._buffer, data), + ol = this._length, + nl = this._length = ol + sjcl.bitArray.bitLength(data); + for (i = this.blockSize+ol & -this.blockSize; i <= nl; + i+= this.blockSize) { + this._block(b.splice(0,16)); + } + return this; + }, + + /** + * Complete hashing and output the hash value. + * @return {bitArray} The hash value, an array of 5 big-endian words. TODO + */ + finalize:function () { + var i, b = this._buffer, h = this._h; + + // Round out and push the buffer + b = sjcl.bitArray.concat(b, [sjcl.bitArray.partial(1,1)]); + // Round out the buffer to a multiple of 16 words, less the 2 length words. + for (i = b.length + 2; i & 15; i++) { + b.push(0); + } + + // append the length + b.push(Math.floor(this._length / 0x100000000)); + b.push(this._length | 0); + + while (b.length) { + this._block(b.splice(0,16)); + } + + this.reset(); + return h; + }, + + /** + * The SHA-1 initialization vector. + * @private + */ + _init:[0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0], + + /** + * The SHA-1 hash key. + * @private + */ + _key:[0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6], + + /** + * The SHA-1 logical functions f(0), f(1), ..., f(79). + * @private + */ + _f:function(t, b, c, d) { + if (t <= 19) { + return (b & c) | (~b & d); + } else if (t <= 39) { + return b ^ c ^ d; + } else if (t <= 59) { + return (b & c) | (b & d) | (c & d); + } else if (t <= 79) { + return b ^ c ^ d; + } + }, + + /** + * Circular left-shift operator. + * @private + */ + _S:function(n, x) { + return (x << n) | (x >>> 32-n); + }, + + /** + * Perform one cycle of SHA-1. + * @param {bitArray} words one block of words. + * @private + */ + _block:function (words) { + var t, tmp, a, b, c, d, e, + w = words.slice(0), + h = this._h, + k = this._key; + + a = h[0]; b = h[1]; c = h[2]; d = h[3]; e = h[4]; + + for (t=0; t<=79; t++) { + if (t >= 16) { + w[t] = this._S(1, w[t-3] ^ w[t-8] ^ w[t-14] ^ w[t-16]); + } + tmp = (this._S(5, a) + this._f(t, b, c, d) + e + w[t] + + this._key[Math.floor(t/20)]) | 0; + e = d; + d = c; + c = this._S(30, b); + b = a; + a = tmp; + } + + h[0] = (h[0]+a) |0; + h[1] = (h[1]+b) |0; + h[2] = (h[2]+c) |0; + h[3] = (h[3]+d) |0; + h[4] = (h[4]+e) |0; + } +}; diff --git a/test/sha1_test.js b/test/sha1_test.js new file mode 100644 index 0000000..8ec38cd --- /dev/null +++ b/test/sha1_test.js @@ -0,0 +1,14 @@ +new sjcl.test.TestCase("SHA-1 from sha1sum", function (cb) { + if (!sjcl.hash.sha1) { + this.unimplemented(); + cb && cb(); + return; + } + + var i, kat=sjcl.test.vector.sha1, p=0, f=0; + for (i=0; i