diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c4f7a0..1cfbcf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ * 0.7.4 - Add HAS-160 + - Add WHIRLPOOL, WHIRLPOOL-0, WHIRLPOOL-T * 0.7.3 - Add RIPEMD128, RIPEMD160, RIPEMD256, RIPEMD320 * 0.7.2 diff --git a/README.md b/README.md index 11fad7d..5ed95f1 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ * [SHA512 (SHA384)](https://tools.ietf.org/html/rfc4634) * [SHA512/256 (SHA512/224)](http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf) * [HAS-160](https://www.randombit.net/has160.html) +* [WHIRLPOOL (WHIRLPOOL-0, WHIRLPOOL-T)](http://www.larc.usp.br/~pbarreto/WhirlpoolPage.html) ### MAC * [HMAC](https://tools.ietf.org/html/rfc2104) @@ -70,6 +71,12 @@ var hash = CryptoApi.hash('sha512/256', 'test message', {}).stringify('hex'); var hash = CryptoApi.hash('has160', 'test message', {}).stringify('hex'); +var hash = CryptoApi.hash('whirlpool', 'test message', {}).stringify('hex'); + +var hash = CryptoApi.hash('whirlpool-0', 'test message', {}).stringify('hex'); + +var hash = CryptoApi.hash('whirlpool-t', 'test message', {}).stringify('hex'); + var hash_hmac = CryptoApi.mac('hmac', 'sha256', '', {}).update('test message') .finalize().stringify('hex'); ``` diff --git a/example/benchmark.html b/example/benchmark.html index 2c63e9b..7d6bcb2 100644 --- a/example/benchmark.html +++ b/example/benchmark.html @@ -19,6 +19,7 @@ + diff --git a/example/unit-tests.html b/example/unit-tests.html index 6af1bdf..8f28552 100644 --- a/example/unit-tests.html +++ b/example/unit-tests.html @@ -20,6 +20,7 @@ + \ No newline at end of file diff --git a/lib/crypto-api.js b/lib/crypto-api.js index 37a8209..b5c284c 100644 --- a/lib/crypto-api.js +++ b/lib/crypto-api.js @@ -532,6 +532,12 @@ case 'ripemd320': filename = 'ripemd160'; break; + case 'whirlpool-0': + filename = 'whirlpool'; + break; + case 'whirlpool-t': + filename = 'whirlpool'; + break; } require('./hasher.' + filename); Hasher = this.hashers[name]; @@ -665,6 +671,9 @@ * @returns {number} */ Tools.prototype.rotateRight64hi = function rotateRight64hi(hi, lo, n) { + if (n === 32) { + return lo; + } if (n > 32) { return this.rotateRight64hi(lo, hi, n - 32); } @@ -680,6 +689,9 @@ * @returns {number} */ Tools.prototype.rotateRight64lo = function rotateRight64lo(hi, lo, n) { + if (n === 32) { + return hi; + } if (n > 32) { return this.rotateRight64lo(lo, hi, n - 32); } diff --git a/lib/hasher.whirlpool.js b/lib/hasher.whirlpool.js new file mode 100644 index 0000000..4875ad2 --- /dev/null +++ b/lib/hasher.whirlpool.js @@ -0,0 +1,253 @@ +/*global require */ +(/** + * + * @param {CryptoApi} CryptoApi + * @returns {Whirlpool} + */ +function (CryptoApi) { + 'use strict'; + + // Transform constants + var SBOX = [ + 0x1823, 0xc6e8, 0x87b8, 0x014f, 0x36a6, 0xd2f5, 0x796f, 0x9152, + 0x60bc, 0x9b8e, 0xa30c, 0x7b35, 0x1de0, 0xd7c2, 0x2e4b, 0xfe57, + 0x1577, 0x37e5, 0x9ff0, 0x4ada, 0x58c9, 0x290a, 0xb1a0, 0x6b85, + 0xbd5d, 0x10f4, 0xcb3e, 0x0567, 0xe427, 0x418b, 0xa77d, 0x95d8, + 0xfbee, 0x7c66, 0xdd17, 0x479e, 0xca2d, 0xbf07, 0xad5a, 0x8333, + 0x6302, 0xaa71, 0xc819, 0x49d9, 0xf2e3, 0x5b88, 0x9a26, 0x32b0, + 0xe90f, 0xd580, 0xbecd, 0x3448, 0xff7a, 0x905f, 0x2068, 0x1aae, + 0xb454, 0x9322, 0x64f1, 0x7312, 0x4008, 0xc3ec, 0xdba1, 0x8d3d, + 0x9700, 0xcf2b, 0x7682, 0xd61b, 0xb5af, 0x6a50, 0x45f3, 0x30ef, + 0x3f55, 0xa2ea, 0x65ba, 0x2fc0, 0xde1c, 0xfd4d, 0x9275, 0x068a, + 0xb2e6, 0x0e1f, 0x62d4, 0xa896, 0xf9c5, 0x2559, 0x8472, 0x394c, + 0x5e78, 0x388c, 0xd1a5, 0xe261, 0xb321, 0x9c1e, 0x43c7, 0xfc04, + 0x5199, 0x6d0d, 0xfadf, 0x7e24, 0x3bab, 0xce11, 0x8f4e, 0xb7eb, + 0x3c81, 0x94f7, 0xb913, 0x2cd3, 0xe76e, 0xc403, 0x5644, 0x7fa9, + 0x2abb, 0xc153, 0xdc0b, 0x9d6c, 0x3174, 0xf646, 0xac89, 0x14e1, + 0x163a, 0x6909, 0x70b6, 0xd0ed, 0xcc42, 0x98a4, 0x285c, 0xf886 + ]; + var SBOX0 = [ + 0x68d0, 0xeb2b, 0x489d, 0x6ae4, 0xe3a3, 0x5681, 0x7df1, 0x859e, + 0x2c8e, 0x78ca, 0x17a9, 0x61d5, 0x5d0b, 0x8c3c, 0x7751, 0x2242, + 0x3f54, 0x4180, 0xcc86, 0xb318, 0x2e57, 0x0662, 0xf436, 0xd16b, + 0x1b65, 0x7510, 0xda49, 0x26f9, 0xcb66, 0xe7ba, 0xae50, 0x52ab, + 0x05f0, 0x0d73, 0x3b04, 0x20fe, 0xddf5, 0xb45f, 0x0ab5, 0xc0a0, + 0x71a5, 0x2d60, 0x7293, 0x3908, 0x8321, 0x5c87, 0xb1e0, 0x00c3, + 0x1291, 0x8a02, 0x1ce6, 0x45c2, 0xc4fd, 0xbf44, 0xa14c, 0x33c5, + 0x8423, 0x7cb0, 0x2515, 0x3569, 0xff94, 0x4d70, 0xa2af, 0xcdd6, + 0x6cb7, 0xf809, 0xf367, 0xa4ea, 0xecb6, 0xd4d2, 0x141e, 0xe124, + 0x38c6, 0xdb4b, 0x7a3a, 0xde5e, 0xdf95, 0xfcaa, 0xd7ce, 0x070f, + 0x3d58, 0x9a98, 0x9cf2, 0xa711, 0x7e8b, 0x4303, 0xe2dc, 0xe5b2, + 0x4ec7, 0x6de9, 0x2740, 0xd837, 0x928f, 0x011d, 0x533e, 0x59c1, + 0x4f32, 0x16fa, 0x74fb, 0x639f, 0x341a, 0x2a5a, 0x8dc9, 0xcff6, + 0x9028, 0x889b, 0x310e, 0xbd4a, 0xe896, 0xa60c, 0xc879, 0xbcbe, + 0xef6e, 0x4697, 0x5bed, 0x19d9, 0xac99, 0xa829, 0x641f, 0xad55, + 0x13bb, 0xf76f, 0xb947, 0x2fee, 0xb87b, 0x8930, 0xd37f, 0x7682 + ]; + var theta = [1, 1, 4, 1, 8, 5, 2, 9]; + var theta0 = [1, 1, 3, 1, 5, 8, 9, 5]; + var C = new Array(512); + var RC = new Array(22); + var C0 = new Array(512); + var RC0 = new Array(22); + var CT = new Array(512); + var RCT = new Array(22); + + function calculateRC(SBOX, theta) { + var C = new Array(512); + var RC = new Array(22); + for (var t = 0; t < 8; t++) { + C[t] = []; + } + for (var i = 0; i < 256; i++) { + var c = SBOX[(i / 2) | 0]; + var V = new Array(10); + V[1] = ((i & 1) === 0) ? c >>> 8 : c & 0xff; + V[2] = V[1] << 1; + if (V[2] >= 0x100) { + V[2] ^= 0x11d; + } + V[3] = V[2] ^ V[1]; + V[4] = V[2] << 1; + if (V[4] >= 0x100) { + V[4] ^= 0x11d; + } + V[5] = V[4] ^ V[1]; + V[8] = V[4] << 1; + if (V[8] >= 0x100) { + V[8] ^= 0x11d; + } + V[9] = V[8] ^ V[1]; + + // build the circulant table C[0][x] = S[x].[1, 1, 4, 1, 8, 5, 2, 9] | S[x].[1, 1, 3, 1, 5, 8, 9, 5] + C[0][i * 2] = (V[theta[0]] << 24) | (V[theta[1]] << 16) | (V[theta[2]] << 8) | V[theta[3]]; + C[0][i * 2 + 1] = (V[theta[4]] << 24) | (V[theta[5]] << 16) | (V[theta[6]] << 8) | V[theta[7]]; + + // build the remaining circulant tables C[t][x] = C[0][x] rotr t + for (t = 1; t < 8; t++) { + C[t][i * 2] = CryptoApi.Tools.rotateRight64lo(C[0][i * 2 + 1], C[0][i * 2], t * 8); + C[t][i * 2 + 1] = CryptoApi.Tools.rotateRight64hi(C[0][i * 2 + 1], C[0][i * 2], t * 8); + } + } + // build the round constants + RC[0] = 0; + RC[1] = 0; + for (i = 1; i <= 10; i++) { + RC[i * 2] = (C[0][16 * i - 16] & 0xff000000) ^ + (C[1][16 * i - 14] & 0x00ff0000) ^ + (C[2][16 * i - 12] & 0x0000ff00) ^ + (C[3][16 * i - 10] & 0x000000ff); + RC[i * 2 + 1] = (C[4][16 * i - 7] & 0xff000000) ^ + (C[5][16 * i - 5] & 0x00ff0000) ^ + (C[6][16 * i - 3] & 0x0000ff00) ^ + (C[7][16 * i - 1] & 0x000000ff); + } + + return [C, RC]; + } + + // Build transform tables + (function () { + // whirlpool-0 + var x = calculateRC(SBOX0, theta0); + C0 = x[0]; + RC0 = x[1]; + // whirlpool-t + x = calculateRC(SBOX, theta0); + CT = x[0]; + RCT = x[1]; + // whirlpool + x = calculateRC(SBOX, theta); + C = x[0]; + RC = x[1]; + })(); + + /** + * @class Whirlpool + * @desc Whirlpool hasher + * @implements HasherInterface + * @extends Hasher32be + * @param {string} name + * @param {Object} options + */ + var Whirlpool = function whirlpool(name, options) { + this.constructor(name, options); + }; + Whirlpool.prototype = Object.create(CryptoApi.Hasher32be.prototype); + /** + * @memberOf Whirlpool + * @constructor + */ + Whirlpool.prototype.constructor = function (name, options) { + CryptoApi.Hasher32be.prototype.constructor.call(this, name, options); + /** + * @desc Hash state + * @memberOf! Whirlpool# + * @alias state.hash + * @type {number[]} + */ + this.state.hash = new Array(16); + for (var i = 0; i < 16; i++) { + this.state.hash[i] = 0; + } + + if (this.name === 'whirlpool-0') { + this.C = C0; + this.RC = RC0; + } else if (this.name === 'whirlpool-t') { + this.C = CT; + this.RC = RCT; + } else { + this.C = C; + this.RC = RC; + } + }; + + /** + * @memberOf Whirlpool + * @method processBlock + * @param {number[]} M + */ + Whirlpool.prototype.processBlock = function processBlock(M) { + // compute and apply K^0 to the cipher state + var K = new Array(16); + var state = []; + for (var i = 0; i < 16; i++) { + state[i] = M[i] ^ (K[i] = this.state.hash[i]); + } + + // iterate over all rounds + var L = []; + for (var r = 1; r <= 10; r++) { + // compute K^r from K^{r-1} + for (i = 0; i < 8; i++) { + L[i * 2] = 0; + L[i * 2 + 1] = 0; + for (var t = 0, s = 56, j = 0; t < 8; t++, s -= 8, j = s < 32 ? 1 : 0) { + L[i * 2] ^= this.C[t][((K[((i - t) & 7) * 2 + j] >>> (s % 32)) & 0xff) * 2]; + L[i * 2 + 1] ^= this.C[t][((K[((i - t) & 7) * 2 + j] >>> (s % 32)) & 0xff) * 2 + 1]; + } + } + for (i = 0; i < 16; i++) { + K[i] = L[i]; + } + K[0] ^= this.RC[r * 2]; + K[1] ^= this.RC[r * 2 + 1]; + + // apply the r-th round transformation + for (i = 0; i < 8; i++) { + L[i * 2] = K[i * 2]; + L[i * 2 + 1] = K[i * 2 + 1]; + for (t = 0, s = 56, j = 0; t < 8; t++, s -= 8, j = s < 32 ? 1 : 0) { + L[i * 2] ^= this.C[t][((state[((i - t) & 7) * 2 + j] >>> (s % 32)) & 0xff) * 2]; + L[i * 2 + 1] ^= this.C[t][((state[((i - t) & 7) * 2 + j] >>> (s % 32)) & 0xff) * 2 + 1]; + } + } + for (i = 0; i < 16; i++) { + state[i] = L[i]; + } + } + // apply the Miyaguchi-Preneel compression function + for (i = 0; i < 16; i++) { + this.state.hash[i] ^= state[i] ^ M[i]; + } + }; + + /** + * @memberOf Whirlpool + * @method finalize + * @return {HashArray} hash + */ + Whirlpool.prototype.finalize = function finalize() { + /// Add padding + var padLen = this.state.message.length < 56 ? 56 - this.state.message.length : 120 - this.state.message.length; + this.state.message += "\x80"; + this.state.message += new Array(padLen).join("\x00"); + + // Add length + // @todo fix length to 64 bit + this.state.message += "\x00\x00\x00\x00"; + var lengthBits = this.state.length * 8; + for (var i = 3; i >= 0; i--) { + this.state.message += String.fromCharCode(lengthBits >> (8 * i)); + } + this.process(); + + var hash = []; + for (var k = 0, l = this.state.hash.length; k < l; k++) { + for (var j = 3; j >= 0; j--) { + hash.push((this.state.hash[k] >> 8 * j) & 0xFF); + } + } + + // Return hash + return CryptoApi.hashArray(hash); + }; + + CryptoApi.Hashers.add('whirlpool-0', Whirlpool); + CryptoApi.Hashers.add('whirlpool-t', Whirlpool); + CryptoApi.Hashers.add('whirlpool', Whirlpool); + return Whirlpool; +})( + this.CryptoApi || require('./crypto-api') +); \ No newline at end of file diff --git a/package.json b/package.json index 44b07da..7b2f6f6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "crypto-api", "description": "Hashing and encrypting library with no depedencies", - "version": "0.7.3", + "version": "0.7.4", "homepage": "https://github.com/nf404/crypto-api/", "author": { "name": "Aleksandr Mogilnikov", @@ -70,6 +70,9 @@ "sha512", "sha512/224", "sha512/256", + "whirlpool", + "whirlpool-0", + "whirlpool-t", "hmac" ] } \ No newline at end of file diff --git a/test/index.html b/test/index.html index 12e2c35..d7d82bd 100644 --- a/test/index.html +++ b/test/index.html @@ -24,6 +24,7 @@ + diff --git a/test/test-vectors/hash.js b/test/test-vectors/hash.js index 4909f40..ef72864 100644 --- a/test/test-vectors/hash.js +++ b/test/test-vectors/hash.js @@ -506,6 +506,102 @@ message: new Array(9).join('1234567890'), hash: '07f05c8c0773c55ca3a5a695ce6aca4c438911b5' } + }, + // The WHIRLPOOL-0 test suite + // https://www.cosic.esat.kuleuven.be/nessie/testvectors/hash/whirlpool/Whirlpool-Orig-512.verified.test-vectors + 'whirlpool-0': { + "whirlpool-0('')": { + message: '', + hash: 'b3e1ab6eaf640a34f784593f2074416accd3b8e62c620175fca0997b1ba2347339aa0d79e754c308209ea36811dfa40c1c32f1a2b9004725d987d3635165d3c8' + }, + "whirlpool-0('a')": { + message: 'a', + hash: 'f4b620445ae62431dbd6dbcec64d2a3031cd2f48df5e755f30b3d069929ed4b4eda0ae65441bc86746021fb7f2167f84d67566efaba003f0abb67a42a2ce5b13' + }, + "whirlpool-0('abc')": { + message: 'abc', + hash: '54ee18b0bbd4dd38a211699f2829793156e5842df502a2a25995c6c541f28cc050ff57d4af772dee7cedcc4c34c3b8ec06446c6657f2f36c2c06464399879b86' + }, + "whirlpool-0('message digest')": { + message: 'message digest', + hash: '29e158ba336ce7f930115178a6c86019f0f413adb283d8f0798af06ca0a06d6d6f295a333b1c24bda2f429ac918a3748aef90f7a2c8bfb084d5f979cf4e7b2b5' + }, + "whirlpool-0('a..z')": { + message: 'abcdefghijklmnopqrstuvwxyz', + hash: '5ac9757e1407432daf348a972b8ad4a65c1123cf1f9b779c1ae7ee2d540f30b3cefa8f98dca5fbb42084c5c2f161a7b40eb6b4a1fc7f9aaab92a4bb6002edc5e' + }, + "whirlpool-0('A..Za..z0..9')": { + message: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', + hash: 'cae4175f09753de84974cfa968621092fe41ee9de913919c2b452e6cb424056721d640e563f628f29dd3bd0030837ae4ac14aa17308505a92e5f7a92f112be75' + }, + "whirlpool-0('1234567890' x 8)": { + message: new Array(9).join('1234567890'), + hash: 'e5965b4565b041a0d459610e5e48e944c4830cd16feba02d9d263e7da8de6a6b88966709bf28a5328d928312e7a172da4cff72fe6de02277dae4b1dba49689a2' + } + }, + // The WHIRLPOOL-T test suite + // https://www.cosic.esat.kuleuven.be/nessie/testvectors/hash/whirlpool/Whirlpool-Tweak-512.verified.test-vectors + 'whirlpool-t': { + "whirlpool-t('')": { + message: '', + hash: '470f0409abaa446e49667d4ebe12a14387cedbd10dd17b8243cad550a089dc0feea7aa40f6c2aaab71c6ebd076e43c7cfca0ad32567897dcb5969861049a0f5a' + }, + "whirlpool-t('a')": { + message: 'a', + hash: 'b290e0e7931025ed37043ad568f0036b40e6bff8f7455868780f47ef7b5d693e62448029a9351cd85ac29cb0725e4cfeb996a92f2b8da8768483ac58ec0e492c' + }, + "whirlpool-t('abc')": { + message: 'abc', + hash: '8afc0527dcc0a19623860ef2369d0e25de8ebe2abaa40f598afaf6b07c002ed73e4fc0fc220fd4f54f74b5d6b07aa57764c3dbdcc2cdd919d89fa8155a34b841' + }, + "whirlpool-t('message digest')": { + message: 'message digest', + hash: '817eadf8efca5afbc11f71d0814e03a8d569c90f748c8603597a7a0de3c8d55f528199010218249517b58b14bee523515608754b53a3cca35c0865ba5e361431' + }, + "whirlpool-t('a..z')": { + message: 'abcdefghijklmnopqrstuvwxyz', + hash: '4afc2b07bddc8417635fcb43e695e16f45e116c226dd84339eb95c2ccb39e7acbe1af8f7b1f3bd380077e71929498bc968200371f9299015434d1df109a0aa1d' + }, + "whirlpool-t('A..Za..z0..9')": { + message: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', + hash: '0f960ec9ab7d0c7e355a423d1ef4911a39797c836a71414276afeb8fa475dba0c348547143162f3212edf1fb8d8c652a11a579a399c2dbd837fe8608f5096131' + }, + "whirlpool-t('1234567890' x 8)": { + message: new Array(9).join('1234567890'), + hash: '6ae43784c69d01c273bba40f8411495167909e0c1acc241473d44e27bc8641e646535d38fce20604941988c387c201cff199c8fa2afbedd036d66202892a7eee' + } + }, + // The WHIRLPOOL test suite + // http://www.larc.usp.br/~pbarreto/WhirlpoolPage.html + 'whirlpool': { + "whirlpool('')": { + message: '', + hash: '19fa61d75522a4669b44e39c1d2e1726c530232130d407f89afee0964997f7a73e83be698b288febcf88e3e03c4f0757ea8964e59b63d93708b138cc42a66eb3' + }, + "whirlpool('a')": { + message: 'a', + hash: '8aca2602792aec6f11a67206531fb7d7f0dff59413145e6973c45001d0087b42d11bc645413aeff63a42391a39145a591a92200d560195e53b478584fdae231a' + }, + "whirlpool('abc')": { + message: 'abc', + hash: '4e2448a4c6f486bb16b6562c73b4020bf3043e3a731bce721ae1b303d97e6d4c7181eebdb6c57e277d0e34957114cbd6c797fc9d95d8b582d225292076d4eef5' + }, + "whirlpool('message digest')": { + message: 'message digest', + hash: '378c84a4126e2dc6e56dcc7458377aac838d00032230f53ce1f5700c0ffb4d3b8421557659ef55c106b4b52ac5a4aaa692ed920052838f3362e86dbd37a8903e' + }, + "whirlpool('a..z')": { + message: 'abcdefghijklmnopqrstuvwxyz', + hash: 'f1d754662636ffe92c82ebb9212a484a8d38631ead4238f5442ee13b8054e41b08bf2a9251c30b6a0b8aae86177ab4a6f68f673e7207865d5d9819a3dba4eb3b' + }, + "whirlpool('A..Za..z0..9')": { + message: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', + hash: 'dc37e008cf9ee69bf11f00ed9aba26901dd7c28cdec066cc6af42e40f82f3a1e08eba26629129d8fb7cb57211b9281a65517cc879d7b962142c65f5a7af01467' + }, + "whirlpool('1234567890' x 8)": { + message: new Array(9).join('1234567890'), + hash: '466ef18babb0154d25b9d38a6414f5c08784372bccb204d6549c4afadb6014294d5bd8df2a6c44e538cd047b2681a51a2c60481e88c5a20b2c2a80cf3a9a083b' + } } }; if (typeof module !== 'undefined' && module.exports) {