UNPKG

jcrypto

Version:

JavaScript white-box cryptography tools.

282 lines (269 loc) 9.76 kB
var hmac = {}; hmac.blockSize = 16; /** * Calculate HMAC of meassge using SHA-256 hash function * @param {string} Message. * @param {object} Configuration object (read more at docs). * @returns {string} Hash value. */ hmac.hash = function(message, options) { if(typeof (options) !== 'object') { options = {}; } // Decode message if necessary switch (options.encoding) { case 'hex': message = hmac.h2s(message); break; default: message = hmac.utf8Encode(message); } // Using key padded w/ ipad var toHash = hmac.prepareMessage(message); var hashState = hmac.states[0].slice(); var hashBytes = hmac.hashBytes(toHash, hashState); // Using key padded w/ opad toHash = hmac.prepareMessage(hmac.a2s(hashBytes)); hashState = hmac.states[1].slice(); hashBytes = hmac.hashBytes(toHash, hashState); var hash = ''; // Encode hash value if necessary. Hex output is default switch (options.encoding) { case 'hex': hash = hmac.a2h(hashBytes); break; case 'binary': hash = hmac.a2s(hashBytes); break; default: hash = hmac.a2h(hashBytes); } return hash; }; /** * Convert string msg into 512-bit/hmac.blockSize-integer blocks arrays of ints [§5.2.1] * @param {string} Message * @returns {number[[]*hmac.blockSize]} Array of blocks each of which consits of hmac.blockSize integers. */ hmac.prepareMessage = function(msg) { // add trailing '1' bit (+ 0's padding) to string [§5.1.1] msg += String.fromCharCode(0x80); // length (in 32-bit integers) of padded msg // + 2 integers of appended length // + hmac.blockSize integers of already hashed HMAC key block var msgLen = (msg.length / 4) + 2; // number of hmac.blockSize-integer-blocks required to hold l ints var nBlocks = Math.ceil(msgLen / hmac.blockSize); var M = []; var i, j; for (i = 0; i < nBlocks; i++) { M[i] = []; // note running off the end of msg is ok 'cos bitwise ops on NaN return 0 for (j = 0; j < hmac.blockSize; j++) { // encode 4 chars per integer, big-endian encoding M[i][j] = (msg.charCodeAt((i * 64) + (j * 4)) << 24) | (msg.charCodeAt((i * 64) + (j * 4) + 1) << 16) | (msg.charCodeAt((i * 64) + (j * 4) + 2) << 8) | (msg.charCodeAt((i * 64) + (j * 4) + 3)); } } // add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1] // note: most significant word would be (len-1)*8 >>> 32, but since JS converts // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators var byteLen = msg.length + (hmac.blockSize * 4) - 1; M[nBlocks - 1][hmac.blockSize - 2] = (byteLen * 8) / Math.pow(2, 32); M[nBlocks - 1][hmac.blockSize - 2] = Math.floor(M[nBlocks - 1][hmac.blockSize - 2]); M[nBlocks - 1][hmac.blockSize - 1] = (byteLen * 8) & 0xffffffff; return M; }; /** * Hash a block of prepared bytes using SHA-256 [§6.1.2] * @param {number[[]*hmac.blockSize]} Array of integer blocks get from prepared message * @param {number[]} Hash state object. * @returns {number[]} Resulting hash state object. */ hmac.hashBytes = function(blocks, state) { var W = []; var a, b, c, d, e, f, g, h, i, j; var nBlocks = blocks.length; for (i = 0; i < nBlocks; i++) { // 1 - prepare message schedule 'W' for (j = 0; j < hmac.blockSize; j++) { W[j] = blocks[i][j]; } for (j = hmac.blockSize; j < 64; j++) { W[j] = (hmac.t1(W[j - 2]) + W[j - 7] + hmac.t0(W[j - 15]) + W[j - 16]); W[j] &= 0xffffffff; } // 2 - initialise working variables a, b, c, d, e, f, g, h with state values a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; f = state[5]; g = state[6]; h = state[7]; // 3 - main loop (note 'addition modulo 2^32') for (j = 0; j < 64; j++) { var T1 = h + hmac.s1(e) + hmac.ch(e, f, g) + hmac.K[j] + W[j]; var T2 = hmac.s0(a) + hmac.maj(a, b, c); h = g; g = f; f = e; e = (d + T1) & 0xffffffff; d = c; c = b; b = a; a = (T1 + T2) & 0xffffffff; } // 4 - compute the new intermediate hash value (note 'addition modulo 2^32') state[0] = (state[0] + a) & 0xffffffff; state[1] = (state[1] + b) & 0xffffffff; state[2] = (state[2] + c) & 0xffffffff; state[3] = (state[3] + d) & 0xffffffff; state[4] = (state[4] + e) & 0xffffffff; state[5] = (state[5] + f) & 0xffffffff; state[6] = (state[6] + g) & 0xffffffff; state[7] = (state[7] + h) & 0xffffffff; } return state; }; /** * Rotates right (circular right shift) value x by n positions [§3.2.4]. */ hmac.rotr = function(n, x) { return (x >>> n) | (x << (32 - n)); }; /** * Logical functions [§4.1.2]. */ hmac.s0 = function(x) { return hmac.rotr(2, x) ^ hmac.rotr(13, x) ^ hmac.rotr(22, x); }; hmac.s1 = function(x) { return hmac.rotr(6, x) ^ hmac.rotr(11, x) ^ hmac.rotr(25, x); }; hmac.t0 = function(x) { return hmac.rotr(7, x) ^ hmac.rotr(18, x) ^ (x >>> 3); }; hmac.t1 = function(x) { return hmac.rotr(17, x) ^ hmac.rotr(19, x) ^ (x >>> 10); }; hmac.ch = function(x, y, z) { return (x & y) ^ (~x & z); }; hmac.maj = function(x, y, z) { return (x & y) ^ (x & z) ^ (y & z); }; /** * Constants [§4.2.2] */ hmac.K = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ]; /** * Encode a string to UTF-8 * @param {string} Input string. * @throws {Error} Error on UTF-8 encode * @returns {string} Encoded string. */ hmac.utf8Encode = function(str) { try { var i, charcode, strLen, utf8Len; var res = '', utf8 = []; for (i = 0, strLen = str.length; i < strLen; i++) { charcode = str.charCodeAt(i); if (charcode < 0x80) { utf8.push(charcode); } else if (charcode < 0x800) { utf8.push(0xc0 | (charcode >> 6), 0x80 | (charcode & 0x3f)); } else if (charcode < 0xd800 || charcode >= 0xe000) { utf8.push(0xe0 | (charcode >> 12), 0x80 | ((charcode >> 6) & 0x3f), 0x80 | (charcode & 0x3f)); } else { i++; // UTF-16 encodes 0x10000-0x10FFFF by // subtracting 0x10000 and splitting the // 20 bits of 0x0-0xFFFFF into two halves charcode = 0x10000 + (((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)); utf8.push(0xf0 | (charcode >> 18), 0x80 | ((charcode >> 12) & 0x3f), 0x80 | ((charcode >> 6) & 0x3f), 0x80 | (charcode & 0x3f)); } } // Finally pack it to string for (i = 0, utf8Len = utf8.length; i < utf8Len; i++) { res += String.fromCharCode(utf8[i]); } return res; } catch(e) { throw new Error('Error on UTF-8 encode'); } }; /** * Transfrom integer into array of bytes */ hmac.i2b = function(n) { var bytes = [], i; for (i = 3; i >= 0; i--) { bytes[3 - i] = (n >>> (i * 8)) & 0xff; } return bytes; }; /** * Transfrom an array of integers into a string */ hmac.a2s = function(numArr) { var string = '', i, j, arrLen, block, bLen; for (i = 0, arrLen = numArr.length; i < arrLen; i++) { block = hmac.i2b(numArr[i]); for (j = 0, bLen = block.length; j < bLen; j++) { string += String.fromCharCode(block[j]); } } return string; }; /** * Transfrom an array of integers into a hex string */ hmac.a2h = function(numArr) { var str = '', i, block, j, arrLen, bLen; for (i = 0, arrLen = numArr.length; i < arrLen; i++) { block = hmac.i2b(numArr[i]); for (j = 0, bLen = block.length; j < bLen; j++) { str += (block[j] < 16 ? '0' : '') + block[j].toString(16); } } return str; }; /** * Decode string from hex representation * @throws {Error} Error on hex decode */ hmac.h2s = function(hexStr) { if (hexStr.length === 0) { return ''; } if (hexStr.length % 2 === 1) { throw Error('Odd-length string'); } var res = '', i; var hexCodes = hexStr.match(/[0-9a-f]{2}/gi); var len = hexCodes.length; for (i = 0; i < len; i++) { res += String.fromCharCode(parseInt(hexCodes[i], 16)); } return res; };