/*jshint bitwise:false*/ /*global unescape*/ /** * SparkMD5 is a fast md5 implementation of the MD5 algorithm. * This script is based in the JKM md5 library which is the * fastest algorithm around (see: http://jsperf.com/md5-shootout/7). * * NOTE: Please disable Firebug while testing this script! * Firebug consumes a lot of memory and CPU and slows down by a great margin. * Opera Dragonfly also slows down by a great margin. * Safari/Chrome developer tools seems not to slow it down. * * Improvements over the JKM md5 library: * * - Functionality wrapped in a closure * - Object oriented library * - Incremental md5 (see bellow) * - Validates using jslint * * Incremental md5 performs a lot better for hashing large ammounts of data, such as * files. One could read files in chunks, using the FileReader & Blob's, and append * each chunk for md5 hashing while keeping memory usage low. See example bellow. * * @example * * Normal usage: * * var hexHash = SparkMD5.hash('Hi there'); // hex hash * var rawHash = SparkMD5.hash('Hi there', true); // raw hash * * Incremental usage: * * var spark = new SparkMD5(); * spark.append('Hi'); * spark.append(' there'); * var hexHash = spark.end(); // hex hash * var rawHash = spark.end(true); // raw hash * * Hash a file incrementally: * * NOTE: If you test the code bellow using the file:// protocol in chrome you must start the browser with -allow-file-access-from-files argument. * Please see: http://code.google.com/p/chromium/issues/detail?id=60889 * * document.getElementById("file").addEventListener("change", function() { * * var fileReader = new FileReader(), * blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice, * file = document.getElementById("file").files[0], * chunkSize = 2097152, // read in chunks of 2MB * chunks = Math.ceil(file.size / chunkSize), * currentChunk = 0, * spark = new SparkMD5(); * * fileReader.onload = function(e) { * console.log("read chunk nr", currentChunk + 1, "of", chunks); * spark.appendBinary(e.target.result); // append binary string * currentChunk++; * * if (currentChunk < chunks) { * loadNext(); * } * else { * console.log("finished loading"); * console.info("computed hash", spark.end()); // compute hash * } * }; * * function loadNext() { * var start = currentChunk * chunkSize, * end = start + chunkSize >= file.size ? file.size : start + chunkSize; * * fileReader.readAsBinaryString(blobSlice.call(file, start, end)); * }; * * loadNext(); * }); * * @TODO: Add native support for reading files? Maybe add it as an extension? */ (function (factory) { if (typeof exports === 'object') { // Node/CommonJS module.exports = factory(); } else if (typeof define === 'function' && define.amd) { // AMD define('spark-md5', factory); } else { // Browser globals (with support for web workers) var glob; try { glob = window; } catch (e) { glob = self; } glob.SparkMD5 = factory(); } }(function (undefined) { 'use strict'; /* converts strings to array buffers * From: http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String */ var str2ab = function (str) { var buf = new ArrayBuffer(str.length * 2), // 2 bytes for each char bufView = new Uint16Array(buf); for (var i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); } return buf; }, abConcat = function(first, second) { var firstLength = first.length, result = new Uint8Array(firstLength + second.byteLength); result.set(first); result.set(new Uint8Array(second), firstLength); return result; }, //////////////////////////////////////////////////////////////////////////// /* * Fastest md5 implementation around (JKM md5) * Credits: Joseph Myers * * @see http://www.myersdaily.org/joseph/javascript/md5-text.html * @see http://jsperf.com/md5-shootout/7 */ add32 = function (a, b) { return (a + b) & 0xFFFFFFFF; }, cmn = function (q, a, b, x, s, t) { a = add32(add32(a, q), add32(x, t)); return add32((a << s) | (a >>> (32 - s)), b); }, ff = function(a, b, c, d, x, s, t) { return cmn((b & c) | ((~b) & d), a, b, x, s, t); }, gg = function(a, b, c, d, x, s, t) { return cmn((b & d) | (c & (~d)), a, b, x, s, t); }, hh = function(a, b, c, d, x, s, t) { return cmn(b ^ c ^ d, a, b, x, s, t); }, ii = function(a, b, c, d, x, s, t) { return cmn(c ^ (b | (~d)), a, b, x, s, t); }, md5cycle = function(x, k) { var a = x[0], b = x[1], c = x[2], d = x[3]; a = ff(a, b, c, d, k[0], 7, -680876936); d = ff(d, a, b, c, k[1], 12, -389564586); c = ff(c, d, a, b, k[2], 17, 606105819); b = ff(b, c, d, a, k[3], 22, -1044525330); a = ff(a, b, c, d, k[4], 7, -176418897); d = ff(d, a, b, c, k[5], 12, 1200080426); c = ff(c, d, a, b, k[6], 17, -1473231341); b = ff(b, c, d, a, k[7], 22, -45705983); a = ff(a, b, c, d, k[8], 7, 1770035416); d = ff(d, a, b, c, k[9], 12, -1958414417); c = ff(c, d, a, b, k[10], 17, -42063); b = ff(b, c, d, a, k[11], 22, -1990404162); a = ff(a, b, c, d, k[12], 7, 1804603682); d = ff(d, a, b, c, k[13], 12, -40341101); c = ff(c, d, a, b, k[14], 17, -1502002290); b = ff(b, c, d, a, k[15], 22, 1236535329); a = gg(a, b, c, d, k[1], 5, -165796510); d = gg(d, a, b, c, k[6], 9, -1069501632); c = gg(c, d, a, b, k[11], 14, 643717713); b = gg(b, c, d, a, k[0], 20, -373897302); a = gg(a, b, c, d, k[5], 5, -701558691); d = gg(d, a, b, c, k[10], 9, 38016083); c = gg(c, d, a, b, k[15], 14, -660478335); b = gg(b, c, d, a, k[4], 20, -405537848); a = gg(a, b, c, d, k[9], 5, 568446438); d = gg(d, a, b, c, k[14], 9, -1019803690); c = gg(c, d, a, b, k[3], 14, -187363961); b = gg(b, c, d, a, k[8], 20, 1163531501); a = gg(a, b, c, d, k[13], 5, -1444681467); d = gg(d, a, b, c, k[2], 9, -51403784); c = gg(c, d, a, b, k[7], 14, 1735328473); b = gg(b, c, d, a, k[12], 20, -1926607734); a = hh(a, b, c, d, k[5], 4, -378558); d = hh(d, a, b, c, k[8], 11, -2022574463); c = hh(c, d, a, b, k[11], 16, 1839030562); b = hh(b, c, d, a, k[14], 23, -35309556); a = hh(a, b, c, d, k[1], 4, -1530992060); d = hh(d, a, b, c, k[4], 11, 1272893353); c = hh(c, d, a, b, k[7], 16, -155497632); b = hh(b, c, d, a, k[10], 23, -1094730640); a = hh(a, b, c, d, k[13], 4, 681279174); d = hh(d, a, b, c, k[0], 11, -358537222); c = hh(c, d, a, b, k[3], 16, -722521979); b = hh(b, c, d, a, k[6], 23, 76029189); a = hh(a, b, c, d, k[9], 4, -640364487); d = hh(d, a, b, c, k[12], 11, -421815835); c = hh(c, d, a, b, k[15], 16, 530742520); b = hh(b, c, d, a, k[2], 23, -995338651); a = ii(a, b, c, d, k[0], 6, -198630844); d = ii(d, a, b, c, k[7], 10, 1126891415); c = ii(c, d, a, b, k[14], 15, -1416354905); b = ii(b, c, d, a, k[5], 21, -57434055); a = ii(a, b, c, d, k[12], 6, 1700485571); d = ii(d, a, b, c, k[3], 10, -1894986606); c = ii(c, d, a, b, k[10], 15, -1051523); b = ii(b, c, d, a, k[1], 21, -2054922799); a = ii(a, b, c, d, k[8], 6, 1873313359); d = ii(d, a, b, c, k[15], 10, -30611744); c = ii(c, d, a, b, k[6], 15, -1560198380); b = ii(b, c, d, a, k[13], 21, 1309151649); a = ii(a, b, c, d, k[4], 6, -145523070); d = ii(d, a, b, c, k[11], 10, -1120210379); c = ii(c, d, a, b, k[2], 15, 718787259); b = ii(b, c, d, a, k[9], 21, -343485551); x[0] = add32(a, x[0]); x[1] = add32(b, x[1]); x[2] = add32(c, x[2]); x[3] = add32(d, x[3]); }, md5blk = function(s) { /* I figured global was faster. */ var md5blks = [], i; /* Andy King said do it this way. */ for (i = 0; i < 64; i += 4) { md5blks[i >> 2] = s[i] + (s[i + 1] << 8) + (s[i + 2] << 16) + (s[i + 3] << 24); } return md5blks; }, md51 = function(s) { var n = s.length, state = [1732584193, -271733879, -1732584194, 271733878], i, length, tail, tmp, lo, hi; for (i = 64; i <= n; i += 64) { md5cycle(state, md5blk(s.subarray(i - 64, i))); } // // Not sure if it is a bug, however IE10 will always produce a sub array of length 1 // containing the last element of the parent array if the sub array specified starts // beyond the length of the parent array - weird. // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue // if((i - 64) < n) s = s.subarray(i - 64); else s = new Uint8Array(0); length = s.length; tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; for (i = 0; i < length; i += 1) { tail[i >> 2] |= s[i] << ((i % 4) << 3); } tail[i >> 2] |= 0x80 << ((i % 4) << 3); if (i > 55) { md5cycle(state, tail); for (i = 0; i < 16; i += 1) { tail[i] = 0; } } // Beware that the final length might not fit in 32 bits so we take care of that tmp = n * 8; tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); lo = parseInt(tmp[2], 16); hi = parseInt(tmp[1], 16) || 0; tail[14] = lo; tail[15] = hi; md5cycle(state, tail); return state; }, hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'], rhex = function(n) { var s = '', j; for (j = 0; j < 4; j += 1) { s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; } return s; }, hex = function(x) { var i; for (i = 0; i < x.length; i += 1) { x[i] = rhex(x[i]); } return x.join(''); }, md5 = function(s) { return hex(md51(s)); }, //////////////////////////////////////////////////////////////////////////// /** * SparkMD5 OOP implementation. * * Use this class to perform an incremental md5, otherwise use the * static methods instead. */ SparkMD5 = function () { // call reset to init the instance this.reset(); }; /** * Appends a string. * Converts to a byte array. * * @param {String} str The string to be appended * * @return {SparkMD5} The instance itself */ SparkMD5.prototype.append = function (str) { // convert to array then append as binary this.appendBinary(str2ab(str)); return this; }; /** * Appends a binary string. * * @param {ArrayBuffer} contents The binary string to be appended * * @return {SparkMD5} The instance itself */ SparkMD5.prototype.appendBinary = function (contents) { var buff = abConcat(this._buff, contents), length = buff.length, i; this._length += contents.byteLength; for (i = 64; i <= length; i += 64) { md5cycle(this._state, md5blk(buff.subarray(i - 64, i))); } // Avoids IE10 weirdness if((i - 64) < length) this._buff = buff.subarray(i - 64); else this._buff = new Uint8Array(0); return this; }; /** * Finishes the incremental computation, reseting the internal state and * returning the result. * Use the raw parameter to obtain the raw result instead of the hex one. * * @param {Boolean} raw True to get the raw result, false to get the hex result * * @return {String|Array} The result */ SparkMD5.prototype.end = function (raw) { var buff = this._buff, length = buff.length, tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], i, ret, tmp, lo, hi; for (i = 0; i < length; i += 1) { tail[i >> 2] |= buff[i] << ((i % 4) << 3); } tail[i >> 2] |= 0x80 << ((i % 4) << 3); if (i > 55) { md5cycle(this._state, tail); for (i = 0; i < 16; i += 1) { tail[i] = 0; } } // Do the final computation based on the tail and length // Beware that the final length may not fit in 32 bits so we take care of that tmp = this._length * 8; tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); lo = parseInt(tmp[2], 16); hi = parseInt(tmp[1], 16) || 0; tail[14] = lo; tail[15] = hi; md5cycle(this._state, tail); ret = !!raw ? this._state : hex(this._state); this.reset(); return ret; }; /** * Resets the internal state of the computation. * * @return {SparkMD5} The instance itself */ SparkMD5.prototype.reset = function () { this._buff = new Uint8Array(0); this._length = 0; this._state = [1732584193, -271733879, -1732584194, 271733878]; return this; }; /** * Releases memory used by the incremental buffer and other aditional * resources. If you plan to use the instance again, use reset instead. */ SparkMD5.prototype.destroy = function () { delete this._state; delete this._buff; delete this._length; }; /** * Performs the md5 hash on a string as utf16 * * @param {String} str The string * @param {Boolean} raw True to get the raw result, false to get the hex result * * @return {String|Array} The result */ SparkMD5.hash = function (str, raw) { var hash = md51(new Uint8Array(str2ab(str))); return !!raw ? hash : hex(hash); }; /** * Performs the md5 hash on a typed array. * * @param {ArrayBuffer} content The binary array * @param {Boolean} raw True to get the raw result, false to get the hex result * * @return {String|Array} The result */ SparkMD5.hashBinary = function (content, raw) { var hash = md51(new Uint8Array(content)); return !!raw ? hash : hex(hash); }; return SparkMD5; }));