diff --git a/build/rollup.config.js b/build/rollup.config.js index 1fa6b21..4387422 100644 --- a/build/rollup.config.js +++ b/build/rollup.config.js @@ -1,9 +1,6 @@ const path = require('path'); const pkg = require('../package.json'); -const resolve = require('rollup-plugin-node-resolve'); -// const commonjs = require('rollup-plugin-commonjs'); -// const babel = require('rollup-plugin-babel'); -// const json = require('rollup-plugin-json'); + const banner = `/* @license @@ -23,7 +20,7 @@ module.exports = { output: { file: uniqResolve('../lib/index.js'), format: 'umd', - name: 'crypto', + name: 'CryptoJS', banner } } \ No newline at end of file diff --git a/src/md5.js b/src/algo/hash/md5.js similarity index 94% rename from src/md5.js rename to src/algo/hash/md5.js index ba013a1..f5c9480 100644 --- a/src/md5.js +++ b/src/algo/hash/md5.js @@ -1,13 +1,13 @@ import { - WordArray, - Hasher -} from './core.js'; + WordArray +} from '../../core/core.js'; +import { Hasher } from '../../core/hasher'; // Constants table const T = []; // Compute constants -for (let i = 0; i < 64; i += 1) { +for (let i = 0; i < 64; i++) { T[i] = (Math.abs(Math.sin(i + 1)) * 0x100000000) | 0; } @@ -48,14 +48,14 @@ export class MD5Algo extends Hasher { const _M = M; // Swap endian - for (let i = 0; i < 16; i += 1) { + for (let i = 0; i < 16; i++) { // Shortcuts const offset_i = offset + i; const M_offset_i = M[offset_i]; _M[offset_i] = ( (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) - | (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + | (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) ); } @@ -79,7 +79,7 @@ export class MD5Algo extends Hasher { const M_offset_14 = _M[offset + 14]; const M_offset_15 = _M[offset + 15]; - // Working varialbes + // Working variables let a = H[0]; let b = H[1]; let c = H[2]; @@ -177,11 +177,11 @@ export class MD5Algo extends Hasher { const nBitsTotalL = nBitsTotal; dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = ( (((nBitsTotalH << 8) | (nBitsTotalH >>> 24)) & 0x00ff00ff) - | (((nBitsTotalH << 24) | (nBitsTotalH >>> 8)) & 0xff00ff00) + | (((nBitsTotalH << 24) | (nBitsTotalH >>> 8)) & 0xff00ff00) ); dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( (((nBitsTotalL << 8) | (nBitsTotalL >>> 24)) & 0x00ff00ff) - | (((nBitsTotalL << 24) | (nBitsTotalL >>> 8)) & 0xff00ff00) + | (((nBitsTotalL << 24) | (nBitsTotalL >>> 8)) & 0xff00ff00) ); data.sigBytes = (dataWords.length + 1) * 4; @@ -194,7 +194,7 @@ export class MD5Algo extends Hasher { const H = hash.words; // Swap endian - for (let i = 0; i < 4; i += 1) { + for (let i = 0; i < 4; i++) { // Shortcut const H_i = H[i]; diff --git a/src/algo/hash/ripemd160.js b/src/algo/hash/ripemd160.js new file mode 100644 index 0000000..4129905 --- /dev/null +++ b/src/algo/hash/ripemd160.js @@ -0,0 +1,243 @@ +/** @preserve +(c) 2012 by Cédric Mesnil. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted +provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import { + WordArray +} from '../../core/core.js'; + +import { Hasher } from '../../core/hasher'; + +// Constants table +const _zl = new WordArray([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13]); +const _zr = new WordArray([ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11]); +const _sl = new WordArray([ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6]); +const _sr = new WordArray([ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11]); + +const _hl = new WordArray([0x00000000, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E]); +const _hr = new WordArray([0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000]); + +const f1 = (x, y, z) => (x) ^ (y) ^ (z); + +const f2 = (x, y, z) => ((x) & (y)) | ((~x) & (z)); + +const f3 = (x, y, z) => ((x) | (~(y))) ^ (z); + +const f4 = (x, y, z) => ((x) & (z)) | ((y) & (~(z))); + +const f5 = (x, y, z) => (x) ^ ((y) | (~(z))); + +const rotl = (x, n) => (x << n) | (x >>> (32 - n)); + +/** + * RIPEMD160 hash algorithm. + */ +export class RIPEMD160Algo extends Hasher { + _doReset() { + this._hash = new WordArray([0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]); + } + + _doProcessBlock(M, offset) { + const _M = M; + + // Swap endian + for (let i = 0; i < 16; i++) { + // Shortcuts + const offset_i = offset + i; + const M_offset_i = _M[offset_i]; + + // Swap + _M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) + | (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + // Shortcut + const H = this._hash.words; + const hl = _hl.words; + const hr = _hr.words; + const zl = _zl.words; + const zr = _zr.words; + const sl = _sl.words; + const sr = _sr.words; + + // Working variables + let al = H[0]; + let bl = H[1]; + let cl = H[2]; + let dl = H[3]; + let el = H[4]; + let ar = H[0]; + let br = H[1]; + let cr = H[2]; + let dr = H[3]; + let er = H[4]; + + // Computation + let t; + for (let i = 0; i < 80; i++) { + t = (al + _M[offset + zl[i]]) | 0; + if (i < 16) { + t += f1(bl, cl, dl) + hl[0]; + } else if (i < 32) { + t += f2(bl, cl, dl) + hl[1]; + } else if (i < 48) { + t += f3(bl, cl, dl) + hl[2]; + } else if (i < 64) { + t += f4(bl, cl, dl) + hl[3]; + } else { // if (i<80) { + t += f5(bl, cl, dl) + hl[4]; + } + t = t | 0; + t = rotl(t, sl[i]); + t = (t + el) | 0; + al = el; + el = dl; + dl = rotl(cl, 10); + cl = bl; + bl = t; + + t = (ar + _M[offset + zr[i]]) | 0; + if (i < 16) { + t += f5(br, cr, dr) + hr[0]; + } else if (i < 32) { + t += f4(br, cr, dr) + hr[1]; + } else if (i < 48) { + t += f3(br, cr, dr) + hr[2]; + } else if (i < 64) { + t += f2(br, cr, dr) + hr[3]; + } else { // if (i<80) { + t += f1(br, cr, dr) + hr[4]; + } + t |= 0; + t = rotl(t, sr[i]); + t = (t + er) | 0; + ar = er; + er = dr; + dr = rotl(cr, 10); + cr = br; + br = t; + } + // Intermediate hash value + t = (H[1] + cl + dr) | 0; + H[1] = (H[2] + dl + er) | 0; + H[2] = (H[3] + el + ar) | 0; + H[3] = (H[4] + al + br) | 0; + H[4] = (H[0] + bl + cr) | 0; + H[0] = t; + } + + _doFinalize() { + // Shortcuts + const data = this._data; + const dataWords = data.words; + + const nBitsTotal = this._nDataBytes * 8; + const nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - (nBitsLeft % 32)); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotal << 8) | (nBitsTotal >>> 24)) & 0x00ff00ff) + | (((nBitsTotal << 24) | (nBitsTotal >>> 8)) & 0xff00ff00) + ); + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + const hash = this._hash; + const H = hash.words; + + // Swap endian + for (let i = 0; i < 5; i++) { + // Shortcut + const H_i = H[i]; + + // Swap + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) + | (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + } + + clone() { + const clone = super.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } +} + +/** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.RIPEMD160('message'); + * var hash = CryptoJS.RIPEMD160(wordArray); + */ +export const RIPEMD160 = Hasher._createHelper(RIPEMD160Algo); + +/** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacRIPEMD160(message, key); + */ +export const HmacRIPEMD160 = Hasher._createHmacHelper(RIPEMD160Algo); diff --git a/src/algo/hash/sha1.js b/src/algo/hash/sha1.js new file mode 100644 index 0000000..741c718 --- /dev/null +++ b/src/algo/hash/sha1.js @@ -0,0 +1,129 @@ +import { + WordArray +} from '../../core/core.js'; + +import { Hasher } from '../../core/hasher'; + +// Reusable object +const W = []; + +/** + * SHA-1 hash algorithm. + */ +export class SHA1Algo extends Hasher { + _doReset() { + this._hash = new WordArray([ + 0x67452301, + 0xefcdab89, + 0x98badcfe, + 0x10325476, + 0xc3d2e1f0 + ]); + } + + _doProcessBlock(M, offset) { + // Shortcut + const H = this._hash.words; + + // Working variables + let a = H[0]; + let b = H[1]; + let c = H[2]; + let d = H[3]; + let e = H[4]; + + // Computation + for (let i = 0; i < 80; i++) { + if (i < 16) { + W[i] = M[offset + i] | 0; + } else { + const n = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; + W[i] = (n << 1) | (n >>> 31); + } + + let t = ((a << 5) | (a >>> 27)) + e + W[i]; + if (i < 20) { + t += ((b & c) | (~b & d)) + 0x5a827999; + } else if (i < 40) { + t += (b ^ c ^ d) + 0x6ed9eba1; + } else if (i < 60) { + t += ((b & c) | (b & d) | (c & d)) - 0x70e44324; + } else /* if (i < 80) */ { + t += (b ^ c ^ d) - 0x359d3e2a; + } + + e = d; + d = c; + c = (b << 30) | (b >>> 2); + b = a; + a = t; + } + + // Intermediate hash value + 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; + } + + _doFinalize() { + // Shortcuts + const data = this._data; + const dataWords = data.words; + + const nBitsTotal = this._nDataBytes * 8; + const nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - (nBitsLeft % 32)); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Return final computed hash + return this._hash; + } + + clone() { + const clone = super.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } +} + +/** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA1('message'); + * var hash = CryptoJS.SHA1(wordArray); + */ +export const SHA1 = Hasher._createHelper(SHA1Algo); + +/** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA1(message, key); + */ +export const HmacSHA1 = Hasher._createHmacHelper(SHA1Algo); diff --git a/src/algo/hash/sha224.js b/src/algo/hash/sha224.js new file mode 100644 index 0000000..be0d2ba --- /dev/null +++ b/src/algo/hash/sha224.js @@ -0,0 +1,60 @@ +import { WordArray } from '../../core/core.js'; +import { SHA256Algo } from './sha256.js'; + +/** + * SHA-224 hash algorithm. + */ +export class SHA224Algo extends SHA256Algo { + _doReset() { + this._hash = new WordArray([ + 0xc1059ed8, + 0x367cd507, + 0x3070dd17, + 0xf70e5939, + 0xffc00b31, + 0x68581511, + 0x64f98fa7, + 0xbefa4fa4 + ]); + } + + _doFinalize() { + const hash = super._doFinalize.call(this); + + hash.sigBytes -= 4; + + return hash; + } +} + +/** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA224('message'); + * var hash = CryptoJS.SHA224(wordArray); + */ +export const SHA224 = SHA256Algo._createHelper(SHA224Algo); + +/** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA224(message, key); + */ +export const HmacSHA224 = SHA256Algo._createHmacHelper(SHA224Algo); diff --git a/src/algo/hash/sha256.js b/src/algo/hash/sha256.js new file mode 100644 index 0000000..f649d2c --- /dev/null +++ b/src/algo/hash/sha256.js @@ -0,0 +1,171 @@ +import { + WordArray +} from '../../core/core.js'; +import { Hasher } from '../../core/hasher'; + +// Initialization and round constants tables +const H = []; +const K = []; + +// Compute constants +const isPrime = (n) => { + const sqrtN = Math.sqrt(n); + for (let factor = 2; factor <= sqrtN; factor++) { + if (!(n % factor)) { + return false; + } + } + + return true; +}; + +const getFractionalBits = n => ((n - (n | 0)) * 0x100000000) | 0; + +let n = 2; +let nPrime = 0; +while (nPrime < 64) { + if (isPrime(n)) { + if (nPrime < 8) { + H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2)); + } + K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3)); + + nPrime++; + } + + n++; +} + +// Reusable object +const W = []; + +/** + * SHA-256 hash algorithm. + */ +export class SHA256Algo extends Hasher { + _doReset() { + this._hash = new WordArray(H.slice(0)); + } + + _doProcessBlock(M, offset) { + // Shortcut + const H = this._hash.words; + + // Working variables + let a = H[0]; + let b = H[1]; + let c = H[2]; + let d = H[3]; + let e = H[4]; + let f = H[5]; + let g = H[6]; + let h = H[7]; + + // Computation + for (let i = 0; i < 64; i++) { + if (i < 16) { + W[i] = M[offset + i] | 0; + } else { + const gamma0x = W[i - 15]; + const gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) + ^ ((gamma0x << 14) | (gamma0x >>> 18)) + ^ (gamma0x >>> 3); + + const gamma1x = W[i - 2]; + const gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) + ^ ((gamma1x << 13) | (gamma1x >>> 19)) + ^ (gamma1x >>> 10); + + W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16]; + } + + const ch = (e & f) ^ (~e & g); + const maj = (a & b) ^ (a & c) ^ (b & c); + + const sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22)); + const sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25)); + + const t1 = h + sigma1 + ch + K[i] + W[i]; + const t2 = sigma0 + maj; + + h = g; + g = f; + f = e; + e = (d + t1) | 0; + d = c; + c = b; + b = a; + a = (t1 + t2) | 0; + } + + // Intermediate hash value + 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; + H[5] = (H[5] + f) | 0; + H[6] = (H[6] + g) | 0; + H[7] = (H[7] + h) | 0; + } + + _doFinalize() { + // Shortcuts + const data = this._data; + const dataWords = data.words; + + const nBitsTotal = this._nDataBytes * 8; + const nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - (nBitsLeft % 32)); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Return final computed hash + return this._hash; + } + + clone() { + const clone = super.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } +} + +/** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA256('message'); + * var hash = CryptoJS.SHA256(wordArray); + */ +export const SHA256 = Hasher._createHelper(SHA256Algo); + +/** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA256(message, key); + */ +export const HmacSHA256 = Hasher._createHmacHelper(SHA256Algo); diff --git a/src/algo/hash/sha3.js b/src/algo/hash/sha3.js new file mode 100644 index 0000000..25eacaf --- /dev/null +++ b/src/algo/hash/sha3.js @@ -0,0 +1,295 @@ +import { + WordArray +} from '../../core/core.js'; +import { X64Word } from '../../core/x64-core.js'; +import { Hasher } from '../../core/hasher'; + +// Constants tables +const RHO_OFFSETS = []; +const PI_INDEXES = []; +const ROUND_CONSTANTS = []; + +// Compute Constants +// Compute rho offset constants +let _x = 1; +let _y = 0; +for (let t = 0; t < 24; t++) { + RHO_OFFSETS[_x + 5 * _y] = ((t + 1) * (t + 2) / 2) % 64; + + const newX = _y % 5; + const newY = (2 * _x + 3 * _y) % 5; + _x = newX; + _y = newY; +} + +// Compute pi index constants +for (let x = 0; x < 5; x++) { + for (let y = 0; y < 5; y++) { + PI_INDEXES[x + 5 * y] = y + ((2 * x + 3 * y) % 5) * 5; + } +} + +// Compute round constants +let LFSR = 0x01; +for (let i = 0; i < 24; i++) { + let roundConstantMsw = 0; + let roundConstantLsw = 0; + + for (let j = 0; j < 7; j++) { + if (LFSR & 0x01) { + const bitPosition = (1 << j) - 1; + if (bitPosition < 32) { + roundConstantLsw ^= 1 << bitPosition; + } else /* if (bitPosition >= 32) */ { + roundConstantMsw ^= 1 << (bitPosition - 32); + } + } + + // Compute next LFSR + if (LFSR & 0x80) { + // Primitive polynomial over GF(2): x^8 + x^6 + x^5 + x^4 + 1 + LFSR = (LFSR << 1) ^ 0x71; + } else { + LFSR <<= 1; + } + } + + ROUND_CONSTANTS[i] = new X64Word(roundConstantMsw, roundConstantLsw); +} + +// Reusable objects for temporary values +const T = []; +for (let i = 0; i < 25; i++) { + T[i] = new X64Word(); +} + +/** + * SHA-3 hash algorithm. + */ +export class SHA3Algo extends Hasher { + constructor(cfg) { + /** + * Configuration options. + * + * @property {number} outputLength + * The desired number of bits in the output hash. + * Only values permitted are: 224, 256, 384, 512. + * Default: 512 + */ + super(Object.assign( + { outputLength: 512 }, + cfg + )); + } + + _doReset() { + this._state = []; + const state = this._state; + for (let i = 0; i < 25; i++) { + state[i] = new X64Word(); + } + + this.blockSize = (1600 - 2 * this.cfg.outputLength) / 32; + } + + _doProcessBlock(M, offset) { + // Shortcuts + const state = this._state; + const nBlockSizeLanes = this.blockSize / 2; + + // Absorb + for (let i = 0; i < nBlockSizeLanes; i++) { + // Shortcuts + let M2i = M[offset + 2 * i]; + let M2i1 = M[offset + 2 * i + 1]; + + // Swap endian + M2i = (((M2i << 8) | (M2i >>> 24)) & 0x00ff00ff) + | (((M2i << 24) | (M2i >>> 8)) & 0xff00ff00); + M2i1 = (((M2i1 << 8) | (M2i1 >>> 24)) & 0x00ff00ff) + | (((M2i1 << 24) | (M2i1 >>> 8)) & 0xff00ff00); + + // Absorb message into state + const lane = state[i]; + lane.high ^= M2i1; + lane.low ^= M2i; + } + + // Rounds + for (let round = 0; round < 24; round++) { + // Theta + for (let x = 0; x < 5; x++) { + // Mix column lanes + let tMsw = 0; + let tLsw = 0; + for (let y = 0; y < 5; y++) { + const lane = state[x + 5 * y]; + tMsw ^= lane.high; + tLsw ^= lane.low; + } + + // Temporary values + const Tx = T[x]; + Tx.high = tMsw; + Tx.low = tLsw; + } + for (let x = 0; x < 5; x++) { + // Shortcuts + const Tx4 = T[(x + 4) % 5]; + const Tx1 = T[(x + 1) % 5]; + const Tx1Msw = Tx1.high; + const Tx1Lsw = Tx1.low; + + // Mix surrounding columns + const tMsw = Tx4.high ^ ((Tx1Msw << 1) | (Tx1Lsw >>> 31)); + const tLsw = Tx4.low ^ ((Tx1Lsw << 1) | (Tx1Msw >>> 31)); + for (let y = 0; y < 5; y++) { + const lane = state[x + 5 * y]; + lane.high ^= tMsw; + lane.low ^= tLsw; + } + } + + // Rho Pi + for (let laneIndex = 1; laneIndex < 25; laneIndex++) { + let tMsw; + let tLsw; + + // Shortcuts + const lane = state[laneIndex]; + const laneMsw = lane.high; + const laneLsw = lane.low; + const rhoOffset = RHO_OFFSETS[laneIndex]; + + // Rotate lanes + if (rhoOffset < 32) { + tMsw = (laneMsw << rhoOffset) | (laneLsw >>> (32 - rhoOffset)); + tLsw = (laneLsw << rhoOffset) | (laneMsw >>> (32 - rhoOffset)); + } else /* if (rhoOffset >= 32) */ { + tMsw = (laneLsw << (rhoOffset - 32)) | (laneMsw >>> (64 - rhoOffset)); + tLsw = (laneMsw << (rhoOffset - 32)) | (laneLsw >>> (64 - rhoOffset)); + } + + // Transpose lanes + const TPiLane = T[PI_INDEXES[laneIndex]]; + TPiLane.high = tMsw; + TPiLane.low = tLsw; + } + + // Rho pi at x = y = 0 + const T0 = T[0]; + const state0 = state[0]; + T0.high = state0.high; + T0.low = state0.low; + + // Chi + for (let x = 0; x < 5; x++) { + for (let y = 0; y < 5; y++) { + // Shortcuts + const laneIndex = x + 5 * y; + const lane = state[laneIndex]; + const TLane = T[laneIndex]; + const Tx1Lane = T[((x + 1) % 5) + 5 * y]; + const Tx2Lane = T[((x + 2) % 5) + 5 * y]; + + // Mix rows + lane.high = TLane.high ^ (~Tx1Lane.high & Tx2Lane.high); + lane.low = TLane.low ^ (~Tx1Lane.low & Tx2Lane.low); + } + } + + // Iota + const lane = state[0]; + const roundConstant = ROUND_CONSTANTS[round]; + lane.high ^= roundConstant.high; + lane.low ^= roundConstant.low; + } + } + + _doFinalize() { + // Shortcuts + const data = this._data; + const dataWords = data.words; + const nBitsLeft = data.sigBytes * 8; + const blockSizeBits = this.blockSize * 32; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x1 << (24 - nBitsLeft % 32); + dataWords[((Math.ceil((nBitsLeft + 1) / blockSizeBits) * blockSizeBits) >>> 5) - 1] |= 0x80; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + const state = this._state; + const outputLengthBytes = this.cfg.outputLength / 8; + const outputLengthLanes = outputLengthBytes / 8; + + // Squeeze + const hashWords = []; + for (let i = 0; i < outputLengthLanes; i++) { + // Shortcuts + const lane = state[i]; + let laneMsw = lane.high; + let laneLsw = lane.low; + + // Swap endian + laneMsw = (((laneMsw << 8) | (laneMsw >>> 24)) & 0x00ff00ff) + | (((laneMsw << 24) | (laneMsw >>> 8)) & 0xff00ff00); + laneLsw = (((laneLsw << 8) | (laneLsw >>> 24)) & 0x00ff00ff) + | (((laneLsw << 24) | (laneLsw >>> 8)) & 0xff00ff00); + + // Squeeze state to retrieve hash + hashWords.push(laneLsw); + hashWords.push(laneMsw); + } + + // Return final computed hash + return new WordArray(hashWords, outputLengthBytes); + } + + clone() { + const clone = super.clone.call(this); + + clone._state = this._state.slice(0); + const state = clone._state; + for (let i = 0; i < 25; i++) { + state[i] = state[i].clone(); + } + + return clone; + } +} + +/** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA3('message'); + * var hash = CryptoJS.SHA3(wordArray); + */ +export const SHA3 = Hasher._createHelper(SHA3Algo); + +/** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA3(message, key); + */ +export const HmacSHA3 = Hasher._createHmacHelper(SHA3Algo); diff --git a/src/algo/hash/sha384.js b/src/algo/hash/sha384.js new file mode 100644 index 0000000..c4e04bb --- /dev/null +++ b/src/algo/hash/sha384.js @@ -0,0 +1,63 @@ +import { + X64Word, + X64WordArray +} from '../../core/x64-core.js'; +import { SHA512Algo } from './sha512.js'; + +/** + * SHA-384 hash algorithm. + */ +export class SHA384Algo extends SHA512Algo { + _doReset() { + this._hash = new X64WordArray([ + new X64Word(0xcbbb9d5d, 0xc1059ed8), + new X64Word(0x629a292a, 0x367cd507), + new X64Word(0x9159015a, 0x3070dd17), + new X64Word(0x152fecd8, 0xf70e5939), + new X64Word(0x67332667, 0xffc00b31), + new X64Word(0x8eb44a87, 0x68581511), + new X64Word(0xdb0c2e0d, 0x64f98fa7), + new X64Word(0x47b5481d, 0xbefa4fa4) + ]); + } + + _doFinalize() { + const hash = super._doFinalize.call(this); + + hash.sigBytes -= 16; + + return hash; + } +} + +/** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA384('message'); + * var hash = CryptoJS.SHA384(wordArray); + */ +export const SHA384 = SHA512Algo._createHelper(SHA384Algo); + +/** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA384(message, key); + */ +export const HmacSHA384 = SHA512Algo._createHmacHelper(SHA384Algo); diff --git a/src/algo/hash/sha512.js b/src/algo/hash/sha512.js new file mode 100644 index 0000000..bffae19 --- /dev/null +++ b/src/algo/hash/sha512.js @@ -0,0 +1,369 @@ +import { Hasher } from '../../core/hasher'; +import { + X64Word, + X64WordArray +} from '../../core/x64-core.js'; + +// Constants +const K = [ + new X64Word(0x428a2f98, 0xd728ae22), + new X64Word(0x71374491, 0x23ef65cd), + new X64Word(0xb5c0fbcf, 0xec4d3b2f), + new X64Word(0xe9b5dba5, 0x8189dbbc), + new X64Word(0x3956c25b, 0xf348b538), + new X64Word(0x59f111f1, 0xb605d019), + new X64Word(0x923f82a4, 0xaf194f9b), + new X64Word(0xab1c5ed5, 0xda6d8118), + new X64Word(0xd807aa98, 0xa3030242), + new X64Word(0x12835b01, 0x45706fbe), + new X64Word(0x243185be, 0x4ee4b28c), + new X64Word(0x550c7dc3, 0xd5ffb4e2), + new X64Word(0x72be5d74, 0xf27b896f), + new X64Word(0x80deb1fe, 0x3b1696b1), + new X64Word(0x9bdc06a7, 0x25c71235), + new X64Word(0xc19bf174, 0xcf692694), + new X64Word(0xe49b69c1, 0x9ef14ad2), + new X64Word(0xefbe4786, 0x384f25e3), + new X64Word(0x0fc19dc6, 0x8b8cd5b5), + new X64Word(0x240ca1cc, 0x77ac9c65), + new X64Word(0x2de92c6f, 0x592b0275), + new X64Word(0x4a7484aa, 0x6ea6e483), + new X64Word(0x5cb0a9dc, 0xbd41fbd4), + new X64Word(0x76f988da, 0x831153b5), + new X64Word(0x983e5152, 0xee66dfab), + new X64Word(0xa831c66d, 0x2db43210), + new X64Word(0xb00327c8, 0x98fb213f), + new X64Word(0xbf597fc7, 0xbeef0ee4), + new X64Word(0xc6e00bf3, 0x3da88fc2), + new X64Word(0xd5a79147, 0x930aa725), + new X64Word(0x06ca6351, 0xe003826f), + new X64Word(0x14292967, 0x0a0e6e70), + new X64Word(0x27b70a85, 0x46d22ffc), + new X64Word(0x2e1b2138, 0x5c26c926), + new X64Word(0x4d2c6dfc, 0x5ac42aed), + new X64Word(0x53380d13, 0x9d95b3df), + new X64Word(0x650a7354, 0x8baf63de), + new X64Word(0x766a0abb, 0x3c77b2a8), + new X64Word(0x81c2c92e, 0x47edaee6), + new X64Word(0x92722c85, 0x1482353b), + new X64Word(0xa2bfe8a1, 0x4cf10364), + new X64Word(0xa81a664b, 0xbc423001), + new X64Word(0xc24b8b70, 0xd0f89791), + new X64Word(0xc76c51a3, 0x0654be30), + new X64Word(0xd192e819, 0xd6ef5218), + new X64Word(0xd6990624, 0x5565a910), + new X64Word(0xf40e3585, 0x5771202a), + new X64Word(0x106aa070, 0x32bbd1b8), + new X64Word(0x19a4c116, 0xb8d2d0c8), + new X64Word(0x1e376c08, 0x5141ab53), + new X64Word(0x2748774c, 0xdf8eeb99), + new X64Word(0x34b0bcb5, 0xe19b48a8), + new X64Word(0x391c0cb3, 0xc5c95a63), + new X64Word(0x4ed8aa4a, 0xe3418acb), + new X64Word(0x5b9cca4f, 0x7763e373), + new X64Word(0x682e6ff3, 0xd6b2b8a3), + new X64Word(0x748f82ee, 0x5defb2fc), + new X64Word(0x78a5636f, 0x43172f60), + new X64Word(0x84c87814, 0xa1f0ab72), + new X64Word(0x8cc70208, 0x1a6439ec), + new X64Word(0x90befffa, 0x23631e28), + new X64Word(0xa4506ceb, 0xde82bde9), + new X64Word(0xbef9a3f7, 0xb2c67915), + new X64Word(0xc67178f2, 0xe372532b), + new X64Word(0xca273ece, 0xea26619c), + new X64Word(0xd186b8c7, 0x21c0c207), + new X64Word(0xeada7dd6, 0xcde0eb1e), + new X64Word(0xf57d4f7f, 0xee6ed178), + new X64Word(0x06f067aa, 0x72176fba), + new X64Word(0x0a637dc5, 0xa2c898a6), + new X64Word(0x113f9804, 0xbef90dae), + new X64Word(0x1b710b35, 0x131c471b), + new X64Word(0x28db77f5, 0x23047d84), + new X64Word(0x32caab7b, 0x40c72493), + new X64Word(0x3c9ebe0a, 0x15c9bebc), + new X64Word(0x431d67c4, 0x9c100d4c), + new X64Word(0x4cc5d4be, 0xcb3e42b6), + new X64Word(0x597f299c, 0xfc657e2a), + new X64Word(0x5fcb6fab, 0x3ad6faec), + new X64Word(0x6c44198c, 0x4a475817) +]; + +// Reusable objects +const W = []; +for (let i = 0; i < 80; i++) { + W[i] = new X64Word(); +} + +/** + * SHA-512 hash algorithm. + */ +export class SHA512Algo extends Hasher { + constructor() { + super(); + + this.blockSize = 1024 / 32; + } + + _doReset() { + this._hash = new X64WordArray([ + new X64Word(0x6a09e667, 0xf3bcc908), + new X64Word(0xbb67ae85, 0x84caa73b), + new X64Word(0x3c6ef372, 0xfe94f82b), + new X64Word(0xa54ff53a, 0x5f1d36f1), + new X64Word(0x510e527f, 0xade682d1), + new X64Word(0x9b05688c, 0x2b3e6c1f), + new X64Word(0x1f83d9ab, 0xfb41bd6b), + new X64Word(0x5be0cd19, 0x137e2179) + ]); + } + + _doProcessBlock(M, offset) { + // Shortcuts + const H = this._hash.words; + + const H0 = H[0]; + const H1 = H[1]; + const H2 = H[2]; + const H3 = H[3]; + const H4 = H[4]; + const H5 = H[5]; + const H6 = H[6]; + const H7 = H[7]; + + const H0h = H0.high; + let H0l = H0.low; + const H1h = H1.high; + let H1l = H1.low; + const H2h = H2.high; + let H2l = H2.low; + const H3h = H3.high; + let H3l = H3.low; + const H4h = H4.high; + let H4l = H4.low; + const H5h = H5.high; + let H5l = H5.low; + const H6h = H6.high; + let H6l = H6.low; + const H7h = H7.high; + let H7l = H7.low; + + // Working variables + let ah = H0h; + let al = H0l; + let bh = H1h; + let bl = H1l; + let ch = H2h; + let cl = H2l; + let dh = H3h; + let dl = H3l; + let eh = H4h; + let el = H4l; + let fh = H5h; + let fl = H5l; + let gh = H6h; + let gl = H6l; + let hh = H7h; + let hl = H7l; + + // Rounds + for (let i = 0; i < 80; i++) { + let Wil; + let Wih; + + // Shortcut + const Wi = W[i]; + + // Extend message + if (i < 16) { + Wi.high = M[offset + i * 2] | 0; + Wih = Wi.high; + Wi.low = M[offset + i * 2 + 1] | 0; + Wil = Wi.low; + } else { + // Gamma0 + const gamma0x = W[i - 15]; + const gamma0xh = gamma0x.high; + const gamma0xl = gamma0x.low; + const gamma0h = ((gamma0xh >>> 1) | (gamma0xl << 31)) + ^ ((gamma0xh >>> 8) | (gamma0xl << 24)) + ^ (gamma0xh >>> 7); + const gamma0l = ((gamma0xl >>> 1) | (gamma0xh << 31)) + ^ ((gamma0xl >>> 8) | (gamma0xh << 24)) + ^ ((gamma0xl >>> 7) | (gamma0xh << 25)); + + // Gamma1 + const gamma1x = W[i - 2]; + const gamma1xh = gamma1x.high; + const gamma1xl = gamma1x.low; + const gamma1h = ((gamma1xh >>> 19) | (gamma1xl << 13)) + ^ ((gamma1xh << 3) | (gamma1xl >>> 29)) + ^ (gamma1xh >>> 6); + const gamma1l = ((gamma1xl >>> 19) | (gamma1xh << 13)) + ^ ((gamma1xl << 3) | (gamma1xh >>> 29)) + ^ ((gamma1xl >>> 6) | (gamma1xh << 26)); + + // W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16] + const Wi7 = W[i - 7]; + const Wi7h = Wi7.high; + const Wi7l = Wi7.low; + + const Wi16 = W[i - 16]; + const Wi16h = Wi16.high; + const Wi16l = Wi16.low; + + Wil = gamma0l + Wi7l; + Wih = gamma0h + Wi7h + ((Wil >>> 0) < (gamma0l >>> 0) ? 1 : 0); + Wil = Wil + gamma1l; + Wih = Wih + gamma1h + ((Wil >>> 0) < (gamma1l >>> 0) ? 1 : 0); + Wil = Wil + Wi16l; + Wih = Wih + Wi16h + ((Wil >>> 0) < (Wi16l >>> 0) ? 1 : 0); + + Wi.high = Wih; + Wi.low = Wil; + } + + const chh = (eh & fh) ^ (~eh & gh); + const chl = (el & fl) ^ (~el & gl); + const majh = (ah & bh) ^ (ah & ch) ^ (bh & ch); + const majl = (al & bl) ^ (al & cl) ^ (bl & cl); + + const sigma0h = ((ah >>> 28) | (al << 4)) + ^ ((ah << 30) | (al >>> 2)) + ^ ((ah << 25) | (al >>> 7)); + const sigma0l = ((al >>> 28) | (ah << 4)) + ^ ((al << 30) | (ah >>> 2)) + ^ ((al << 25) | (ah >>> 7)); + const sigma1h = ((eh >>> 14) | (el << 18)) + ^ ((eh >>> 18) | (el << 14)) + ^ ((eh << 23) | (el >>> 9)); + const sigma1l = ((el >>> 14) | (eh << 18)) + ^ ((el >>> 18) | (eh << 14)) + ^ ((el << 23) | (eh >>> 9)); + + // t1 = h + sigma1 + ch + K[i] + W[i] + const Ki = K[i]; + const Kih = Ki.high; + const Kil = Ki.low; + + let t1l = hl + sigma1l; + let t1h = hh + sigma1h + ((t1l >>> 0) < (hl >>> 0) ? 1 : 0); + t1l = t1l + chl; + t1h = t1h + chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0); + t1l = t1l + Kil; + t1h = t1h + Kih + ((t1l >>> 0) < (Kil >>> 0) ? 1 : 0); + t1l = t1l + Wil; + t1h = t1h + Wih + ((t1l >>> 0) < (Wil >>> 0) ? 1 : 0); + + // t2 = sigma0 + maj + const t2l = sigma0l + majl; + const t2h = sigma0h + majh + ((t2l >>> 0) < (sigma0l >>> 0) ? 1 : 0); + + // Update working variables + hh = gh; + hl = gl; + gh = fh; + gl = fl; + fh = eh; + fl = el; + el = (dl + t1l) | 0; + eh = (dh + t1h + ((el >>> 0) < (dl >>> 0) ? 1 : 0)) | 0; + dh = ch; + dl = cl; + ch = bh; + cl = bl; + bh = ah; + bl = al; + al = (t1l + t2l) | 0; + ah = (t1h + t2h + ((al >>> 0) < (t1l >>> 0) ? 1 : 0)) | 0; + } + + // Intermediate hash value + H0.low = (H0l + al); + H0l = H0.low; + H0.high = (H0h + ah + ((H0l >>> 0) < (al >>> 0) ? 1 : 0)); + H1.low = (H1l + bl); + H1l = H1.low; + H1.high = (H1h + bh + ((H1l >>> 0) < (bl >>> 0) ? 1 : 0)); + H2.low = (H2l + cl); + H2l = H2.low; + H2.high = (H2h + ch + ((H2l >>> 0) < (cl >>> 0) ? 1 : 0)); + H3.low = (H3l + dl); + H3l = H3.low; + H3.high = (H3h + dh + ((H3l >>> 0) < (dl >>> 0) ? 1 : 0)); + H4.low = (H4l + el); + H4l = H4.low; + H4.high = (H4h + eh + ((H4l >>> 0) < (el >>> 0) ? 1 : 0)); + H5.low = (H5l + fl); + H5l = H5.low; + H5.high = (H5h + fh + ((H5l >>> 0) < (fl >>> 0) ? 1 : 0)); + H6.low = (H6l + gl); + H6l = H6.low; + H6.high = (H6h + gh + ((H6l >>> 0) < (gl >>> 0) ? 1 : 0)); + H7.low = (H7l + hl); + H7l = H7.low; + H7.high = (H7h + hh + ((H7l >>> 0) < (hl >>> 0) ? 1 : 0)); + } + + _doFinalize() { + // Shortcuts + const data = this._data; + const dataWords = data.words; + + const nBitsTotal = this._nDataBytes * 8; + const nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - (nBitsLeft % 32)); + dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 30] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 31] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Convert hash to 32-bit word array before returning + const hash = this._hash.toX32(); + + // Return final computed hash + return hash; + } + + clone() { + const clone = super.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } +} + +/** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA512('message'); + * var hash = CryptoJS.SHA512(wordArray); + */ +export const SHA512 = Hasher._createHelper(SHA512Algo); + +/** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA512(message, key); + */ +export const HmacSHA512 = Hasher._createHmacHelper(SHA512Algo); diff --git a/src/algo/hmac/hmac.js b/src/algo/hmac/hmac.js new file mode 100644 index 0000000..18dcdcd --- /dev/null +++ b/src/algo/hmac/hmac.js @@ -0,0 +1,129 @@ +import { + Base, WordArray +} from '../../core/core.js'; +import { Utf8 } from '../../encoding/enc-utf8'; +import { isString } from '../../utils'; + + +/** + * HMAC algorithm. + */ +export class HMAC extends Base { + /** + * Initializes a newly created HMAC. + * + * @param {Hasher} SubHasher The hash algorithm to use. + * @param {WordArray|string} key The secret key. + * + * @example + * + * let hmacHasher = new HMAC(CryptoJS.algo.SHA256, key); + */ + constructor(SubHasher, key) { + super(); + + const hasher = new SubHasher(); + this._hasher = hasher; + + // Convert string to WordArray, else assume WordArray already + let _key = key; + if (isString(_key)) { + _key = Utf8.parse(_key); + } + + // Shortcuts + const hasherBlockSize = hasher.blockSize; + const hasherBlockSizeBytes = hasherBlockSize * 4; + + // Allow arbitrary length keys + if (_key.sigBytes > hasherBlockSizeBytes) { + _key = hasher.finalize(key); + } + + // Clamp excess bits + _key.clamp(); + + // Clone key for inner and outer pads + const oKey = _key.clone(); + this._oKey = oKey; + const iKey = _key.clone(); + this._iKey = iKey; + + // Shortcuts + const oKeyWords = oKey.words; + const iKeyWords = iKey.words; + + // XOR keys with pad constants + for (let i = 0; i < hasherBlockSize; i++) { + oKeyWords[i] ^= 0x5c5c5c5c; + iKeyWords[i] ^= 0x36363636; + } + oKey.sigBytes = hasherBlockSizeBytes; + iKey.sigBytes = hasherBlockSizeBytes; + + // Set initial values + this.reset(); + } + + /** + * Resets this HMAC to its initial state. + * + * @example + * + * hmacHasher.reset(); + */ + reset() { + // Shortcut + const hasher = this._hasher; + + // Reset + hasher.reset(); + hasher.update(this._iKey); + } + + /** + * Updates this HMAC with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {HMAC} This HMAC instance. + * + * @example + * + * hmacHasher.update('message'); + * hmacHasher.update(wordArray); + */ + update(messageUpdate) { + this._hasher.update(messageUpdate); + + // Chainable + return this; + } + + /** + * Finalizes the HMAC computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The HMAC. + * + * @example + * + * let hmac = hmacHasher.finalize(); + * let hmac = hmacHasher.finalize('message'); + * let hmac = hmacHasher.finalize(wordArray); + */ + finalize(messageUpdate) { + // Shortcut + const hasher = this._hasher; + + // Compute HMAC + const innerHash = hasher.finalize(messageUpdate); + hasher.reset(); + const hmac = hasher.finalize(this._oKey.clone().concat(innerHash)); + + return hmac; + } +} + diff --git a/src/algo/pbkdf2/pbkdf2.js b/src/algo/pbkdf2/pbkdf2.js new file mode 100644 index 0000000..e0b4d20 --- /dev/null +++ b/src/algo/pbkdf2/pbkdf2.js @@ -0,0 +1,123 @@ +import { + Base, + WordArray +} from '../../core/core.js'; +import { SHA1Algo } from '../hash/sha1.js'; +import { HMAC } from '../hmac/hmac.js'; + + +/** + * Password-Based Key Derivation Function 2 algorithm. + */ +export class PBKDF2Algo extends Base { + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * const kdf = new CryptoJS.algo.PBKDF2(); + * const kdf = new CryptoJS.algo.PBKDF2({ keySize: 8 }); + * const kdf = new CryptoJS.algo.PBKDF2({ keySize: 8, iterations: 1000 }); + */ + constructor(cfg) { + super(); + + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hasher to use. Default: SHA1 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + this.cfg = Object.assign( + new Base(), + { + keySize: 128 / 32, + hasher: SHA1Algo, + iterations: 1 + }, + cfg + ); + } + + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * const key = kdf.compute(password, salt); + */ + compute(password, salt) { + // Shortcut + const { cfg } = this; + + // Init HMAC + const hmac = new HMAC(cfg.hasher, password); + + // Initial values + const derivedKey = new WordArray(); + const blockIndex = new WordArray([0x00000001]); + + // Shortcuts + const derivedKeyWords = derivedKey.words; + const blockIndexWords = blockIndex.words; + const { keySize, iterations } = cfg; + + // Generate key + while (derivedKeyWords.length < keySize) { + const block = hmac.update(salt).finalize(blockIndex); + hmac.reset(); + + // Shortcuts + const blockWords = block.words; + const blockWordsLength = blockWords.length; + + // Iterations + let intermediate = block; + for (let i = 1; i < iterations; i++) { + intermediate = hmac.finalize(intermediate); + hmac.reset(); + + // Shortcut + const intermediateWords = intermediate.words; + + // XOR intermediate with block + for (let j = 0; j < blockWordsLength; j++) { + blockWords[j] ^= intermediateWords[j]; + } + } + + derivedKey.concat(block); + blockIndexWords[0]++; + } + derivedKey.sigBytes = keySize * 4; + + return derivedKey; + } +} + +/** + * Computes the Password-Based Key Derivation Function 2. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.PBKDF2(password, salt); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8 }); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8, iterations: 1000 }); + */ +export const PBKDF2 = (password, salt, cfg) =>new PBKDF2Algo(cfg).compute(password, salt); diff --git a/src/core.js b/src/core.js deleted file mode 100644 index aa35d00..0000000 --- a/src/core.js +++ /dev/null @@ -1,772 +0,0 @@ -/* eslint-disable no-use-before-define */ - -/** - * Base class for inheritance. - */ -export class Base { - - /** - * Copies properties into this object. - * - * @param {Object} properties The properties to mix in. - * - * @example - * - * MyType.mixIn({ - * field: 'value' - * }); - */ - mixIn(properties) { - return Object.assign(this, properties); - } - - /** - * Creates a copy of this object. - * - * @return {Object} The clone. - * - * @example - * - * let clone = instance.clone(); - */ - clone() { - const clone = new this.constructor(); - Object.assign(clone, this); - return clone; - } -} - -/** - * An array of 32-bit words. - * - * @property {Array} words The array of 32-bit words. - * @property {number} sigBytes The number of significant bytes in this word array. - */ -export class WordArray extends Base { - /** - * Initializes a newly created word array. - * - * @param {Array} words (Optional) An array of 32-bit words. - * @param {number} sigBytes (Optional) The number of significant bytes in the words. - * - * @example - * - * let wordArray = new WordArray(); - * let wordArray = new WordArray([0x00010203, 0x04050607]); - * let wordArray = new WordArray([0x00010203, 0x04050607], 6); - */ - constructor(words = [], sigBytes = words.length * 4) { - super(); - - let typedArray = words; - // Convert buffers to uint8 - if (typedArray instanceof ArrayBuffer) { - typedArray = new Uint8Array(typedArray); - } - - // Convert other array views to uint8 - if ( - typedArray instanceof Int8Array || - typedArray instanceof Uint8ClampedArray || - typedArray instanceof Int16Array || - typedArray instanceof Uint16Array || - typedArray instanceof Int32Array || - typedArray instanceof Uint32Array || - typedArray instanceof Float32Array || - typedArray instanceof Float64Array - ) { - typedArray = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); - } - - // Handle Uint8Array - if (typedArray instanceof Uint8Array) { - // Shortcut - const typedArrayByteLength = typedArray.byteLength; - - // Extract bytes - const _words = []; - for (let i = 0; i < typedArrayByteLength; i += 1) { - _words[i >>> 2] |= typedArray[i] << (24 - (i % 4) * 8); - } - - // Initialize this word array - this.words = _words; - this.sigBytes = typedArrayByteLength; - } else { - // Else call normal init - this.words = words; - this.sigBytes = sigBytes; - } - } - - /** - * Creates a word array filled with random bytes. - * - * @param {number} nBytes The number of random bytes to generate. - * - * @return {WordArray} The random word array. - * - * @static - * - * @example - * - * let wordArray = CryptoJS.lib.WordArray.random(16); - */ - static random(nBytes) { - const words = []; - - const r = (m_w) => { - let _m_w = m_w; - let _m_z = 0x3ade68b1; - const mask = 0xffffffff; - - return () => { - _m_z = (0x9069 * (_m_z & 0xFFFF) + (_m_z >> 0x10)) & mask; - _m_w = (0x4650 * (_m_w & 0xFFFF) + (_m_w >> 0x10)) & mask; - let result = ((_m_z << 0x10) + _m_w) & mask; - result /= 0x100000000; - result += 0.5; - return result * (Math.random() > 0.5 ? 1 : -1); - }; - }; - - for (let i = 0, rcache; i < nBytes; i += 4) { - const _r = r((rcache || Math.random()) * 0x100000000); - - rcache = _r() * 0x3ade67b7; - words.push((_r() * 0x100000000) | 0); - } - - return new WordArray(words, nBytes); - } - - /** - * Converts this word array to a string. - * - * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex - * - * @return {string} The stringified word array. - * - * @example - * - * let string = wordArray + ''; - * let string = wordArray.toString(); - * let string = wordArray.toString(CryptoJS.enc.Utf8); - */ - toString(encoder = Hex) { - return encoder.stringify(this); - } - - /** - * Concatenates a word array to this word array. - * - * @param {WordArray} wordArray The word array to append. - * - * @return {WordArray} This word array. - * - * @example - * - * wordArray1.concat(wordArray2); - */ - concat(wordArray) { - // Shortcuts - const thisWords = this.words; - const thatWords = wordArray.words; - const thisSigBytes = this.sigBytes; - const thatSigBytes = wordArray.sigBytes; - - // Clamp excess bits - this.clamp(); - - // Concat - if (thisSigBytes % 4) { - // Copy one byte at a time - for (let i = 0; i < thatSigBytes; i += 1) { - const thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8); - } - } else { - // Copy one word at a time - for (let i = 0; i < thatSigBytes; i += 4) { - thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2]; - } - } - this.sigBytes += thatSigBytes; - - // Chainable - return this; - } - - /** - * Removes insignificant bits. - * - * @example - * - * wordArray.clamp(); - */ - clamp() { - // Shortcuts - const { - words, - sigBytes - } = this; - - // Clamp - words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8); - words.length = Math.ceil(sigBytes / 4); - } - - /** - * Creates a copy of this word array. - * - * @return {WordArray} The clone. - * - * @example - * - * let clone = wordArray.clone(); - */ - clone() { - const clone = super.clone.call(this); - clone.words = this.words.slice(0); - - return clone; - } -} - -/** - * Hex encoding strategy. - */ -export const Hex = { - /** - * Converts a word array to a hex string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The hex string. - * - * @static - * - * @example - * - * let hexString = CryptoJS.enc.Hex.stringify(wordArray); - */ - stringify(wordArray) { - // Shortcuts - const { - words, - sigBytes - } = wordArray; - - // Convert - const hexChars = []; - for (let i = 0; i < sigBytes; i += 1) { - const bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - hexChars.push((bite >>> 4).toString(16)); - hexChars.push((bite & 0x0f).toString(16)); - } - - return hexChars.join(''); - }, - - /** - * Converts a hex string to a word array. - * - * @param {string} hexStr The hex string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * let wordArray = CryptoJS.enc.Hex.parse(hexString); - */ - parse(hexStr) { - // Shortcut - const hexStrLength = hexStr.length; - - // Convert - const words = []; - for (let i = 0; i < hexStrLength; i += 2) { - words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); - } - - return new WordArray(words, hexStrLength / 2); - } -}; - -/** - * Latin1 encoding strategy. - */ -export const Latin1 = { - /** - * Converts a word array to a Latin1 string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The Latin1 string. - * - * @static - * - * @example - * - * let latin1String = CryptoJS.enc.Latin1.stringify(wordArray); - */ - stringify(wordArray) { - // Shortcuts - const { - words, - sigBytes - } = wordArray; - - // Convert - const latin1Chars = []; - for (let i = 0; i < sigBytes; i += 1) { - const bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - latin1Chars.push(String.fromCharCode(bite)); - } - - return latin1Chars.join(''); - }, - - /** - * Converts a Latin1 string to a word array. - * - * @param {string} latin1Str The Latin1 string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * let wordArray = CryptoJS.enc.Latin1.parse(latin1String); - */ - parse(latin1Str) { - // Shortcut - const latin1StrLength = latin1Str.length; - - // Convert - const words = []; - for (let i = 0; i < latin1StrLength; i += 1) { - words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8); - } - - return new WordArray(words, latin1StrLength); - } -}; - -/** - * UTF-8 encoding strategy. - */ -export const Utf8 = { - /** - * Converts a word array to a UTF-8 string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The UTF-8 string. - * - * @static - * - * @example - * - * let utf8String = CryptoJS.enc.Utf8.stringify(wordArray); - */ - stringify(wordArray) { - try { - return decodeURIComponent(escape(Latin1.stringify(wordArray))); - } catch (e) { - throw new Error('Malformed UTF-8 data'); - } - }, - - /** - * Converts a UTF-8 string to a word array. - * - * @param {string} utf8Str The UTF-8 string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * let wordArray = CryptoJS.enc.Utf8.parse(utf8String); - */ - parse(utf8Str) { - return Latin1.parse(unescape(encodeURIComponent(utf8Str))); - } -}; - -/** - * Abstract buffered block algorithm template. - * - * The property blockSize must be implemented in a concrete subtype. - * - * @property {number} _minBufferSize - * - * The number of blocks that should be kept unprocessed in the buffer. Default: 0 - */ -export class BufferedBlockAlgorithm extends Base { - constructor() { - super(); - this._minBufferSize = 0; - } - - /** - * Resets this block algorithm's data buffer to its initial state. - * - * @example - * - * bufferedBlockAlgorithm.reset(); - */ - reset() { - // Initial values - this._data = new WordArray(); - this._nDataBytes = 0; - } - - /** - * Adds new data to this block algorithm's buffer. - * - * @param {WordArray|string} data - * - * The data to append. Strings are converted to a WordArray using UTF-8. - * - * @example - * - * bufferedBlockAlgorithm._append('data'); - * bufferedBlockAlgorithm._append(wordArray); - */ - _append(data) { - let m_data = data; - - // Convert string to WordArray, else assume WordArray already - if (typeof m_data === 'string') { - m_data = Utf8.parse(m_data); - } - - // Append - this._data.concat(m_data); - this._nDataBytes += m_data.sigBytes; - } - - /** - * Processes available data blocks. - * - * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype. - * - * @param {boolean} doFlush Whether all blocks and partial blocks should be processed. - * - * @return {WordArray} The processed data. - * - * @example - * - * let processedData = bufferedBlockAlgorithm._process(); - * let processedData = bufferedBlockAlgorithm._process(!!'flush'); - */ - _process(doFlush) { - let processedWords; - - // Shortcuts - const { - _data: data, - blockSize - } = this; - const dataWords = data.words; - const dataSigBytes = data.sigBytes; - const blockSizeBytes = blockSize * 4; - - // Count blocks ready - let nBlocksReady = dataSigBytes / blockSizeBytes; - if (doFlush) { - // Round up to include partial blocks - nBlocksReady = Math.ceil(nBlocksReady); - } else { - // Round down to include only full blocks, - // less the number of blocks that must remain in the buffer - nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); - } - - // Count words ready - const nWordsReady = nBlocksReady * blockSize; - - // Count bytes ready - const nBytesReady = Math.min(nWordsReady * 4, dataSigBytes); - - // Process blocks - if (nWordsReady) { - for (let offset = 0; offset < nWordsReady; offset += blockSize) { - // Perform concrete-algorithm logic - this._doProcessBlock(dataWords, offset); - } - - // Remove processed words - processedWords = dataWords.splice(0, nWordsReady); - data.sigBytes -= nBytesReady; - } - - // Return processed words - return new WordArray(processedWords, nBytesReady); - } - - /** - * Creates a copy of this object. - * - * @return {Object} The clone. - * - * @example - * - * let clone = bufferedBlockAlgorithm.clone(); - */ - clone() { - const clone = super.clone.call(this); - clone._data = this._data.clone(); - - return clone; - } -} - -/** - * Abstract hasher template. - * - * @property {number} blockSize - * - * The number of 32-bit words this hasher operates on. Default: 16 (512 bits) - */ -export class Hasher extends BufferedBlockAlgorithm { - constructor(cfg) { - super(); - - this.blockSize = 512 / 32; - - /** - * Configuration options. - */ - this.cfg = Object.assign(new Base(), cfg); - - // Set initial values - this.reset(); - } - - /** - * Creates a shortcut function to a hasher's object interface. - * - * @param {Hasher} SubHasher The hasher to create a helper for. - * - * @return {Function} The shortcut function. - * - * @static - * - * @example - * - * let SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256); - */ - static _createHelper(SubHasher) { - return (message, cfg) => new SubHasher(cfg).finalize(message); - } - - /** - * Creates a shortcut function to the HMAC's object interface. - * - * @param {Hasher} SubHasher The hasher to use in this HMAC helper. - * - * @return {Function} The shortcut function. - * - * @static - * - * @example - * - * let HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256); - */ - static _createHmacHelper(SubHasher) { - return (message, key) => new HMAC(SubHasher, key).finalize(message); - } - - /** - * Resets this hasher to its initial state. - * - * @example - * - * hasher.reset(); - */ - reset() { - // Reset data buffer - super.reset.call(this); - - // Perform concrete-hasher logic - this._doReset(); - } - - /** - * Updates this hasher with a message. - * - * @param {WordArray|string} messageUpdate The message to append. - * - * @return {Hasher} This hasher. - * - * @example - * - * hasher.update('message'); - * hasher.update(wordArray); - */ - update(messageUpdate) { - // Append - this._append(messageUpdate); - - // Update the hash - this._process(); - - // Chainable - return this; - } - - /** - * Finalizes the hash computation. - * Note that the finalize operation is effectively a destructive, read-once operation. - * - * @param {WordArray|string} messageUpdate (Optional) A final message update. - * - * @return {WordArray} The hash. - * - * @example - * - * let hash = hasher.finalize(); - * let hash = hasher.finalize('message'); - * let hash = hasher.finalize(wordArray); - */ - finalize(messageUpdate) { - // Final message update - if (messageUpdate) { - this._append(messageUpdate); - } - - // Perform concrete-hasher logic - const hash = this._doFinalize(); - - return hash; - } -} - -/** - * HMAC algorithm. - */ -export class HMAC extends Base { - /** - * Initializes a newly created HMAC. - * - * @param {Hasher} SubHasher The hash algorithm to use. - * @param {WordArray|string} key The secret key. - * - * @example - * - * let hmacHasher =new HMAC(CryptoJS.algo.SHA256, key); - */ - constructor(SubHasher, key) { - super(); - - const hasher = new SubHasher(); - this._hasher = hasher; - - // Convert string to WordArray, else assume WordArray already - let _key = key; - if (typeof _key === 'string') { - _key = Utf8.parse(_key); - } - - // Shortcuts - const hasherBlockSize = hasher.blockSize; - const hasherBlockSizeBytes = hasherBlockSize * 4; - - // Allow arbitrary length keys - if (_key.sigBytes > hasherBlockSizeBytes) { - _key = hasher.finalize(key); - } - - // Clamp excess bits - _key.clamp(); - - // Clone key for inner and outer pads - const oKey = _key.clone(); - this._oKey = oKey; - const iKey = _key.clone(); - this._iKey = iKey; - - // Shortcuts - const oKeyWords = oKey.words; - const iKeyWords = iKey.words; - - // XOR keys with pad constants - for (let i = 0; i < hasherBlockSize; i += 1) { - oKeyWords[i] ^= 0x5c5c5c5c; - iKeyWords[i] ^= 0x36363636; - } - oKey.sigBytes = hasherBlockSizeBytes; - iKey.sigBytes = hasherBlockSizeBytes; - - // Set initial values - this.reset(); - } - - /** - * Resets this HMAC to its initial state. - * - * @example - * - * hmacHasher.reset(); - */ - reset() { - // Shortcut - const hasher = this._hasher; - - // Reset - hasher.reset(); - hasher.update(this._iKey); - } - - /** - * Updates this HMAC with a message. - * - * @param {WordArray|string} messageUpdate The message to append. - * - * @return {HMAC} This HMAC instance. - * - * @example - * - * hmacHasher.update('message'); - * hmacHasher.update(wordArray); - */ - update(messageUpdate) { - this._hasher.update(messageUpdate); - - // Chainable - return this; - } - - /** - * Finalizes the HMAC computation. - * Note that the finalize operation is effectively a destructive, read-once operation. - * - * @param {WordArray|string} messageUpdate (Optional) A final message update. - * - * @return {WordArray} The HMAC. - * - * @example - * - * let hmac = hmacHasher.finalize(); - * let hmac = hmacHasher.finalize('message'); - * let hmac = hmacHasher.finalize(wordArray); - */ - finalize(messageUpdate) { - // Shortcut - const hasher = this._hasher; - - // Compute HMAC - const innerHash = hasher.finalize(messageUpdate); - hasher.reset(); - const hmac = hasher.finalize(this._oKey.clone().concat(innerHash)); - - return hmac; - } -} \ No newline at end of file diff --git a/src/cipher-core.js b/src/core/cipher-core.js similarity index 91% rename from src/cipher-core.js rename to src/core/cipher-core.js index 6b74f0e..77d4f1f 100644 --- a/src/cipher-core.js +++ b/src/core/cipher-core.js @@ -4,13 +4,16 @@ import { Base, WordArray, BufferedBlockAlgorithm -} from './core.js'; +} from '../core/core.js'; import { Base64 -} from './enc-base64.js'; +} from '../encoding/enc-base64.js'; import { EvpKDFAlgo -} from './evpkdf.js'; +} from '../encryption/evpkdf.js'; +import { isString } from '../utils'; +import { Pkcs7 } from '../pad/pad-pkcs7'; + /** * Abstract base cipher template. @@ -106,7 +109,7 @@ export class Cipher extends BufferedBlockAlgorithm { */ static _createHelper(SubCipher) { const selectCipherStrategy = (key) => { - if (typeof key === 'string') { + if (isString(key)) { return PasswordBasedCipher; } return SerializableCipher; @@ -281,7 +284,7 @@ function xorBlock(words, offset, blockSize) { } // XOR blocks - for (let i = 0; i < blockSize; i += 1) { + for (let i = 0; i < blockSize; i++) { _words[offset + i] ^= block[i]; } } @@ -293,7 +296,7 @@ function xorBlock(words, offset, blockSize) { /** * Abstract base CBC mode. */ -export class CBC extends BlockCipherMode {} +export class CBC extends BlockCipherMode { } /** * CBC encryptor. */ @@ -356,68 +359,6 @@ CBC.Decryptor = class extends CBC { } }; -/** - * PKCS #5/7 padding strategy. - */ -export const Pkcs7 = { - /** - * Pads data using the algorithm defined in PKCS #5/7. - * - * @param {WordArray} data The data to pad. - * @param {number} blockSize The multiple that the data should be padded to. - * - * @static - * - * @example - * - * CryptoJS.pad.Pkcs7.pad(wordArray, 4); - */ - pad(data, blockSize) { - // Shortcut - const blockSizeBytes = blockSize * 4; - - // Count padding bytes - const nPaddingBytes = blockSizeBytes - (data.sigBytes % blockSizeBytes); - - // Create padding word - const paddingWord = (nPaddingBytes << 24) | - (nPaddingBytes << 16) | - (nPaddingBytes << 8) | - nPaddingBytes; - - // Create padding - const paddingWords = []; - for (let i = 0; i < nPaddingBytes; i += 4) { - paddingWords.push(paddingWord); - } - const padding =new WordArray(paddingWords, nPaddingBytes); - - // Add padding - data.concat(padding); - }, - - /** - * Unpads data that had been padded using the algorithm defined in PKCS #5/7. - * - * @param {WordArray} data The data to unpad. - * - * @static - * - * @example - * - * CryptoJS.pad.Pkcs7.unpad(wordArray); - */ - unpad(data) { - const _data = data; - - // Get number of padding bytes from last byte - const nPaddingBytes = _data.words[(_data.sigBytes - 1) >>> 2] & 0xff; - - // Remove padding - _data.sigBytes -= nPaddingBytes; - } -}; - /** * Abstract base block cipher template. * @@ -437,7 +378,7 @@ export class BlockCipher extends Cipher { mode: CBC, padding: Pkcs7 }, - cfg, + cfg, )); this.blockSize = 128 / 32; @@ -524,7 +465,7 @@ export class CipherParams extends Base { * * @example * - * let cipherParams =new CipherParams({ + * let cipherParams = new CipherParams({ * ciphertext: ciphertextWordArray, * key: keyWordArray, * iv: ivWordArray, @@ -590,7 +531,7 @@ export const OpenSSLFormatter = { // Format if (salt) { - wordArray =new WordArray([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext); + wordArray = new WordArray([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext); } else { wordArray = ciphertext; } @@ -623,7 +564,7 @@ export const OpenSSLFormatter = { // Test for salt if (ciphertextWords[0] === 0x53616c74 && ciphertextWords[1] === 0x65645f5f) { // Extract salt - salt =new WordArray(ciphertextWords.slice(2, 4)); + salt = new WordArray(ciphertextWords.slice(2, 4)); // Remove salt from ciphertext ciphertextWords.splice(0, 4); @@ -739,7 +680,7 @@ export class SerializableCipher extends Base { * ._parse(ciphertextStringOrParams, format); */ static _parse(ciphertext, format) { - if (typeof ciphertext === 'string') { + if (isString(ciphertext)) { return format.parse(ciphertext, this); } return ciphertext; @@ -755,8 +696,8 @@ export class SerializableCipher extends Base { */ SerializableCipher.cfg = Object.assign( new Base(), { - format: OpenSSLFormatter - }, + format: OpenSSLFormatter +}, ); /** @@ -790,12 +731,12 @@ export const OpenSSLKdf = { } // Derive key and IV - const key =new EvpKDFAlgo({ + const key = new EvpKDFAlgo({ keySize: keySize + ivSize }).compute(password, _salt); // Separate key and IV - const iv =new WordArray(key.words.slice(keySize), ivSize * 4); + const iv = new WordArray(key.words.slice(keySize), ivSize * 4); key.sigBytes = keySize * 4; // Return params diff --git a/src/core/core.js b/src/core/core.js new file mode 100644 index 0000000..e11ffb1 --- /dev/null +++ b/src/core/core.js @@ -0,0 +1,373 @@ +/* eslint-disable no-use-before-define */ + +/** + * Base class for inheritance. + */ +import { Utf8 } from '../encoding/enc-utf8'; +import { Hex } from '../encoding/enc-hax'; +import { isString } from '../utils'; + +export class Base { + + /** + * Copies properties into this object. + * + * @param {Object} properties The properties to mix in. + * + * @example + * + * MyType.mixIn({ + * field: 'value' + * }); + */ + mixIn(properties) { + return Object.assign(this, properties); + } + + /** + * Creates a copy of this object. + * + * @return {Object} The clone. + * + * @example + * + * let clone = instance.clone(); + */ + clone() { + const clone = new this.constructor(); + Object.assign(clone, this); + return clone; + } +} + +/** + * An array of 32-bit words. + * + * @property {Array} words The array of 32-bit words. + * @property {number} sigBytes The number of significant bytes in this word array. + */ +export class WordArray extends Base { + /** + * Initializes a newly created word array. + * + * @param {Array} words (Optional) An array of 32-bit words. + * @param {number} sigBytes (Optional) The number of significant bytes in the words. + * + * @example + * + * let wordArray = new WordArray(); + * let wordArray = new WordArray([0x00010203, 0x04050607]); + * let wordArray = new WordArray([0x00010203, 0x04050607], 6); + */ + constructor(words = [], sigBytes = words.length * 4) { + super(); + + let typedArray = words; + // Convert buffers to uint8 + if (typedArray instanceof ArrayBuffer) { + typedArray = new Uint8Array(typedArray); + } + + // Convert other array views to uint8 + if ( + typedArray instanceof Int8Array + || typedArray instanceof Uint8ClampedArray + || typedArray instanceof Int16Array + || typedArray instanceof Uint16Array + || typedArray instanceof Int32Array + || typedArray instanceof Uint32Array + || typedArray instanceof Float32Array + || typedArray instanceof Float64Array + ) { + typedArray = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); + } + + // Handle Uint8Array + if (typedArray instanceof Uint8Array) { + // Shortcut + const typedArrayByteLength = typedArray.byteLength; + + // Extract bytes + const _words = []; + for (let i = 0; i < typedArrayByteLength; i++) { + _words[i >>> 2] |= typedArray[i] << (24 - (i % 4) * 8); + } + + // Initialize this word array + this.words = _words; + this.sigBytes = typedArrayByteLength; + } else { + // Else call normal init + this.words = words; + this.sigBytes = sigBytes; + } + } + + /** + * Creates a word array filled with random bytes. + * + * @param {number} nBytes The number of random bytes to generate. + * + * @return {WordArray} The random word array. + * + * @static + * + * @example + * + * let wordArray = CryptoJS.lib.WordArray.random(16); + */ + static random(nBytes) { + const words = []; + + const r = (m_w) => { + let _m_w = m_w; + let _m_z = 0x3ade68b1; + const mask = 0xffffffff; + + return () => { + _m_z = (0x9069 * (_m_z & 0xFFFF) + (_m_z >> 0x10)) & mask; + _m_w = (0x4650 * (_m_w & 0xFFFF) + (_m_w >> 0x10)) & mask; + let result = ((_m_z << 0x10) + _m_w) & mask; + result /= 0x100000000; + result += 0.5; + return result * (Math.random() > 0.5 ? 1 : -1); + }; + }; + + for (let i = 0, rcache; i < nBytes; i += 4) { + const _r = r((rcache || Math.random()) * 0x100000000); + + rcache = _r() * 0x3ade67b7; + words.push((_r() * 0x100000000) | 0); + } + + return new WordArray(words, nBytes); + } + + /** + * Converts this word array to a string. + * + * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex + * + * @return {string} The stringified word array. + * + * @example + * + * let string = wordArray + ''; + * let string = wordArray.toString(); + * let string = wordArray.toString(CryptoJS.enc.Utf8); + */ + toString(encoder = Hex) { + return encoder.stringify(this); + } + + /** + * Concatenates a word array to this word array. + * + * @param {WordArray} wordArray The word array to append. + * + * @return {WordArray} This word array. + * + * @example + * + * wordArray1.concat(wordArray2); + */ + concat(wordArray) { + // Shortcuts + const thisWords = this.words; + const thatWords = wordArray.words; + const thisSigBytes = this.sigBytes; + const thatSigBytes = wordArray.sigBytes; + + // Clamp excess bits + this.clamp(); + + // Concat + if (thisSigBytes % 4) { + // Copy one byte at a time + for (let i = 0; i < thatSigBytes; i++) { + const thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8); + } + } else { + // Copy one word at a time + for (let i = 0; i < thatSigBytes; i += 4) { + thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2]; + } + } + this.sigBytes += thatSigBytes; + + // Chainable + return this; + } + + /** + * Removes insignificant bits. + * + * @example + * + * wordArray.clamp(); + */ + clamp() { + // Shortcuts + const { + words, + sigBytes + } = this; + + // Clamp + words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8); + words.length = Math.ceil(sigBytes / 4); + } + + /** + * Creates a copy of this word array. + * + * @return {WordArray} The clone. + * + * @example + * + * let clone = wordArray.clone(); + */ + clone() { + const clone = super.clone.call(this); + clone.words = this.words.slice(0); + + return clone; + } +} + + +/** + * Abstract buffered block algorithm template. + * + * The property blockSize must be implemented in a concrete subtype. + * + * @property {number} _minBufferSize + * + * The number of blocks that should be kept unprocessed in the buffer. Default: 0 + */ +export class BufferedBlockAlgorithm extends Base { + constructor() { + super(); + this._minBufferSize = 0; + } + + /** + * Resets this block algorithm's data buffer to its initial state. + * + * @example + * + * bufferedBlockAlgorithm.reset(); + */ + reset() { + // Initial values + this._data = new WordArray(); + this._nDataBytes = 0; + } + + /** + * Adds new data to this block algorithm's buffer. + * + * @param {WordArray|string} data + * + * The data to append. Strings are converted to a WordArray using UTF-8. + * + * @example + * + * bufferedBlockAlgorithm._append('data'); + * bufferedBlockAlgorithm._append(wordArray); + */ + _append(data) { + let m_data = data; + + // Convert string to WordArray, else assume WordArray already + if (isString(m_data)) { + m_data = Utf8.parse(m_data); + } + + // Append + this._data.concat(m_data); + this._nDataBytes += m_data.sigBytes; + } + + /** + * Processes available data blocks. + * + * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype. + * + * @param {boolean} doFlush Whether all blocks and partial blocks should be processed. + * + * @return {WordArray} The processed data. + * + * @example + * + * let processedData = bufferedBlockAlgorithm._process(); + * let processedData = bufferedBlockAlgorithm._process(!!'flush'); + */ + _process(doFlush) { + let processedWords; + + // Shortcuts + const { + _data: data, + blockSize + } = this; + const dataWords = data.words; + const dataSigBytes = data.sigBytes; + const blockSizeBytes = blockSize * 4; + + // Count blocks ready + let nBlocksReady = dataSigBytes / blockSizeBytes; + if (doFlush) { + // Round up to include partial blocks + nBlocksReady = Math.ceil(nBlocksReady); + } else { + // Round down to include only full blocks, + // less the number of blocks that must remain in the buffer + nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); + } + + // Count words ready + const nWordsReady = nBlocksReady * blockSize; + + // Count bytes ready + const nBytesReady = Math.min(nWordsReady * 4, dataSigBytes); + + // Process blocks + if (nWordsReady) { + for (let offset = 0; offset < nWordsReady; offset += blockSize) { + // Perform concrete-algorithm logic + this._doProcessBlock(dataWords, offset); + } + + // Remove processed words + processedWords = dataWords.splice(0, nWordsReady); + data.sigBytes -= nBytesReady; + } + + // Return processed words + return new WordArray(processedWords, nBytesReady); + } + + /** + * Creates a copy of this object. + * + * @return {Object} The clone. + * + * @example + * + * let clone = bufferedBlockAlgorithm.clone(); + */ + clone() { + const clone = super.clone.call(this); + clone._data = this._data.clone(); + + return clone; + } +} + + + + + diff --git a/src/core/hasher.js b/src/core/hasher.js new file mode 100644 index 0000000..44134f9 --- /dev/null +++ b/src/core/hasher.js @@ -0,0 +1,128 @@ +import {BufferedBlockAlgorithm, Base} from './core'; +import {HMAC} from '../algo/hmac/hmac'; + + +/** + * Abstract hasher template. + * + * @property {number} blockSize + * + * The number of 32-bit words this hasher operates on. Default: 16 (512 bits) + */ + +export class Hasher extends BufferedBlockAlgorithm { + + + + constructor(cfg) { + super(); + + this.blockSize = 512 / 32; + + /** + * Configuration options. + */ + this.cfg = Object.assign(new Base(), cfg); + + // Set initial values + this.reset(); + } + + /** + * Creates a shortcut function to a hasher's object interface. + * + * @param {Hasher} SubHasher The hasher to create a helper for. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * let SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256); + */ + static _createHelper(SubHasher) { + return (message, cfg) => new SubHasher(cfg).finalize(message); + } + + /** + * Creates a shortcut function to the HMAC's object interface. + * + * @param {Hasher} SubHasher The hasher to use in this HMAC helper. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * let HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256); + */ + static _createHmacHelper(SubHasher) { + return (message, key) => new HMAC(SubHasher, key).finalize(message); + } + + /** + * Resets this hasher to its initial state. + * + * @example + * + * hasher.reset(); + */ + reset() { + // Reset data buffer + super.reset.call(this); + + // Perform concrete-hasher logic + this._doReset(); + } + + /** + * Updates this hasher with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {Hasher} This hasher. + * + * @example + * + * hasher.update('message'); + * hasher.update(wordArray); + */ + update(messageUpdate) { + // Append + this._append(messageUpdate); + + // Update the hash + this._process(); + + // Chainable + return this; + } + + /** + * Finalizes the hash computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The hash. + * + * @example + * + * let hash = hasher.finalize(); + * let hash = hasher.finalize('message'); + * let hash = hasher.finalize(wordArray); + */ + finalize(messageUpdate) { + // Final message update + if (messageUpdate) { + this._append(messageUpdate); + } + + // Perform concrete-hasher logic + const hash = this._doFinalize(); + + return hash; + } + } \ No newline at end of file diff --git a/src/x64-core.js b/src/core/x64-core.js similarity index 90% rename from src/x64-core.js rename to src/core/x64-core.js index 8fbcf22..22d601b 100644 --- a/src/x64-core.js +++ b/src/core/x64-core.js @@ -1,7 +1,7 @@ import { Base, WordArray -} from './core.js'; +} from '../core/core.js'; const X32WordArray = WordArray; @@ -42,14 +42,14 @@ export class X64WordArray extends Base { * * @example * - * let wordArray =new X64WordArray(); + * let wordArray = new X64WordArray(); * - * let wordArray =new X64WordArray([ + * let wordArray = new X64WordArray([ * new x64Word(0x00010203, 0x04050607), * new x64Word(0x18191a1b, 0x1c1d1e1f) * ]); * - * let wordArray =new X64WordArray([ + * let wordArray = new X64WordArray([ * new x64Word(0x00010203, 0x04050607), * new x64Word(0x18191a1b, 0x1c1d1e1f) * ], 10); @@ -77,7 +77,7 @@ export class X64WordArray extends Base { // Convert const x32Words = []; - for (let i = 0; i < x64WordsLength; i += 1) { + for (let i = 0; i < x64WordsLength; i++) { const x64Word = x64Words[i]; x32Words.push(x64Word.high); x32Words.push(x64Word.low); @@ -104,7 +104,7 @@ export class X64WordArray extends Base { // Clone each X64Word object const wordsLength = words.length; - for (let i = 0; i < wordsLength; i += 1) { + for (let i = 0; i < wordsLength; i++) { words[i] = words[i].clone(); } diff --git a/src/enc-base64.js b/src/encoding/enc-base64.js similarity index 90% rename from src/enc-base64.js rename to src/encoding/enc-base64.js index da41ad3..b22aee7 100644 --- a/src/enc-base64.js +++ b/src/encoding/enc-base64.js @@ -1,17 +1,17 @@ import { WordArray -} from './core.js'; +} from '../core/core.js'; const parseLoop = (base64Str, base64StrLength, reverseMap) => { const words = []; let nBytes = 0; - for (let i = 0; i < base64StrLength; i += 1) { + for (let i = 0; i < base64StrLength; i++) { if (i % 4) { const bits1 = reverseMap[base64Str.charCodeAt(i - 1)] << ((i % 4) * 2); const bits2 = reverseMap[base64Str.charCodeAt(i)] >>> (6 - (i % 4) * 2); const bitsCombined = bits1 | bits2; words[nBytes >>> 2] |= bitsCombined << (24 - (nBytes % 4) * 8); - nBytes += 1; + nBytes++; } } return new WordArray(words, nBytes); @@ -36,7 +36,10 @@ export const Base64 = { */ stringify(wordArray) { // Shortcuts - const { words, sigBytes } = wordArray; + const { + words, + sigBytes + } = wordArray; const map = this._map; // Clamp excess bits @@ -51,7 +54,7 @@ export const Base64 = { const triplet = (byte1 << 16) | (byte2 << 8) | byte3; - for (let j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j += 1) { + for (let j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); } } @@ -89,7 +92,7 @@ export const Base64 = { if (!reverseMap) { this._reverseMap = []; reverseMap = this._reverseMap; - for (let j = 0; j < map.length; j += 1) { + for (let j = 0; j < map.length; j++) { reverseMap[map.charCodeAt(j)] = j; } } @@ -108,4 +111,4 @@ export const Base64 = { }, _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' -}; +}; \ No newline at end of file diff --git a/src/encoding/enc-hax.js b/src/encoding/enc-hax.js new file mode 100644 index 0000000..ad333f4 --- /dev/null +++ b/src/encoding/enc-hax.js @@ -0,0 +1,65 @@ +import { + WordArray + } from '../core/core.js'; + + /** + * Hex encoding strategy. + */ +export const Hex = { + /** + * Converts a word array to a hex string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The hex string. + * + * @static + * + * @example + * + * let hexString = CryptoJS.enc.Hex.stringify(wordArray); + */ + stringify(wordArray) { + // Shortcuts + const { + words, + sigBytes + } = wordArray; + + // Convert + const hexChars = []; + for (let i = 0; i < sigBytes; i++) { + const bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + hexChars.push((bite >>> 4).toString(16)); + hexChars.push((bite & 0x0f).toString(16)); + } + + return hexChars.join(''); + }, + + /** + * Converts a hex string to a word array. + * + * @param {string} hexStr The hex string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * let wordArray = CryptoJS.enc.Hex.parse(hexString); + */ + parse(hexStr) { + // Shortcut + const hexStrLength = hexStr.length; + + // Convert + const words = []; + for (let i = 0; i < hexStrLength; i += 2) { + words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); + } + + return new WordArray(words, hexStrLength / 2); + } + }; \ No newline at end of file diff --git a/src/encoding/enc-latin1.js b/src/encoding/enc-latin1.js new file mode 100644 index 0000000..ba05f20 --- /dev/null +++ b/src/encoding/enc-latin1.js @@ -0,0 +1,64 @@ +import { + WordArray + } from '../core/core.js'; + +/** + * Latin1 encoding strategy. + */ + export const Latin1 = { + /** + * Converts a word array to a Latin1 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Latin1 string. + * + * @static + * + * @example + * + * let latin1String = CryptoJS.enc.Latin1.stringify(wordArray); + */ + stringify(wordArray) { + // Shortcuts + const { + words, + sigBytes + } = wordArray; + + // Convert + const latin1Chars = []; + for (let i = 0; i < sigBytes; i++) { + const bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + latin1Chars.push(String.fromCharCode(bite)); + } + + return latin1Chars.join(''); + }, + + /** + * Converts a Latin1 string to a word array. + * + * @param {string} latin1Str The Latin1 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * let wordArray = CryptoJS.enc.Latin1.parse(latin1String); + */ + parse(latin1Str) { + // Shortcut + const latin1StrLength = latin1Str.length; + + // Convert + const words = []; + for (let i = 0; i < latin1StrLength; i++) { + words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8); + } + + return new WordArray(words, latin1StrLength); + } + }; \ No newline at end of file diff --git a/src/encoding/enc-utf16.js b/src/encoding/enc-utf16.js new file mode 100644 index 0000000..4ac5190 --- /dev/null +++ b/src/encoding/enc-utf16.js @@ -0,0 +1,122 @@ +import { + WordArray +} from '../core/core'; + +const swapEndian = word => ((word << 8) & 0xff00ff00) | ((word >>> 8) & 0x00ff00ff); + +/** + * UTF-16 BE encoding strategy. + */ +export const Utf16BE = { + /** + * Converts a word array to a UTF-16 BE string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-16 BE string. + * + * @static + * + * @example + * + * const utf16String = CryptoJS.enc.Utf16.stringify(wordArray); + */ + stringify(wordArray) { + // Shortcuts + const { words, sigBytes } = wordArray; + + // Convert + const utf16Chars = []; + for (let i = 0; i < sigBytes; i += 2) { + const codePoint = (words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff; + utf16Chars.push(String.fromCharCode(codePoint)); + } + + return utf16Chars.join(''); + }, + + /** + * Converts a UTF-16 BE string to a word array. + * + * @param {string} utf16Str The UTF-16 BE string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * const wordArray = CryptoJS.enc.Utf16.parse(utf16String); + */ + parse(utf16Str) { + // Shortcut + const utf16StrLength = utf16Str.length; + + // Convert + const words = []; + for (let i = 0; i < utf16StrLength; i++) { + words[i >>> 1] |= utf16Str.charCodeAt(i) << (16 - (i % 2) * 16); + } + + return new WordArray(words, utf16StrLength * 2); + } +}; +export const Utf16 = Utf16BE; + +/** + * UTF-16 LE encoding strategy. + */ +export const Utf16LE = { + /** + * Converts a word array to a UTF-16 LE string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-16 LE string. + * + * @static + * + * @example + * + * const utf16Str = CryptoJS.enc.Utf16LE.stringify(wordArray); + */ + stringify(wordArray) { + // Shortcuts + const { words, sigBytes } = wordArray; + + // Convert + const utf16Chars = []; + for (let i = 0; i < sigBytes; i += 2) { + const codePoint = swapEndian((words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff); + utf16Chars.push(String.fromCharCode(codePoint)); + } + + return utf16Chars.join(''); + }, + + /** + * Converts a UTF-16 LE string to a word array. + * + * @param {string} utf16Str The UTF-16 LE string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * const wordArray = CryptoJS.enc.Utf16LE.parse(utf16Str); + */ + parse(utf16Str) { + // Shortcut + const utf16StrLength = utf16Str.length; + + // Convert + const words = []; + for (let i = 0; i < utf16StrLength; i++) { + words[i >>> 1] |= swapEndian(utf16Str.charCodeAt(i) << (16 - (i % 2) * 16)); + } + + return new WordArray(words, utf16StrLength * 2); + } +}; diff --git a/src/encoding/enc-utf8.js b/src/encoding/enc-utf8.js new file mode 100644 index 0000000..e3a181e --- /dev/null +++ b/src/encoding/enc-utf8.js @@ -0,0 +1,47 @@ +import { + WordArray +} from '../core/core.js'; +import { Latin1 } from './enc-latin1.js'; + +/** +* UTF-8 encoding strategy. +*/ +export const Utf8 = { + /** + * Converts a word array to a UTF-8 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-8 string. + * + * @static + * + * @example + * + * let utf8String = CryptoJS.enc.Utf8.stringify(wordArray); + */ + stringify(wordArray) { + try { + return decodeURIComponent(escape(Latin1.stringify(wordArray))); + } catch (e) { + throw new Error('Malformed UTF-8 data'); + } + }, + + /** + * Converts a UTF-8 string to a word array. + * + * @param {string} utf8Str The UTF-8 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * let wordArray = CryptoJS.enc.Utf8.parse(utf8String); + */ + parse(utf8Str) { + return Latin1.parse(unescape(encodeURIComponent(utf8Str))); + } +}; \ No newline at end of file diff --git a/src/encryption/aes.js b/src/encryption/aes.js new file mode 100644 index 0000000..eee520d --- /dev/null +++ b/src/encryption/aes.js @@ -0,0 +1,283 @@ +import { + BlockCipher +} from '../core/cipher-core.js'; + +// Lookup tables +const _SBOX = []; +const INV_SBOX = []; +const _SUB_MIX_0 = []; +const _SUB_MIX_1 = []; +const _SUB_MIX_2 = []; +const _SUB_MIX_3 = []; +const INV_SUB_MIX_0 = []; +const INV_SUB_MIX_1 = []; +const INV_SUB_MIX_2 = []; +const INV_SUB_MIX_3 = []; + +// Compute lookup tables + +// Compute double table +const d = []; +for (let i = 0; i < 256; i++) { + if (i < 128) { + d[i] = i << 1; + } else { + d[i] = (i << 1) ^ 0x11b; + } +} + +// Walk GF(2^8) +let x = 0; +let xi = 0; +for (let i = 0; i < 256; i++) { + // Compute sbox + let sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4); + sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63; + _SBOX[x] = sx; + INV_SBOX[sx] = x; + + // Compute multiplication + const x2 = d[x]; + const x4 = d[x2]; + const x8 = d[x4]; + + // Compute sub bytes, mix columns tables + let t = (d[sx] * 0x101) ^ (sx * 0x1010100); + _SUB_MIX_0[x] = (t << 24) | (t >>> 8); + _SUB_MIX_1[x] = (t << 16) | (t >>> 16); + _SUB_MIX_2[x] = (t << 8) | (t >>> 24); + _SUB_MIX_3[x] = t; + + // Compute inv sub bytes, inv mix columns tables + t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100); + INV_SUB_MIX_0[sx] = (t << 24) | (t >>> 8); + INV_SUB_MIX_1[sx] = (t << 16) | (t >>> 16); + INV_SUB_MIX_2[sx] = (t << 8) | (t >>> 24); + INV_SUB_MIX_3[sx] = t; + + // Compute next counter + if (!x) { + xi = 1; + x = xi; + } else { + x = x2 ^ d[d[d[x8 ^ x2]]]; + xi ^= d[d[xi]]; + } +} + +// Precomputed Rcon lookup +const RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; + +/** + * AES block cipher algorithm. + */ +export class AESAlgo extends BlockCipher { + constructor(...args) { + super(...args); + + this.keySize = 256 / 32; + } + + _doReset() { + let t; + + // Skip reset of nRounds has been set before and key did not change + if (this._nRounds && this._keyPriorReset === this._key) { + return; + } + + // Shortcuts + this._keyPriorReset = this._key; + const key = this._keyPriorReset; + const keyWords = key.words; + const keySize = key.sigBytes / 4; + + // Compute number of rounds + this._nRounds = keySize + 6; + const nRounds = this._nRounds; + + // Compute number of key schedule rows + const ksRows = (nRounds + 1) * 4; + + // Compute key schedule + this._keySchedule = []; + const keySchedule = this._keySchedule; + for (let ksRow = 0; ksRow < ksRows; ksRow++) { + if (ksRow < keySize) { + keySchedule[ksRow] = keyWords[ksRow]; + } else { + t = keySchedule[ksRow - 1]; + + if (!(ksRow % keySize)) { + // Rot word + t = (t << 8) | (t >>> 24); + + // Sub word + t = (_SBOX[t >>> 24] << 24) + | (_SBOX[(t >>> 16) & 0xff] << 16) + | (_SBOX[(t >>> 8) & 0xff] << 8) + | _SBOX[t & 0xff]; + + // Mix Rcon + t ^= RCON[(ksRow / keySize) | 0] << 24; + } else if (keySize > 6 && ksRow % keySize === 4) { + // Sub word + t = (_SBOX[t >>> 24] << 24) + | (_SBOX[(t >>> 16) & 0xff] << 16) + | (_SBOX[(t >>> 8) & 0xff] << 8) + | _SBOX[t & 0xff]; + } + + keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t; + } + } + + // Compute inv key schedule + this._invKeySchedule = []; + const invKeySchedule = this._invKeySchedule; + for (let invKsRow = 0; invKsRow < ksRows; invKsRow++) { + const ksRow = ksRows - invKsRow; + + if (invKsRow % 4) { + t = keySchedule[ksRow]; + } else { + t = keySchedule[ksRow - 4]; + } + + if (invKsRow < 4 || ksRow <= 4) { + invKeySchedule[invKsRow] = t; + } else { + invKeySchedule[invKsRow] = INV_SUB_MIX_0[_SBOX[t >>> 24]] + ^ INV_SUB_MIX_1[_SBOX[(t >>> 16) & 0xff]] + ^ INV_SUB_MIX_2[_SBOX[(t >>> 8) & 0xff]] + ^ INV_SUB_MIX_3[_SBOX[t & 0xff]]; + } + } + } + + encryptBlock(M, offset) { + this._doCryptBlock( + M, offset, this._keySchedule, _SUB_MIX_0, _SUB_MIX_1, _SUB_MIX_2, _SUB_MIX_3, _SBOX, + ); + } + + decryptBlock(M, offset) { + const _M = M; + + // Swap 2nd and 4th rows + let t = _M[offset + 1]; + _M[offset + 1] = _M[offset + 3]; + _M[offset + 3] = t; + + this._doCryptBlock( + _M, + offset, + this._invKeySchedule, + INV_SUB_MIX_0, + INV_SUB_MIX_1, + INV_SUB_MIX_2, + INV_SUB_MIX_3, + INV_SBOX, + ); + + // Inv swap 2nd and 4th rows + t = _M[offset + 1]; + _M[offset + 1] = _M[offset + 3]; + _M[offset + 3] = t; + } + + _doCryptBlock(M, offset, keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX) { + const _M = M; + + // Shortcut + const nRounds = this._nRounds; + + // Get input, add round key + let s0 = _M[offset] ^ keySchedule[0]; + let s1 = _M[offset + 1] ^ keySchedule[1]; + let s2 = _M[offset + 2] ^ keySchedule[2]; + let s3 = _M[offset + 3] ^ keySchedule[3]; + + // Key schedule row counter + let ksRow = 4; + + // Rounds + for (let round = 1; round < nRounds; round++) { + // Shift rows, sub bytes, mix columns, add round key + const t0 = SUB_MIX_0[s0 >>> 24] + ^ SUB_MIX_1[(s1 >>> 16) & 0xff] + ^ SUB_MIX_2[(s2 >>> 8) & 0xff] + ^ SUB_MIX_3[s3 & 0xff] + ^ keySchedule[ksRow]; + ksRow++; + const t1 = SUB_MIX_0[s1 >>> 24] + ^ SUB_MIX_1[(s2 >>> 16) & 0xff] + ^ SUB_MIX_2[(s3 >>> 8) & 0xff] + ^ SUB_MIX_3[s0 & 0xff] + ^ keySchedule[ksRow]; + ksRow++; + const t2 = SUB_MIX_0[s2 >>> 24] + ^ SUB_MIX_1[(s3 >>> 16) & 0xff] + ^ SUB_MIX_2[(s0 >>> 8) & 0xff] + ^ SUB_MIX_3[s1 & 0xff] + ^ keySchedule[ksRow]; + ksRow++; + const t3 = SUB_MIX_0[s3 >>> 24] + ^ SUB_MIX_1[(s0 >>> 16) & 0xff] + ^ SUB_MIX_2[(s1 >>> 8) & 0xff] + ^ SUB_MIX_3[s2 & 0xff] + ^ keySchedule[ksRow]; + ksRow++; + + // Update state + s0 = t0; + s1 = t1; + s2 = t2; + s3 = t3; + } + + // Shift rows, sub bytes, add round key + const t0 = ( + (SBOX[s0 >>> 24] << 24) + | (SBOX[(s1 >>> 16) & 0xff] << 16) + | (SBOX[(s2 >>> 8) & 0xff] << 8) + | SBOX[s3 & 0xff] + ) ^ keySchedule[ksRow]; + ksRow++; + const t1 = ( + (SBOX[s1 >>> 24] << 24) + | (SBOX[(s2 >>> 16) & 0xff] << 16) + | (SBOX[(s3 >>> 8) & 0xff] << 8) + | SBOX[s0 & 0xff] + ) ^ keySchedule[ksRow]; + ksRow++; + const t2 = ( + (SBOX[s2 >>> 24] << 24) + | (SBOX[(s3 >>> 16) & 0xff] << 16) + | (SBOX[(s0 >>> 8) & 0xff] << 8) + | SBOX[s1 & 0xff] + ) ^ keySchedule[ksRow]; + ksRow++; + const t3 = ( + (SBOX[s3 >>> 24] << 24) + | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff] + ) ^ keySchedule[ksRow]; + ksRow++; + + // Set output + _M[offset] = t0; + _M[offset + 1] = t1; + _M[offset + 2] = t2; + _M[offset + 3] = t3; + } +} + +/** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.AES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.AES.decrypt(ciphertext, key, cfg); + */ +export const AES = BlockCipher._createHelper(AESAlgo); diff --git a/src/evpkdf.js b/src/encryption/evpkdf.js similarity index 92% rename from src/evpkdf.js rename to src/encryption/evpkdf.js index 72304fe..3f06b01 100644 --- a/src/evpkdf.js +++ b/src/encryption/evpkdf.js @@ -1,8 +1,8 @@ import { Base, WordArray -} from './core.js'; -import { MD5Algo } from './md5.js'; +} from '../core/core.js'; +import { MD5Algo } from '../algo/hash/md5.js'; /** * This key derivation function is meant to conform with EVP_BytesToKey. @@ -37,7 +37,7 @@ export class EvpKDFAlgo extends Base { hasher: MD5Algo, iterations: 1 }, - cfg, + cfg ); } @@ -60,10 +60,10 @@ export class EvpKDFAlgo extends Base { const { cfg } = this; // Init hasher - const hasher =new cfg.hasher(); + const hasher = new cfg.hasher(); // Initial values - const derivedKey =new WordArray(); + const derivedKey = new WordArray(); // Shortcuts const derivedKeyWords = derivedKey.words; @@ -78,7 +78,7 @@ export class EvpKDFAlgo extends Base { hasher.reset(); // Iterations - for (let i = 1; i < iterations; i += 1) { + for (let i = 1; i < iterations; i++) { block = hasher.finalize(block); hasher.reset(); } diff --git a/src/encryption/rabbit-legacy.js b/src/encryption/rabbit-legacy.js new file mode 100644 index 0000000..8d1c02c --- /dev/null +++ b/src/encryption/rabbit-legacy.js @@ -0,0 +1,175 @@ +import { + StreamCipher +} from '../core/cipher-core.js'; + +// Reusable objects +const S = []; +const C_ = []; +const G = []; + +function nextState() { + // Shortcuts + const X = this._X; + const C = this._C; + + // Save old counter values + for (let i = 0; i < 8; i++) { + C_[i] = C[i]; + } + + // Calculate new counter values + C[0] = (C[0] + 0x4d34d34d + this._b) | 0; + C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; + C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; + C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; + C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; + C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; + C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; + C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; + this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; + + // Calculate the g-values + for (let i = 0; i < 8; i++) { + const gx = X[i] + C[i]; + + // Construct high and low argument for squaring + const ga = gx & 0xffff; + const gb = gx >>> 16; + + // Calculate high and low result of squaring + const gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; + const gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); + + // High XOR low + G[i] = gh ^ gl; + } + + // Calculate new state values + X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; + X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; + X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; + X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; + X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; + X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; + X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; + X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; +} + +/** + * Rabbit stream cipher algorithm. + * + * This is a legacy version that neglected to convert the key to little-endian. + * This error doesn't affect the cipher's security, + * but it does affect its compatibility with other implementations. + */ +export class RabbitLegacyAlgo extends StreamCipher { + constructor(...args) { + super(...args); + + this.blockSize = 128 / 32; + this.ivSize = 64 / 32; + } + + _doReset() { + // Shortcuts + const K = this._key.words; + const { iv } = this.cfg; + + // Generate initial state values + this._X = [ + K[0], (K[3] << 16) | (K[2] >>> 16), + K[1], (K[0] << 16) | (K[3] >>> 16), + K[2], (K[1] << 16) | (K[0] >>> 16), + K[3], (K[2] << 16) | (K[1] >>> 16), + ]; + const X = this._X; + + // Generate initial counter values + this._C = [ + (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), + (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), + (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), + (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff), + ]; + const C = this._C; + + // Carry bit + this._b = 0; + + // Iterate the system four times + for (let i = 0; i < 4; i++) { + nextState.call(this); + } + + // Modify the counters + for (let i = 0; i < 8; i++) { + C[i] ^= X[(i + 4) & 7]; + } + + // IV setup + if (iv) { + // Shortcuts + const IV = iv.words; + const IV_0 = IV[0]; + const IV_1 = IV[1]; + + // Generate four subvectors + const i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) + | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); + const i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) + | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); + const i1 = (i0 >>> 16) | (i2 & 0xffff0000); + const i3 = (i2 << 16) | (i0 & 0x0000ffff); + + // Modify counter values + C[0] ^= i0; + C[1] ^= i1; + C[2] ^= i2; + C[3] ^= i3; + C[4] ^= i0; + C[5] ^= i1; + C[6] ^= i2; + C[7] ^= i3; + + // Iterate the system four times + for (let i = 0; i < 4; i++) { + nextState.call(this); + } + } + } + + _doProcessBlock(M, offset) { + const _M = M; + + // Shortcut + const X = this._X; + + // Iterate the system + nextState.call(this); + + // Generate four keystream words + S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); + S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); + S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); + S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); + + for (let i = 0; i < 4; i++) { + // Swap endian + S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) + | (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); + + // Encrypt + _M[offset + i] ^= S[i]; + } + } +} + +/** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RabbitLegacy.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RabbitLegacy.decrypt(ciphertext, key, cfg); + */ +export const RabbitLegacy = StreamCipher._createHelper(RabbitLegacyAlgo); diff --git a/src/encryption/rabbit.js b/src/encryption/rabbit.js new file mode 100644 index 0000000..83e1917 --- /dev/null +++ b/src/encryption/rabbit.js @@ -0,0 +1,177 @@ +import { + StreamCipher +} from '../core/cipher-core.js'; + +// Reusable objects +const S = []; +const C_ = []; +const G = []; + +function nextState() { + // Shortcuts + const X = this._X; + const C = this._C; + + // Save old counter values + for (let i = 0; i < 8; i++) { + C_[i] = C[i]; + } + + // Calculate new counter values + C[0] = (C[0] + 0x4d34d34d + this._b) | 0; + C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; + C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; + C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; + C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; + C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; + C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; + C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; + this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; + + // Calculate the g-values + for (let i = 0; i < 8; i++) { + const gx = X[i] + C[i]; + + // Construct high and low argument for squaring + const ga = gx & 0xffff; + const gb = gx >>> 16; + + // Calculate high and low result of squaring + const gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; + const gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); + + // High XOR low + G[i] = gh ^ gl; + } + + // Calculate new state values + X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; + X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; + X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; + X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; + X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; + X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; + X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; + X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; +} + +/** + * Rabbit stream cipher algorithm + */ +export class RabbitAlgo extends StreamCipher { + constructor(...args) { + super(...args); + + this.blockSize = 128 / 32; + this.ivSize = 64 / 32; + } + + _doReset() { + // Shortcuts + const K = this._key.words; + const { iv } = this.cfg; + + // Swap endian + for (let i = 0; i < 4; i++) { + K[i] = (((K[i] << 8) | (K[i] >>> 24)) & 0x00ff00ff) + | (((K[i] << 24) | (K[i] >>> 8)) & 0xff00ff00); + } + + // Generate initial state values + this._X = [ + K[0], (K[3] << 16) | (K[2] >>> 16), + K[1], (K[0] << 16) | (K[3] >>> 16), + K[2], (K[1] << 16) | (K[0] >>> 16), + K[3], (K[2] << 16) | (K[1] >>> 16), + ]; + const X = this._X; + + // Generate initial counter values + this._C = [ + (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), + (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), + (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), + (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff), + ]; + const C = this._C; + + // Carry bit + this._b = 0; + + // Iterate the system four times + for (let i = 0; i < 4; i++) { + nextState.call(this); + } + + // Modify the counters + for (let i = 0; i < 8; i++) { + C[i] ^= X[(i + 4) & 7]; + } + + // IV setup + if (iv) { + // Shortcuts + const IV = iv.words; + const IV_0 = IV[0]; + const IV_1 = IV[1]; + + // Generate four subvectors + const i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) + | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); + const i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) + | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); + const i1 = (i0 >>> 16) | (i2 & 0xffff0000); + const i3 = (i2 << 16) | (i0 & 0x0000ffff); + + // Modify counter values + C[0] ^= i0; + C[1] ^= i1; + C[2] ^= i2; + C[3] ^= i3; + C[4] ^= i0; + C[5] ^= i1; + C[6] ^= i2; + C[7] ^= i3; + + // Iterate the system four times + for (let i = 0; i < 4; i++) { + nextState.call(this); + } + } + } + + _doProcessBlock(M, offset) { + const _M = M; + + // Shortcut + const X = this._X; + + // Iterate the system + nextState.call(this); + + // Generate four keystream words + S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); + S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); + S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); + S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); + + for (let i = 0; i < 4; i++) { + // Swap endian + S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) + | (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); + + // Encrypt + _M[offset + i] ^= S[i]; + } + } +} + +/** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.Rabbit.encrypt(message, key, cfg); + * var plaintext = CryptoJS.Rabbit.decrypt(ciphertext, key, cfg); + */ +export const Rabbit = StreamCipher._createHelper(RabbitAlgo); diff --git a/src/encryption/rc4.js b/src/encryption/rc4.js new file mode 100644 index 0000000..aa15044 --- /dev/null +++ b/src/encryption/rc4.js @@ -0,0 +1,124 @@ +import { + StreamCipher +} from '../core/cipher-core.js'; + +function generateKeystreamWord() { + // Shortcuts + const S = this._S; + let i = this._i; + let j = this._j; + + // Generate keystream word + let keystreamWord = 0; + for (let n = 0; n < 4; n++) { + i = (i + 1) % 256; + j = (j + S[i]) % 256; + + // Swap + const t = S[i]; + S[i] = S[j]; + S[j] = t; + + keystreamWord |= S[(S[i] + S[j]) % 256] << (24 - n * 8); + } + + // Update counters + this._i = i; + this._j = j; + + return keystreamWord; +} + +/** + * RC4 stream cipher algorithm. + */ +export class RC4Algo extends StreamCipher { + constructor(...args) { + super(...args); + + this.keySize = 256 / 32; + this.ivSize = 0; + } + + _doReset() { + // Shortcuts + const key = this._key; + const keyWords = key.words; + const keySigBytes = key.sigBytes; + + // Init sbox + this._S = []; + const S = this._S; + for (let i = 0; i < 256; i++) { + S[i] = i; + } + + // Key setup + for (let i = 0, j = 0; i < 256; i++) { + const keyByteIndex = i % keySigBytes; + const keyByte = (keyWords[keyByteIndex >>> 2] >>> (24 - (keyByteIndex % 4) * 8)) & 0xff; + + j = (j + S[i] + keyByte) % 256; + + // Swap + const t = S[i]; + S[i] = S[j]; + S[j] = t; + } + + // Counters + this._j = 0; + this._i = this._j; + } + + _doProcessBlock(M, offset) { + const _M = M; + + _M[offset] ^= generateKeystreamWord.call(this); + } +} + +/** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4.decrypt(ciphertext, key, cfg); + */ +export const RC4 = StreamCipher._createHelper(RC4Algo); + +/** + * Modified RC4 stream cipher algorithm. + */ +export class RC4DropAlgo extends RC4Algo { + constructor(...args) { + super(...args); + + /** + * Configuration options. + * + * @property {number} drop The number of keystream words to drop. Default 192 + */ + Object.assign(this.cfg, { drop: 192 }); + } + + _doReset() { + super._doReset.call(this); + + // Drop + for (let i = this.cfg.drop; i > 0; i--) { + generateKeystreamWord.call(this); + } + } +} + +/** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4Drop.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4Drop.decrypt(ciphertext, key, cfg); + */ +export const RC4Drop = StreamCipher._createHelper(RC4DropAlgo); diff --git a/src/encryption/tripledes.js b/src/encryption/tripledes.js new file mode 100644 index 0000000..678d383 --- /dev/null +++ b/src/encryption/tripledes.js @@ -0,0 +1,759 @@ +import { + WordArray +} from '../core/core.js'; +import { + BlockCipher +} from '../core/cipher-core.js'; + +// Permuted Choice 1 constants +const PC1 = [ + 57, 49, 41, 33, 25, 17, 9, 1, + 58, 50, 42, 34, 26, 18, 10, 2, + 59, 51, 43, 35, 27, 19, 11, 3, + 60, 52, 44, 36, 63, 55, 47, 39, + 31, 23, 15, 7, 62, 54, 46, 38, + 30, 22, 14, 6, 61, 53, 45, 37, + 29, 21, 13, 5, 28, 20, 12, 4, +]; + +// Permuted Choice 2 constants +const PC2 = [ + 14, 17, 11, 24, 1, 5, + 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, + 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, + 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, + 46, 42, 50, 36, 29, 32, +]; + +// Cumulative bit shift constants +const BIT_SHIFTS = [1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28]; + +// SBOXes and round permutation constants +const SBOX_P = [ + { + 0x0: 0x808200, + 0x10000000: 0x8000, + 0x20000000: 0x808002, + 0x30000000: 0x2, + 0x40000000: 0x200, + 0x50000000: 0x808202, + 0x60000000: 0x800202, + 0x70000000: 0x800000, + 0x80000000: 0x202, + 0x90000000: 0x800200, + 0xa0000000: 0x8200, + 0xb0000000: 0x808000, + 0xc0000000: 0x8002, + 0xd0000000: 0x800002, + 0xe0000000: 0x0, + 0xf0000000: 0x8202, + 0x8000000: 0x0, + 0x18000000: 0x808202, + 0x28000000: 0x8202, + 0x38000000: 0x8000, + 0x48000000: 0x808200, + 0x58000000: 0x200, + 0x68000000: 0x808002, + 0x78000000: 0x2, + 0x88000000: 0x800200, + 0x98000000: 0x8200, + 0xa8000000: 0x808000, + 0xb8000000: 0x800202, + 0xc8000000: 0x800002, + 0xd8000000: 0x8002, + 0xe8000000: 0x202, + 0xf8000000: 0x800000, + 0x1: 0x8000, + 0x10000001: 0x2, + 0x20000001: 0x808200, + 0x30000001: 0x800000, + 0x40000001: 0x808002, + 0x50000001: 0x8200, + 0x60000001: 0x200, + 0x70000001: 0x800202, + 0x80000001: 0x808202, + 0x90000001: 0x808000, + 0xa0000001: 0x800002, + 0xb0000001: 0x8202, + 0xc0000001: 0x202, + 0xd0000001: 0x800200, + 0xe0000001: 0x8002, + 0xf0000001: 0x0, + 0x8000001: 0x808202, + 0x18000001: 0x808000, + 0x28000001: 0x800000, + 0x38000001: 0x200, + 0x48000001: 0x8000, + 0x58000001: 0x800002, + 0x68000001: 0x2, + 0x78000001: 0x8202, + 0x88000001: 0x8002, + 0x98000001: 0x800202, + 0xa8000001: 0x202, + 0xb8000001: 0x808200, + 0xc8000001: 0x800200, + 0xd8000001: 0x0, + 0xe8000001: 0x8200, + 0xf8000001: 0x808002 + }, + { + 0x0: 0x40084010, + 0x1000000: 0x4000, + 0x2000000: 0x80000, + 0x3000000: 0x40080010, + 0x4000000: 0x40000010, + 0x5000000: 0x40084000, + 0x6000000: 0x40004000, + 0x7000000: 0x10, + 0x8000000: 0x84000, + 0x9000000: 0x40004010, + 0xa000000: 0x40000000, + 0xb000000: 0x84010, + 0xc000000: 0x80010, + 0xd000000: 0x0, + 0xe000000: 0x4010, + 0xf000000: 0x40080000, + 0x800000: 0x40004000, + 0x1800000: 0x84010, + 0x2800000: 0x10, + 0x3800000: 0x40004010, + 0x4800000: 0x40084010, + 0x5800000: 0x40000000, + 0x6800000: 0x80000, + 0x7800000: 0x40080010, + 0x8800000: 0x80010, + 0x9800000: 0x0, + 0xa800000: 0x4000, + 0xb800000: 0x40080000, + 0xc800000: 0x40000010, + 0xd800000: 0x84000, + 0xe800000: 0x40084000, + 0xf800000: 0x4010, + 0x10000000: 0x0, + 0x11000000: 0x40080010, + 0x12000000: 0x40004010, + 0x13000000: 0x40084000, + 0x14000000: 0x40080000, + 0x15000000: 0x10, + 0x16000000: 0x84010, + 0x17000000: 0x4000, + 0x18000000: 0x4010, + 0x19000000: 0x80000, + 0x1a000000: 0x80010, + 0x1b000000: 0x40000010, + 0x1c000000: 0x84000, + 0x1d000000: 0x40004000, + 0x1e000000: 0x40000000, + 0x1f000000: 0x40084010, + 0x10800000: 0x84010, + 0x11800000: 0x80000, + 0x12800000: 0x40080000, + 0x13800000: 0x4000, + 0x14800000: 0x40004000, + 0x15800000: 0x40084010, + 0x16800000: 0x10, + 0x17800000: 0x40000000, + 0x18800000: 0x40084000, + 0x19800000: 0x40000010, + 0x1a800000: 0x40004010, + 0x1b800000: 0x80010, + 0x1c800000: 0x0, + 0x1d800000: 0x4010, + 0x1e800000: 0x40080010, + 0x1f800000: 0x84000 + }, + { + 0x0: 0x104, + 0x100000: 0x0, + 0x200000: 0x4000100, + 0x300000: 0x10104, + 0x400000: 0x10004, + 0x500000: 0x4000004, + 0x600000: 0x4010104, + 0x700000: 0x4010000, + 0x800000: 0x4000000, + 0x900000: 0x4010100, + 0xa00000: 0x10100, + 0xb00000: 0x4010004, + 0xc00000: 0x4000104, + 0xd00000: 0x10000, + 0xe00000: 0x4, + 0xf00000: 0x100, + 0x80000: 0x4010100, + 0x180000: 0x4010004, + 0x280000: 0x0, + 0x380000: 0x4000100, + 0x480000: 0x4000004, + 0x580000: 0x10000, + 0x680000: 0x10004, + 0x780000: 0x104, + 0x880000: 0x4, + 0x980000: 0x100, + 0xa80000: 0x4010000, + 0xb80000: 0x10104, + 0xc80000: 0x10100, + 0xd80000: 0x4000104, + 0xe80000: 0x4010104, + 0xf80000: 0x4000000, + 0x1000000: 0x4010100, + 0x1100000: 0x10004, + 0x1200000: 0x10000, + 0x1300000: 0x4000100, + 0x1400000: 0x100, + 0x1500000: 0x4010104, + 0x1600000: 0x4000004, + 0x1700000: 0x0, + 0x1800000: 0x4000104, + 0x1900000: 0x4000000, + 0x1a00000: 0x4, + 0x1b00000: 0x10100, + 0x1c00000: 0x4010000, + 0x1d00000: 0x104, + 0x1e00000: 0x10104, + 0x1f00000: 0x4010004, + 0x1080000: 0x4000000, + 0x1180000: 0x104, + 0x1280000: 0x4010100, + 0x1380000: 0x0, + 0x1480000: 0x10004, + 0x1580000: 0x4000100, + 0x1680000: 0x100, + 0x1780000: 0x4010004, + 0x1880000: 0x10000, + 0x1980000: 0x4010104, + 0x1a80000: 0x10104, + 0x1b80000: 0x4000004, + 0x1c80000: 0x4000104, + 0x1d80000: 0x4010000, + 0x1e80000: 0x4, + 0x1f80000: 0x10100 + }, + { + 0x0: 0x80401000, + 0x10000: 0x80001040, + 0x20000: 0x401040, + 0x30000: 0x80400000, + 0x40000: 0x0, + 0x50000: 0x401000, + 0x60000: 0x80000040, + 0x70000: 0x400040, + 0x80000: 0x80000000, + 0x90000: 0x400000, + 0xa0000: 0x40, + 0xb0000: 0x80001000, + 0xc0000: 0x80400040, + 0xd0000: 0x1040, + 0xe0000: 0x1000, + 0xf0000: 0x80401040, + 0x8000: 0x80001040, + 0x18000: 0x40, + 0x28000: 0x80400040, + 0x38000: 0x80001000, + 0x48000: 0x401000, + 0x58000: 0x80401040, + 0x68000: 0x0, + 0x78000: 0x80400000, + 0x88000: 0x1000, + 0x98000: 0x80401000, + 0xa8000: 0x400000, + 0xb8000: 0x1040, + 0xc8000: 0x80000000, + 0xd8000: 0x400040, + 0xe8000: 0x401040, + 0xf8000: 0x80000040, + 0x100000: 0x400040, + 0x110000: 0x401000, + 0x120000: 0x80000040, + 0x130000: 0x0, + 0x140000: 0x1040, + 0x150000: 0x80400040, + 0x160000: 0x80401000, + 0x170000: 0x80001040, + 0x180000: 0x80401040, + 0x190000: 0x80000000, + 0x1a0000: 0x80400000, + 0x1b0000: 0x401040, + 0x1c0000: 0x80001000, + 0x1d0000: 0x400000, + 0x1e0000: 0x40, + 0x1f0000: 0x1000, + 0x108000: 0x80400000, + 0x118000: 0x80401040, + 0x128000: 0x0, + 0x138000: 0x401000, + 0x148000: 0x400040, + 0x158000: 0x80000000, + 0x168000: 0x80001040, + 0x178000: 0x40, + 0x188000: 0x80000040, + 0x198000: 0x1000, + 0x1a8000: 0x80001000, + 0x1b8000: 0x80400040, + 0x1c8000: 0x1040, + 0x1d8000: 0x80401000, + 0x1e8000: 0x400000, + 0x1f8000: 0x401040 + }, + { + 0x0: 0x80, + 0x1000: 0x1040000, + 0x2000: 0x40000, + 0x3000: 0x20000000, + 0x4000: 0x20040080, + 0x5000: 0x1000080, + 0x6000: 0x21000080, + 0x7000: 0x40080, + 0x8000: 0x1000000, + 0x9000: 0x20040000, + 0xa000: 0x20000080, + 0xb000: 0x21040080, + 0xc000: 0x21040000, + 0xd000: 0x0, + 0xe000: 0x1040080, + 0xf000: 0x21000000, + 0x800: 0x1040080, + 0x1800: 0x21000080, + 0x2800: 0x80, + 0x3800: 0x1040000, + 0x4800: 0x40000, + 0x5800: 0x20040080, + 0x6800: 0x21040000, + 0x7800: 0x20000000, + 0x8800: 0x20040000, + 0x9800: 0x0, + 0xa800: 0x21040080, + 0xb800: 0x1000080, + 0xc800: 0x20000080, + 0xd800: 0x21000000, + 0xe800: 0x1000000, + 0xf800: 0x40080, + 0x10000: 0x40000, + 0x11000: 0x80, + 0x12000: 0x20000000, + 0x13000: 0x21000080, + 0x14000: 0x1000080, + 0x15000: 0x21040000, + 0x16000: 0x20040080, + 0x17000: 0x1000000, + 0x18000: 0x21040080, + 0x19000: 0x21000000, + 0x1a000: 0x1040000, + 0x1b000: 0x20040000, + 0x1c000: 0x40080, + 0x1d000: 0x20000080, + 0x1e000: 0x0, + 0x1f000: 0x1040080, + 0x10800: 0x21000080, + 0x11800: 0x1000000, + 0x12800: 0x1040000, + 0x13800: 0x20040080, + 0x14800: 0x20000000, + 0x15800: 0x1040080, + 0x16800: 0x80, + 0x17800: 0x21040000, + 0x18800: 0x40080, + 0x19800: 0x21040080, + 0x1a800: 0x0, + 0x1b800: 0x21000000, + 0x1c800: 0x1000080, + 0x1d800: 0x40000, + 0x1e800: 0x20040000, + 0x1f800: 0x20000080 + }, + { + 0x0: 0x10000008, + 0x100: 0x2000, + 0x200: 0x10200000, + 0x300: 0x10202008, + 0x400: 0x10002000, + 0x500: 0x200000, + 0x600: 0x200008, + 0x700: 0x10000000, + 0x800: 0x0, + 0x900: 0x10002008, + 0xa00: 0x202000, + 0xb00: 0x8, + 0xc00: 0x10200008, + 0xd00: 0x202008, + 0xe00: 0x2008, + 0xf00: 0x10202000, + 0x80: 0x10200000, + 0x180: 0x10202008, + 0x280: 0x8, + 0x380: 0x200000, + 0x480: 0x202008, + 0x580: 0x10000008, + 0x680: 0x10002000, + 0x780: 0x2008, + 0x880: 0x200008, + 0x980: 0x2000, + 0xa80: 0x10002008, + 0xb80: 0x10200008, + 0xc80: 0x0, + 0xd80: 0x10202000, + 0xe80: 0x202000, + 0xf80: 0x10000000, + 0x1000: 0x10002000, + 0x1100: 0x10200008, + 0x1200: 0x10202008, + 0x1300: 0x2008, + 0x1400: 0x200000, + 0x1500: 0x10000000, + 0x1600: 0x10000008, + 0x1700: 0x202000, + 0x1800: 0x202008, + 0x1900: 0x0, + 0x1a00: 0x8, + 0x1b00: 0x10200000, + 0x1c00: 0x2000, + 0x1d00: 0x10002008, + 0x1e00: 0x10202000, + 0x1f00: 0x200008, + 0x1080: 0x8, + 0x1180: 0x202000, + 0x1280: 0x200000, + 0x1380: 0x10000008, + 0x1480: 0x10002000, + 0x1580: 0x2008, + 0x1680: 0x10202008, + 0x1780: 0x10200000, + 0x1880: 0x10202000, + 0x1980: 0x10200008, + 0x1a80: 0x2000, + 0x1b80: 0x202008, + 0x1c80: 0x200008, + 0x1d80: 0x0, + 0x1e80: 0x10000000, + 0x1f80: 0x10002008 + }, + { + 0x0: 0x100000, + 0x10: 0x2000401, + 0x20: 0x400, + 0x30: 0x100401, + 0x40: 0x2100401, + 0x50: 0x0, + 0x60: 0x1, + 0x70: 0x2100001, + 0x80: 0x2000400, + 0x90: 0x100001, + 0xa0: 0x2000001, + 0xb0: 0x2100400, + 0xc0: 0x2100000, + 0xd0: 0x401, + 0xe0: 0x100400, + 0xf0: 0x2000000, + 0x8: 0x2100001, + 0x18: 0x0, + 0x28: 0x2000401, + 0x38: 0x2100400, + 0x48: 0x100000, + 0x58: 0x2000001, + 0x68: 0x2000000, + 0x78: 0x401, + 0x88: 0x100401, + 0x98: 0x2000400, + 0xa8: 0x2100000, + 0xb8: 0x100001, + 0xc8: 0x400, + 0xd8: 0x2100401, + 0xe8: 0x1, + 0xf8: 0x100400, + 0x100: 0x2000000, + 0x110: 0x100000, + 0x120: 0x2000401, + 0x130: 0x2100001, + 0x140: 0x100001, + 0x150: 0x2000400, + 0x160: 0x2100400, + 0x170: 0x100401, + 0x180: 0x401, + 0x190: 0x2100401, + 0x1a0: 0x100400, + 0x1b0: 0x1, + 0x1c0: 0x0, + 0x1d0: 0x2100000, + 0x1e0: 0x2000001, + 0x1f0: 0x400, + 0x108: 0x100400, + 0x118: 0x2000401, + 0x128: 0x2100001, + 0x138: 0x1, + 0x148: 0x2000000, + 0x158: 0x100000, + 0x168: 0x401, + 0x178: 0x2100400, + 0x188: 0x2000001, + 0x198: 0x2100000, + 0x1a8: 0x0, + 0x1b8: 0x2100401, + 0x1c8: 0x100401, + 0x1d8: 0x400, + 0x1e8: 0x2000400, + 0x1f8: 0x100001 + }, + { + 0x0: 0x8000820, + 0x1: 0x20000, + 0x2: 0x8000000, + 0x3: 0x20, + 0x4: 0x20020, + 0x5: 0x8020820, + 0x6: 0x8020800, + 0x7: 0x800, + 0x8: 0x8020000, + 0x9: 0x8000800, + 0xa: 0x20800, + 0xb: 0x8020020, + 0xc: 0x820, + 0xd: 0x0, + 0xe: 0x8000020, + 0xf: 0x20820, + 0x80000000: 0x800, + 0x80000001: 0x8020820, + 0x80000002: 0x8000820, + 0x80000003: 0x8000000, + 0x80000004: 0x8020000, + 0x80000005: 0x20800, + 0x80000006: 0x20820, + 0x80000007: 0x20, + 0x80000008: 0x8000020, + 0x80000009: 0x820, + 0x8000000a: 0x20020, + 0x8000000b: 0x8020800, + 0x8000000c: 0x0, + 0x8000000d: 0x8020020, + 0x8000000e: 0x8000800, + 0x8000000f: 0x20000, + 0x10: 0x20820, + 0x11: 0x8020800, + 0x12: 0x20, + 0x13: 0x800, + 0x14: 0x8000800, + 0x15: 0x8000020, + 0x16: 0x8020020, + 0x17: 0x20000, + 0x18: 0x0, + 0x19: 0x20020, + 0x1a: 0x8020000, + 0x1b: 0x8000820, + 0x1c: 0x8020820, + 0x1d: 0x20800, + 0x1e: 0x820, + 0x1f: 0x8000000, + 0x80000010: 0x20000, + 0x80000011: 0x800, + 0x80000012: 0x8020020, + 0x80000013: 0x20820, + 0x80000014: 0x20, + 0x80000015: 0x8020000, + 0x80000016: 0x8000000, + 0x80000017: 0x8000820, + 0x80000018: 0x8020820, + 0x80000019: 0x8000020, + 0x8000001a: 0x8000800, + 0x8000001b: 0x0, + 0x8000001c: 0x20800, + 0x8000001d: 0x820, + 0x8000001e: 0x20020, + 0x8000001f: 0x8020800 + } +]; + +// Masks that select the SBOX input +const SBOX_MASK = [ + 0xf8000001, 0x1f800000, 0x01f80000, 0x001f8000, + 0x0001f800, 0x00001f80, 0x000001f8, 0x8000001f +]; + +// Swap bits across the left and right words +function exchangeLR(offset, mask) { + const t = ((this._lBlock >>> offset) ^ this._rBlock) & mask; + this._rBlock ^= t; + this._lBlock ^= t << offset; +} + +function exchangeRL(offset, mask) { + const t = ((this._rBlock >>> offset) ^ this._lBlock) & mask; + this._lBlock ^= t; + this._rBlock ^= t << offset; +} + +/** + * DES block cipher algorithm. + */ +export class DESAlgo extends BlockCipher { + constructor(...args) { + super(...args); + + this.keySize = 64 / 32; + this.ivSize = 64 / 32; + this.blockSize = 64 / 32; + } + + _doReset() { + // Shortcuts + const key = this._key; + const keyWords = key.words; + + // Select 56 bits according to PC1 + const keyBits = []; + for (let i = 0; i < 56; i++) { + const keyBitPos = PC1[i] - 1; + keyBits[i] = (keyWords[keyBitPos >>> 5] >>> (31 - (keyBitPos % 32))) & 1; + } + + // Assemble 16 subkeys + this._subKeys = []; + const subKeys = this._subKeys; + for (let nSubKey = 0; nSubKey < 16; nSubKey++) { + // Create subkey + subKeys[nSubKey] = []; + const subKey = subKeys[nSubKey]; + + // Shortcut + const bitShift = BIT_SHIFTS[nSubKey]; + + // Select 48 bits according to PC2 + for (let i = 0; i < 24; i++) { + // Select from the left 28 key bits + subKey[(i / 6) | 0] |= keyBits[((PC2[i] - 1) + bitShift) % 28] << (31 - (i % 6)); + + // Select from the right 28 key bits + subKey[4 + ((i / 6) | 0)] + |= keyBits[28 + (((PC2[i + 24] - 1) + bitShift) % 28)] + << (31 - (i % 6)); + } + + // Since each subkey is applied to an expanded 32-bit input, + // the subkey can be broken into 8 values scaled to 32-bits, + // which allows the key to be used without expansion + subKey[0] = (subKey[0] << 1) | (subKey[0] >>> 31); + for (let i = 1; i < 7; i++) { + subKey[i] >>>= ((i - 1) * 4 + 3); + } + subKey[7] = (subKey[7] << 5) | (subKey[7] >>> 27); + } + + // Compute inverse subkeys + this._invSubKeys = []; + const invSubKeys = this._invSubKeys; + for (let i = 0; i < 16; i++) { + invSubKeys[i] = subKeys[15 - i]; + } + } + + encryptBlock(M, offset) { + this._doCryptBlock(M, offset, this._subKeys); + } + + decryptBlock(M, offset) { + this._doCryptBlock(M, offset, this._invSubKeys); + } + + _doCryptBlock(M, offset, subKeys) { + const _M = M; + + // Get input + this._lBlock = M[offset]; + this._rBlock = M[offset + 1]; + + // Initial permutation + exchangeLR.call(this, 4, 0x0f0f0f0f); + exchangeLR.call(this, 16, 0x0000ffff); + exchangeRL.call(this, 2, 0x33333333); + exchangeRL.call(this, 8, 0x00ff00ff); + exchangeLR.call(this, 1, 0x55555555); + + // Rounds + for (let round = 0; round < 16; round++) { + // Shortcuts + const subKey = subKeys[round]; + const lBlock = this._lBlock; + const rBlock = this._rBlock; + + // Feistel function + let f = 0; + for (let i = 0; i < 8; i++) { + f |= SBOX_P[i][((rBlock ^ subKey[i]) & SBOX_MASK[i]) >>> 0]; + } + this._lBlock = rBlock; + this._rBlock = lBlock ^ f; + } + + // Undo swap from last round + const t = this._lBlock; + this._lBlock = this._rBlock; + this._rBlock = t; + + // Final permutation + exchangeLR.call(this, 1, 0x55555555); + exchangeRL.call(this, 8, 0x00ff00ff); + exchangeRL.call(this, 2, 0x33333333); + exchangeLR.call(this, 16, 0x0000ffff); + exchangeLR.call(this, 4, 0x0f0f0f0f); + + // Set output + _M[offset] = this._lBlock; + _M[offset + 1] = this._rBlock; + } +} + +/** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.DES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.DES.decrypt(ciphertext, key, cfg); + */ +export const DES = BlockCipher._createHelper(DESAlgo); + +/** + * Triple-DES block cipher algorithm. + */ +export class TripleDESAlgo extends BlockCipher { + constructor(...args) { + super(...args); + + this.keySize = 192 / 32; + this.ivSize = 64 / 32; + this.blockSize = 64 / 32; + } + + _doReset() { + // Shortcuts + const key = this._key; + const keyWords = key.words; + + // Create DES instances + this._des1 = DESAlgo.createEncryptor(new WordArray(keyWords.slice(0, 2))); + this._des2 = DESAlgo.createEncryptor(new WordArray(keyWords.slice(2, 4))); + this._des3 = DESAlgo.createEncryptor(new WordArray(keyWords.slice(4, 6))); + } + + encryptBlock(M, offset) { + this._des1.encryptBlock(M, offset); + this._des2.decryptBlock(M, offset); + this._des3.encryptBlock(M, offset); + } + + decryptBlock(M, offset) { + this._des3.decryptBlock(M, offset); + this._des2.encryptBlock(M, offset); + this._des1.decryptBlock(M, offset); + } +} + +/** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.TripleDES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.TripleDES.decrypt(ciphertext, key, cfg); + */ +export const TripleDES = BlockCipher._createHelper(TripleDESAlgo); diff --git a/src/format/format-hex.js b/src/format/format-hex.js new file mode 100644 index 0000000..5792d81 --- /dev/null +++ b/src/format/format-hex.js @@ -0,0 +1,43 @@ +import { + CipherParams +} from '../core/cipher-core.js'; +import { + Hex +} from '../encoding/enc-hax.js'; + +export const HexFormatter = { + /** + * Converts the ciphertext of a cipher params object to a hexadecimally encoded string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The hexadecimally encoded string. + * + * @static + * + * @example + * + * let hexString = CryptoJS.format.Hex.stringify(cipherParams); + */ + stringify(cipherParams) { + return cipherParams.ciphertext.toString(Hex); + }, + + /** + * Converts a hexadecimally encoded ciphertext string to a cipher params object. + * + * @param {string} input The hexadecimally encoded string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * let cipherParams = CryptoJS.format.Hex.parse(hexString); + */ + parse(input) { + const ciphertext = Hex.parse(input); + return new CipherParams({ ciphertext }); + } +}; diff --git a/src/hmac.js b/src/hmac.js deleted file mode 100644 index 62f853d..0000000 --- a/src/hmac.js +++ /dev/null @@ -1,3 +0,0 @@ -export { - HMAC -} from './core.js'; diff --git a/src/index.js b/src/index.js index 0df8cda..7a54fab 100644 --- a/src/index.js +++ b/src/index.js @@ -1,32 +1,69 @@ import { Base, WordArray, - Hex, - Latin1, - Utf8, - BufferedBlockAlgorithm, - Hasher -} from './core.js'; + BufferedBlockAlgorithm +} from './core/core.js'; +import { Hasher } from './core/hasher'; import { X64Word, X64WordArray -} from './x64-core.js'; +} from './core/x64-core.js'; import { Cipher, StreamCipher, BlockCipherMode, CBC, - Pkcs7, BlockCipher, CipherParams, OpenSSLFormatter, SerializableCipher, OpenSSLKdf, PasswordBasedCipher -} from './cipher-core.js'; +} from './core/cipher-core.js'; -import { MD5Algo, MD5, HmacMD5 } from './md5.js'; -import { HMAC } from './hmac.js'; +import { Utf16, Utf16BE, Utf16LE } from './encoding/enc-utf16.js'; +import { Latin1 } from './encoding/enc-latin1'; +import { Utf8 } from './encoding/enc-utf8'; +import { Hex } from './encoding/enc-hax'; +import { Base64 } from './encoding/enc-base64.js'; +import { HMAC } from './algo/hmac/hmac.js'; +import { MD5Algo, MD5, HmacMD5 } from './algo/hash/md5.js'; +import { SHA1Algo, SHA1, HmacSHA1 } from './algo/hash/sha1.js'; +import { SHA224Algo, SHA224, HmacSHA224 } from './algo/hash/sha224.js'; +import { SHA256Algo, SHA256, HmacSHA256 } from './algo/hash/sha256.js'; +import { SHA384Algo, SHA384, HmacSHA384 } from './algo/hash/sha384.js'; +import { SHA512Algo, SHA512, HmacSHA512 } from './algo/hash/sha512.js'; +import { SHA3Algo, SHA3, HmacSHA3 } from './algo/hash/sha3.js'; +import { RIPEMD160Algo, RIPEMD160, HmacRIPEMD160 } from './algo/hash/ripemd160.js'; +import { PBKDF2Algo, PBKDF2 } from './algo/pbkdf2/pbkdf2.js'; +import { EvpKDFAlgo, EvpKDF } from './encryption/evpkdf.js'; +import { AESAlgo, AES } from './encryption/aes.js'; +import { + DESAlgo, + DES, + TripleDESAlgo, + TripleDES +} from './encryption/tripledes.js'; +import { RabbitAlgo, Rabbit } from './encryption/rabbit.js'; +import { RabbitLegacyAlgo, RabbitLegacy } from './encryption/rabbit-legacy.js'; +import { + RC4Algo, + RC4, + RC4DropAlgo, + RC4Drop +} from './encryption/rc4.js'; +import { CFB } from './mode/mode-cfb.js'; +import { CTR } from './mode/mode-ctr.js'; +import { CTRGladman } from './mode/mode-ctr-gladman.js'; +import { ECB } from './mode/mode-ecb.js'; +import { OFB } from './mode/mode-ofb.js'; +import { Pkcs7 } from './pad/pad-pkcs7.js'; +import { AnsiX923 } from './pad/pad-ansix923.js'; +import { Iso10126 } from './pad/pad-iso10126.js'; +import { Iso97971 } from './pad/pad-iso97971.js'; +import { NoPadding } from './pad/pad-nopadding.js'; +import { ZeroPadding } from './pad/pad-zeropadding.js'; +import { HexFormatter } from './format/format-hex.js'; export default { lib: { @@ -51,24 +88,57 @@ export default { enc: { Hex, Latin1, - Utf8 + Utf8, + Utf16, + Utf16BE, + Utf16LE, + Base64 }, algo: { HMAC, - MD5: MD5Algo + MD5: MD5Algo, + SHA1: SHA1Algo, + SHA224: SHA224Algo, + SHA256: SHA256Algo, + SHA384: SHA384Algo, + SHA512: SHA512Algo, + SHA3: SHA3Algo, + RIPEMD160: RIPEMD160Algo, + + PBKDF2: PBKDF2Algo, + EvpKDF: EvpKDFAlgo, + + AES: AESAlgo, + DES: DESAlgo, + TripleDES: TripleDESAlgo, + Rabbit: RabbitAlgo, + RabbitLegacy: RabbitLegacyAlgo, + RC4: RC4Algo, + RC4Drop: RC4DropAlgo }, mode: { - CBC + CBC, + CFB, + CTR, + CTRGladman, + ECB, + OFB }, pad: { - Pkcs7 + Pkcs7, + AnsiX923, + Iso10126, + Iso97971, + NoPadding, + ZeroPadding }, format: { - OpenSSL: OpenSSLFormatter + OpenSSL: OpenSSLFormatter, + Hex: HexFormatter }, kdf: { @@ -76,5 +146,30 @@ export default { }, MD5, - HmacMD5 + HmacMD5, + SHA1, + HmacSHA1, + SHA224, + HmacSHA224, + SHA256, + HmacSHA256, + SHA384, + HmacSHA384, + SHA512, + HmacSHA512, + SHA3, + HmacSHA3, + RIPEMD160, + HmacRIPEMD160, + + PBKDF2, + EvpKDF, + + AES, + DES, + TripleDES, + Rabbit, + RabbitLegacy, + RC4, + RC4Drop }; diff --git a/src/mode/mode-cfb.js b/src/mode/mode-cfb.js new file mode 100644 index 0000000..77f2532 --- /dev/null +++ b/src/mode/mode-cfb.js @@ -0,0 +1,63 @@ +import { + BlockCipherMode +} from '../core/cipher-core.js'; + +function generateKeystreamAndEncrypt(words, offset, blockSize, cipher) { + const _words = words; + let keystream; + + // Shortcut + const iv = this._iv; + + // Generate keystream + if (iv) { + keystream = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + keystream = this._prevBlock; + } + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (let i = 0; i < blockSize; i++) { + _words[offset + i] ^= keystream[i]; + } +} + +/** + * Cipher Feedback block mode. + */ +export class CFB extends BlockCipherMode { + + static Encryptor(){} + +} +CFB.Encryptor = class extends CFB { + processBlock(words, offset) { + // Shortcuts + const cipher = this._cipher; + const { blockSize } = cipher; + + generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } +}; +CFB.Decryptor = class extends CFB { + processBlock(words, offset) { + // Shortcuts + const cipher = this._cipher; + const { blockSize } = cipher; + + // Remember this block to use with next block + const thisBlock = words.slice(offset, offset + blockSize); + + generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } +}; diff --git a/src/mode/mode-ctr-gladman.js b/src/mode/mode-ctr-gladman.js new file mode 100644 index 0000000..6678831 --- /dev/null +++ b/src/mode/mode-ctr-gladman.js @@ -0,0 +1,87 @@ +import { + BlockCipherMode +} from '../core/cipher-core.js'; + +const incWord = (word) => { + let _word = word; + + if (((word >> 24) & 0xff) === 0xff) { // overflow + let b1 = (word >> 16) & 0xff; + let b2 = (word >> 8) & 0xff; + let b3 = word & 0xff; + + if (b1 === 0xff) { // overflow b1 + b1 = 0; + if (b2 === 0xff) { + b2 = 0; + if (b3 === 0xff) { + b3 = 0; + } else { + b3++; + } + } else { + b2++; + } + } else { + b1++; + } + + _word = 0; + _word += (b1 << 16); + _word += (b2 << 8); + _word += b3; + } else { + _word += (0x01 << 24); + } + return _word; +}; + +const incCounter = (counter) => { + const _counter = counter; + _counter[0] = incWord(_counter[0]); + + if (_counter[0] === 0) { + // encr_data in fileenc.c from Dr Brian Gladman's counts only with DWORD j < 8 + _counter[1] = incWord(_counter[1]); + } + return _counter; +}; + +/** @preserve + * Counter block mode compatible with Dr Brian Gladman fileenc.c + * derived from CryptoJS.mode.CTR + * Jan Hruby jhruby.web@gmail.com + */ +export class CTRGladman extends BlockCipherMode { +} +CTRGladman.Encryptor = class extends CTRGladman { + processBlock(words, offset) { + const _words = words; + + // Shortcuts + const cipher = this._cipher; + const { blockSize } = cipher; + const iv = this._iv; + let counter = this._counter; + + // Generate keystream + if (iv) { + this._counter = iv.slice(0); + counter = this._counter; + + // Remove IV for subsequent blocks + this._iv = undefined; + } + + incCounter(counter); + + const keystream = counter.slice(0); + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (let i = 0; i < blockSize; i++) { + _words[offset + i] ^= keystream[i]; + } + } +}; +CTRGladman.Decryptor = CTRGladman.Encryptor; diff --git a/src/mode/mode-ctr.js b/src/mode/mode-ctr.js new file mode 100644 index 0000000..804d6f8 --- /dev/null +++ b/src/mode/mode-ctr.js @@ -0,0 +1,40 @@ +/** + * Counter block mode. + */ +import { + BlockCipherMode +} from '../core/cipher-core.js'; + +export class CTR extends BlockCipherMode { +} +CTR.Encryptor = class extends CTR { + processBlock(words, offset) { + const _words = words; + + // Shortcuts + const cipher = this._cipher; + const { blockSize } = cipher; + const iv = this._iv; + let counter = this._counter; + + // Generate keystream + if (iv) { + this._counter = iv.slice(0); + counter = this._counter; + + // Remove IV for subsequent blocks + this._iv = undefined; + } + const keystream = counter.slice(0); + cipher.encryptBlock(keystream, 0); + + // Increment counter + counter[blockSize - 1] = (counter[blockSize - 1] + 1) | 0; + + // Encrypt + for (let i = 0; i < blockSize; i++) { + _words[offset + i] ^= keystream[i]; + } + } +}; +CTR.Decryptor = CTR.Encryptor; diff --git a/src/mode/mode-ecb.js b/src/mode/mode-ecb.js new file mode 100644 index 0000000..c35ca49 --- /dev/null +++ b/src/mode/mode-ecb.js @@ -0,0 +1,19 @@ +/** + * Electronic Codebook block mode. + */ +import { + BlockCipherMode +} from '../core/cipher-core.js'; + +export class ECB extends BlockCipherMode { +} +ECB.Encryptor = class extends ECB { + processBlock(words, offset) { + this._cipher.encryptBlock(words, offset); + } +}; +ECB.Decryptor = class extends ECB { + processBlock(words, offset) { + this._cipher.decryptBlock(words, offset); + } +}; diff --git a/src/mode/mode-ofb.js b/src/mode/mode-ofb.js new file mode 100644 index 0000000..5fb9f58 --- /dev/null +++ b/src/mode/mode-ofb.js @@ -0,0 +1,36 @@ +/** + * Output Feedback block mode. + */ +import { + BlockCipherMode +} from '../core/cipher-core.js'; + +export class OFB extends BlockCipherMode { +} +OFB.Encryptor = class extends OFB { + processBlock(words, offset) { + const _words = words; + + // Shortcuts + const cipher = this._cipher; + const { blockSize } = cipher; + const iv = this._iv; + let keystream = this._keystream; + + // Generate keystream + if (iv) { + this._keystream = iv.slice(0); + keystream = this._keystream; + + // Remove IV for subsequent blocks + this._iv = undefined; + } + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (let i = 0; i < blockSize; i++) { + _words[offset + i] ^= keystream[i]; + } + } +}; +OFB.Decryptor = OFB.Encryptor; diff --git a/src/pad/pad-ansix923.js b/src/pad/pad-ansix923.js new file mode 100644 index 0000000..233eff7 --- /dev/null +++ b/src/pad/pad-ansix923.js @@ -0,0 +1,33 @@ +/** + * ANSI X.923 padding strategy. + */ +export const AnsiX923 = { + pad(data, blockSize) { + const _data = data; + + // Shortcuts + const dataSigBytes = _data.sigBytes; + const blockSizeBytes = blockSize * 4; + + // Count padding bytes + const nPaddingBytes = blockSizeBytes - (dataSigBytes % blockSizeBytes); + + // Compute last byte position + const lastBytePos = dataSigBytes + nPaddingBytes - 1; + + // Pad + _data.clamp(); + _data.words[lastBytePos >>> 2] |= nPaddingBytes << (24 - (lastBytePos % 4) * 8); + _data.sigBytes += nPaddingBytes; + }, + + unpad(data) { + const _data = data; + + // Get number of padding bytes from last byte + const nPaddingBytes = _data.words[(_data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + _data.sigBytes -= nPaddingBytes; + } +}; diff --git a/src/pad/pad-iso10126.js b/src/pad/pad-iso10126.js new file mode 100644 index 0000000..ad3e8be --- /dev/null +++ b/src/pad/pad-iso10126.js @@ -0,0 +1,30 @@ +import { + WordArray, +} from '../core/core.js'; + +/** + * ISO 10126 padding strategy. + */ +export const Iso10126 = { + pad(data, blockSize) { + // Shortcut + const blockSizeBytes = blockSize * 4; + + // Count padding bytes + const nPaddingBytes = blockSizeBytes - (data.sigBytes % blockSizeBytes); + + // Pad + data + .concat(WordArray.random(nPaddingBytes - 1)) + .concat(new WordArray([nPaddingBytes << 24], 1)); + }, + + unpad(data) { + const _data = data; + // Get number of padding bytes from last byte + const nPaddingBytes = _data.words[(_data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + _data.sigBytes -= nPaddingBytes; + } +}; diff --git a/src/pad/pad-iso97971.js b/src/pad/pad-iso97971.js new file mode 100644 index 0000000..fd0d446 --- /dev/null +++ b/src/pad/pad-iso97971.js @@ -0,0 +1,29 @@ +import { + WordArray, +} from '../core/core.js'; +import { + ZeroPadding, +} from './pad-zeropadding.js'; + +/** + * ISO/IEC 9797-1 Padding Method 2. + */ +export const Iso97971 = { + pad(data, blockSize) { + // Add 0x80 byte + data.concat(new WordArray([0x80000000], 1)); + + // Zero pad the rest + ZeroPadding.pad(data, blockSize); + }, + + unpad(data) { + const _data = data; + + // Remove zero padding + ZeroPadding.unpad(_data); + + // Remove one more byte -- the 0x80 byte + _data.sigBytes--; + } +}; diff --git a/src/pad/pad-nopadding.js b/src/pad/pad-nopadding.js new file mode 100644 index 0000000..e1ffc0a --- /dev/null +++ b/src/pad/pad-nopadding.js @@ -0,0 +1,10 @@ +/** + * A noop padding strategy. + */ +export const NoPadding = { + pad() { + }, + + unpad() { + } +}; diff --git a/src/pad/pad-pkcs7.js b/src/pad/pad-pkcs7.js new file mode 100644 index 0000000..3cba2bb --- /dev/null +++ b/src/pad/pad-pkcs7.js @@ -0,0 +1,63 @@ +import { WordArray } from '../core/core'; + +/** + * PKCS #5/7 padding strategy. + */ +export const Pkcs7 = { + /** + * Pads data using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to pad. + * @param {number} blockSize The multiple that the data should be padded to. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.pad(wordArray, 4); + */ + pad(data, blockSize) { + // Shortcut + const blockSizeBytes = blockSize * 4; + + // Count padding bytes + const nPaddingBytes = blockSizeBytes - (data.sigBytes % blockSizeBytes); + + // Create padding word + const paddingWord = (nPaddingBytes << 24) | + (nPaddingBytes << 16) | + (nPaddingBytes << 8) | + nPaddingBytes; + + // Create padding + const paddingWords = []; + for (let i = 0; i < nPaddingBytes; i += 4) { + paddingWords.push(paddingWord); + } + const padding = new WordArray(paddingWords, nPaddingBytes); + + // Add padding + data.concat(padding); + }, + + /** + * Unpads data that had been padded using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to unpad. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.unpad(wordArray); + */ + unpad(data) { + const _data = data; + + // Get number of padding bytes from last byte + const nPaddingBytes = _data.words[(_data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + _data.sigBytes -= nPaddingBytes; + } +}; \ No newline at end of file diff --git a/src/pad/pad-zeropadding.js b/src/pad/pad-zeropadding.js new file mode 100644 index 0000000..276fa53 --- /dev/null +++ b/src/pad/pad-zeropadding.js @@ -0,0 +1,30 @@ +/** + * Zero padding strategy. + */ +export const ZeroPadding = { + pad(data, blockSize) { + const _data = data; + + // Shortcut + const blockSizeBytes = blockSize * 4; + + // Pad + _data.clamp(); + _data.sigBytes += blockSizeBytes - ((data.sigBytes % blockSizeBytes) || blockSizeBytes); + }, + + unpad(data) { + const _data = data; + + // Shortcut + const dataWords = _data.words; + + // Unpad + for (let i = _data.sigBytes - 1; i >= 0; i--) { + if (((dataWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff)) { + _data.sigBytes = i + 1; + break; + } + } + } +}; diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..51581ce --- /dev/null +++ b/src/utils.js @@ -0,0 +1,61 @@ +const toStringPro = Object.prototype.toString; + +/** + * Determine if a value is a String + * + * @param {Object} val The value to test + * @returns {Boolean} True if value is a String, otherwise false + */ +export const isString = (val) => typeof val === 'string'; + +/** + * Determine if a value is a Number + * + * @param {Object} val The value to test + * @returns {Boolean} True if value is a Number, otherwise false + */ +export const isNumber = (val) => typeof val === 'number'; + +/** + * Determine if a value is a undefined + * + * @param {Object} val The value to test + * @returns {Boolean} True if value is a undefined, otherwise false + */ +export const isUndefined = (val) => typeof val === 'undefined'; + +/** + * Determine if a value is a Function + * + * @param {Object} val The value to test + * @returns {Boolean} True if value is a Function, otherwise false + */ +export const isFunction = (val) => toStringPro.call(val) === '[object Function]'; + +/** + * Determine if a value is a Array + * + * @param {Object} val The value to test + * @returns {Boolean} True if value is a Array, otherwise false + */ +export const isArray = (val) => toStringPro.call(val) === '[object, Array]'; + +/** + * Determine if a value is a Object + * + * @param {Object} val The value to test + * @returns {Boolean} True if value is a Object, otherwise false + */ +export const isObject = (val) => toStringPro.call(val) === '[object, object]'; + +/** + * Accepts varargs expecting each argument to be an object, then + * immutably merges the properties of each object and returns result. + * + * When multiple objects contain the same key the later object in + * the arguments list will take precedence. + * + * @param {Object} args Object to merge + * @returns {Object} Result of all merge properties + */ +export const merge = (...args) => args.length && Object.assign(...args); \ No newline at end of file diff --git a/test/aes.profile.test.js b/test/aes.profile.test.js new file mode 100644 index 0000000..695147a --- /dev/null +++ b/test/aes.profile.test.js @@ -0,0 +1,30 @@ +import C from '../src/index.js'; + +let data = {}; +beforeAll(() => { + data.key = C.enc.Hex.parse('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'); + data.iv = C.enc.Hex.parse('000102030405060708090a0b0c0d0e0f'); +}); + +describe('algo-aes-profile', () => { + + test('profileSinglePartMessage', () => { + let singlePartMessage = ''; + for (let i = 0; i < 500; i++) { + singlePartMessage += '12345678901234567890123456789012345678901234567890'; + } + C.algo.AES.createEncryptor(data.key, { + iv: data.iv + }).finalize(singlePartMessage) + ''; + }); + + test('profileMultiPartMessage', () => { + let aes = C.algo.AES.createEncryptor(data.key, { + iv: data.iv + }); + for (let i = 0; i < 500; i++) { + aes.process('12345678901234567890123456789012345678901234567890') + ''; + } + aes.finalize() + ''; + }); +}); \ No newline at end of file diff --git a/test/aes.test.js b/test/aes.test.js new file mode 100644 index 0000000..f136b30 --- /dev/null +++ b/test/aes.test.js @@ -0,0 +1,101 @@ +import C from '../src/index.js'; + + +const ENCRYPT_KEY_SIZE = [ + [128, '00112233445566778899aabbccddeeff', '000102030405060708090a0b0c0d0e0f', '69c4e0d86a7b0430d8cdb78070b4c55a'], + [192, '00112233445566778899aabbccddeeff', '000102030405060708090a0b0c0d0e0f1011121314151617', 'dda97ca4864cdfe06eaf70a0ec0d7191'], + [256, '00112233445566778899aabbccddeeff', '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', '8ea2b7ca516745bfeafc49904b496089'] +]; + +const DECRYPT_KEY_SIZE = [ + [128, '69c4e0d86a7b0430d8cdb78070b4c55a', '000102030405060708090a0b0c0d0e0f', '00112233445566778899aabbccddeeff'], + [192, 'dda97ca4864cdfe06eaf70a0ec0d7191', '000102030405060708090a0b0c0d0e0f1011121314151617', '00112233445566778899aabbccddeeff'], + [256, '8ea2b7ca516745bfeafc49904b496089', '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', '00112233445566778899aabbccddeeff'] +]; + +describe('algo-aes-test', () => { + test.each(ENCRYPT_KEY_SIZE)( + 'testEncryptKeySize%i', + (a, b, c, expected) => { + expect(C.AES.encrypt(C.enc.Hex.parse(b), C.enc.Hex.parse(c), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).ciphertext.toString()).toBe(expected); + } + ); + + test.each(DECRYPT_KEY_SIZE)( + 'testDecryptKeySize%i', + (a, b, c, expected) => { + expect(C.AES.decrypt(new C.lib.CipherParams({ + ciphertext: C.enc.Hex.parse(b) + }), C.enc.Hex.parse(c), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()).toBe(expected); + } + ); + + test('testMultiPart', () => { + let aes = C.algo.AES.createEncryptor(C.enc.Hex.parse('000102030405060708090a0b0c0d0e0f'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }); + let ciphertext1 = aes.process(C.enc.Hex.parse('001122334455')); + let ciphertext2 = aes.process(C.enc.Hex.parse('66778899aa')); + let ciphertext3 = aes.process(C.enc.Hex.parse('bbccddeeff')); + let ciphertext4 = aes.finalize(); + expect(ciphertext1.concat(ciphertext2).concat(ciphertext3).concat(ciphertext4).toString()).toBe('69c4e0d86a7b0430d8cdb78070b4c55a'); + }); + + test('testInputIntegrity', () => { + let message = C.enc.Hex.parse('00112233445566778899aabbccddeeff'); + let key = C.enc.Hex.parse('000102030405060708090a0b0c0d0e0f'); + let iv = C.enc.Hex.parse('101112131415161718191a1b1c1d1e1f'); + let expectedMessage = message.toString(); + let expectedKey = key.toString(); + let expectedIv = iv.toString(); + C.AES.encrypt(message, key, { + iv: iv + }); + expect(message.toString()).toBe(expectedMessage); + expect(key.toString()).toBe(expectedKey); + expect(iv.toString()).toBe(expectedIv); + }); + + test('testHelper', () => { + // Save original random method + let random = C.lib.WordArray.random; + // Replace random method with one that returns a predictable value + C.lib.WordArray.random = function (nBytes) { + let words = []; + for (let i = 0; i < nBytes; i += 4) { + words.push([0x11223344]); + } + return new C.lib.WordArray(words, nBytes); + }; + expect(C.AES.encrypt('Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).ciphertext.toString()).toBe(C.algo.AES.createEncryptor(C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).finalize('Hi There').toString()); + expect(C.AES.encrypt('Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()).toBe(C.lib.SerializableCipher.encrypt(C.algo.AES, 'Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()); + expect(C.AES.encrypt('Hi There', 'Jefe', { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()).toBe(C.lib.PasswordBasedCipher.encrypt(C.algo.AES, 'Hi There', 'Jefe', { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()); + // Restore random method + C.lib.WordArray.random = random; + }); +}); \ No newline at end of file diff --git a/test/algo.pbkdf2.profile.test.js b/test/algo.pbkdf2.profile.test.js new file mode 100644 index 0000000..2df2b2f --- /dev/null +++ b/test/algo.pbkdf2.profile.test.js @@ -0,0 +1,10 @@ +import C from '../src/index'; + +describe('algo-pbkdf2-profile', () => { + test('profileKeySize256Iterations20', () => { + new C.algo.PBKDF2({ + keySize: 256 / 32, + iterations: 20 + }).compute('password', 'ATHENA.MIT.EDUraeburn'); + }); +}); \ No newline at end of file diff --git a/test/algo.pbkdf2.test.js b/test/algo.pbkdf2.test.js new file mode 100644 index 0000000..77576f4 --- /dev/null +++ b/test/algo.pbkdf2.test.js @@ -0,0 +1,119 @@ +import C from '../src/index'; + +describe('algo-pbkdf2-test', () => { + test('testKeySize128', () => { + expect(C.PBKDF2('password', 'ATHENA.MIT.EDUraeburn', { + keySize: 128 / 32 + }).toString()).toBe('cdedb5281bb2f801565a1122b2563515'); + }); + + test('testKeySize256', () => { + expect(C.PBKDF2('password', 'ATHENA.MIT.EDUraeburn', { + keySize: 256 / 32 + }).toString()).toBe('cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837'); + }); + + test('testKeySize128Iterations2', () => { + expect(C.PBKDF2('password', 'ATHENA.MIT.EDUraeburn', { + keySize: 128 / 32, + iterations: 2 + }).toString()).toBe('01dbee7f4a9e243e988b62c73cda935d'); + }); + + test('testKeySize256Iterations2', () => { + expect(C.PBKDF2('password', 'ATHENA.MIT.EDUraeburn', { + keySize: 256 / 32, + iterations: 2 + }).toString()).toBe('01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86'); + }); + + test('testKeySize128Iterations1200', () => { + expect(C.PBKDF2('password', 'ATHENA.MIT.EDUraeburn', { + keySize: 128 / 32, + iterations: 1200 + }).toString()).toBe('5c08eb61fdf71e4e4ec3cf6ba1f5512b'); + }); + + test('testKeySize256Iterations1200', () => { + expect(C.PBKDF2('password', 'ATHENA.MIT.EDUraeburn', { + keySize: 256 / 32, + iterations: 1200 + }).toString()).toBe('5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13'); + }); + + test('testKeySize128Iterations5', () => { + expect(C.PBKDF2('password', C.enc.Hex.parse('1234567878563412'), { + keySize: 128 / 32, + iterations: 5 + }).toString()).toBe('d1daa78615f287e6a1c8b120d7062a49'); + }); + + test('testKeySize256Iterations5', () => { + expect(C.PBKDF2('password', C.enc.Hex.parse('1234567878563412'), { + keySize: 256 / 32, + iterations: 5 + }).toString()).toBe('d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee'); + }); + + test('testKeySize128Iterations1200PassPhraseEqualsBlockSize', () => { + expect(C.PBKDF2('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', 'pass phrase equals block size', { + keySize: 128 / 32, + iterations: 1200 + }).toString()).toBe('139c30c0966bc32ba55fdbf212530ac9'); + }); + + test('testKeySize256Iterations1200PassPhraseEqualsBlockSize', () => { + expect(C.PBKDF2('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', 'pass phrase equals block size', { + keySize: 256 / 32, + iterations: 1200 + }).toString()).toBe('139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1'); + }); + + test('testKeySize128Iterations1200PassPhraseExceedsBlockSize', () => { + expect(C.PBKDF2('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', 'pass phrase exceeds block size', { + keySize: 128 / 32, + iterations: 1200 + }).toString()).toBe('9ccad6d468770cd51b10e6a68721be61'); + }); + + test('testKeySize256Iterations1200PassPhraseExceedsBlockSize', () => { + expect(C.PBKDF2('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', 'pass phrase exceeds block size', { + keySize: 256 / 32, + iterations: 1200 + }).toString()).toBe('9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a'); + }); + + test('testKeySize128Iterations50', () => { + expect(C.PBKDF2(C.enc.Hex.parse('f09d849e'), 'EXAMPLE.COMpianist', { + keySize: 128 / 32, + iterations: 50 + }).toString()).toBe('6b9cf26d45455a43a5b8bb276a403b39'); + }); + + test('testKeySize256Iterations50', () => { + expect(C.PBKDF2(C.enc.Hex.parse('f09d849e'), 'EXAMPLE.COMpianist', { + keySize: 256 / 32, + iterations: 50 + }).toString()).toBe('6b9cf26d45455a43a5b8bb276a403b39e7fe37a0c41e02c281ff3069e1e94f52'); + }); + + test('testInputIntegrity', () => { + let password = new C.lib.WordArray([0x12345678]); + let salt = new C.lib.WordArray([0x12345678]); + + let expectedPassword = password.toString(); + let expectedSalt = salt.toString(); + + C.PBKDF2(password, salt); + expect(password.toString()).toBe(expectedPassword); + expect(salt.toString()).toBe(expectedSalt); + }); + + test('testHelper', () => { + expect(C.PBKDF2('password', 'ATHENA.MIT.EDUraeburn', { + keySize: 128 / 32 + }).toString()).toBe(new C.algo.PBKDF2({ + keySize: 128 / 32 + }).compute('password', 'ATHENA.MIT.EDUraeburn').toString()); + }); +}); \ No newline at end of file diff --git a/test/algo.rabbit.legacy.test.js b/test/algo.rabbit.legacy.test.js new file mode 100644 index 0000000..83a0697 --- /dev/null +++ b/test/algo.rabbit.legacy.test.js @@ -0,0 +1,115 @@ +import C from '../src/index'; + +describe('algo-rabbit-legacy-test', () => { + test('testVector1', () => { + expect(C.RabbitLegacy.encrypt(C.enc.Hex.parse('00000000000000000000000000000000'), C.enc.Hex.parse('00000000000000000000000000000000')).ciphertext.toString()) + .toBe('02f74a1c26456bf5ecd6a536f05457b1'); + }); + + test('testVector2', () => { + expect(C.RabbitLegacy.encrypt(C.enc.Hex.parse('00000000000000000000000000000000'), C.enc.Hex.parse('dc51c3ac3bfc62f12e3d36fe91281329')).ciphertext.toString()) + .toBe('9c51e28784c37fe9a127f63ec8f32d3d'); + }); + + test('testVector3', () => { + expect(C.RabbitLegacy.encrypt(C.enc.Hex.parse('00000000000000000000000000000000'), C.enc.Hex.parse('c09b0043e9e9ab0187e0c73383957415')).ciphertext.toString()) + .toBe('9b60d002fd5ceb32accd41a0cd0db10c'); + }); + + test('testVector4', () => { + expect(C.RabbitLegacy.encrypt(C.enc.Hex.parse('00000000000000000000000000000000'), C.enc.Hex.parse('00000000000000000000000000000000'), { + iv: C.enc.Hex.parse('0000000000000000') + }).ciphertext.toString()) + .toBe('edb70567375dcd7cd89554f85e27a7c6'); + }); + + test('testVector5', () => { + expect(C.RabbitLegacy.encrypt(C.enc.Hex.parse('00000000000000000000000000000000'), C.enc.Hex.parse('00000000000000000000000000000000'), { + iv: C.enc.Hex.parse('597e26c175f573c3') + }).ciphertext.toString()) + .toBe('6d7d012292ccdce0e2120058b94ecd1f'); + }); + + test('testVector6', () => { + expect(C.RabbitLegacy.encrypt(C.enc.Hex.parse('00000000000000000000000000000000'), C.enc.Hex.parse('00000000000000000000000000000000'), { + iv: C.enc.Hex.parse('2717f4d21a56eba6') + }).ciphertext.toString()) + .toBe('4d1051a123afb670bf8d8505c8d85a44'); + }); + + test('testMultiPart', () => { + const rabbit = C.algo.RabbitLegacy.createEncryptor(C.enc.Hex.parse('00000000000000000000000000000000')); + const ciphertext1 = rabbit.process(C.enc.Hex.parse('000000000000')); + const ciphertext2 = rabbit.process(C.enc.Hex.parse('0000000000')); + const ciphertext3 = rabbit.process(C.enc.Hex.parse('0000000000')); + const ciphertext4 = rabbit.finalize(); + + expect(ciphertext1.concat(ciphertext2).concat(ciphertext3).concat(ciphertext4).toString()) + .toBe('02f74a1c26456bf5ecd6a536f05457b1'); + }); + + test('testInputIntegrity', () => { + const message = C.enc.Hex.parse('00000000000000000000000000000000'); + const key = C.enc.Hex.parse('00000000000000000000000000000000'); + const iv = C.enc.Hex.parse('0000000000000000'); + + const expectedMessage = message.toString(); + const expectedKey = key.toString(); + const expectedIv = iv.toString(); + + C.RabbitLegacy.encrypt(message, key, { + iv + }); + + expect(message.toString()).toBe(expectedMessage); + expect(key.toString()).toBe(expectedKey); + expect(iv.toString()).toBe(expectedIv); + }); + + test('testHelper', () => { + // Save original random method + const { + random + } = C.lib.WordArray; + + // Replace random method with one that returns a predictable value + C.lib.WordArray.random = (nBytes) => { + const words = []; + for (let i = 0; i < nBytes; i += 4) { + words.push([0x11223344]); + } + + return new C.lib.WordArray(words, nBytes); + }; + + expect(C.RabbitLegacy.encrypt('Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).ciphertext.toString()) + .toBe(C.algo.RabbitLegacy.createEncryptor(C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).finalize('Hi There').toString()); + + expect(C.RabbitLegacy.encrypt('Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()) + .toBe(C.lib.SerializableCipher.encrypt(C.algo.RabbitLegacy, 'Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()); + + expect(C.RabbitLegacy.encrypt('Hi There', 'Jefe', { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()) + .toBe(C.lib.PasswordBasedCipher.encrypt(C.algo.RabbitLegacy, 'Hi There', 'Jefe', { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()); + + // Restore random method + C.lib.WordArray.random = random; + }); +}); \ No newline at end of file diff --git a/test/algo.rabbit.profile.test.js b/test/algo.rabbit.profile.test.js new file mode 100644 index 0000000..c74d359 --- /dev/null +++ b/test/algo.rabbit.profile.test.js @@ -0,0 +1,26 @@ +import C from '../src/index'; + +const data = {}; + +beforeAll(() => { + data.key = C.enc.Hex.parse('000102030405060708090a0b0c0d0e0f'); +}); + +describe('algo-rabbit-profile', () => { + test('profileSinglePartMessage', () => { + let singlePartMessage = ''; + for (let i = 0; i < 500; i++) { + singlePartMessage += '12345678901234567890123456789012345678901234567890'; + } + + C.algo.Rabbit.createEncryptor(data.key).finalize(singlePartMessage) + ''; + }); + + test('profileMultiPartMessage', () => { + let rabbit = C.algo.Rabbit.createEncryptor(data.key); + for (let i = 0; i < 500; i++) { + rabbit.process('12345678901234567890123456789012345678901234567890') + ''; + } + rabbit.finalize() + ''; + }); +}); \ No newline at end of file diff --git a/test/algo.rabbit.test.js b/test/algo.rabbit.test.js new file mode 100644 index 0000000..a71536d --- /dev/null +++ b/test/algo.rabbit.test.js @@ -0,0 +1,120 @@ +import C from '../src/index'; + +describe('algo-rabbit-test', () => { + test('testVector1', () => { + expect(C.Rabbit.encrypt(C.enc.Hex.parse('00000000000000000000000000000000'), C.enc.Hex.parse('00000000000000000000000000000000')).ciphertext.toString()) + .toBe('02f74a1c26456bf5ecd6a536f05457b1'); + }); + + test('testVector2', () => { + expect(C.Rabbit.encrypt(C.enc.Hex.parse('00000000000000000000000000000000'), C.enc.Hex.parse('c21fcf3881cd5ee8628accb0a9890df8')).ciphertext.toString()) + .toBe('3d02e0c730559112b473b790dee018df'); + }); + + test('testVector3', () => { + expect(C.Rabbit.encrypt(C.enc.Hex.parse('00000000000000000000000000000000'), C.enc.Hex.parse('1d272c6a2d8e3dfcac14056b78d633a0')).ciphertext.toString()) + .toBe('a3a97abb80393820b7e50c4abb53823d'); + }); + + test('testVector4', () => { + expect(C.Rabbit.encrypt(C.enc.Hex.parse('00000000000000000000000000000000'), C.enc.Hex.parse('0053a6f94c9ff24598eb3e91e4378add'), { + iv: C.enc.Hex.parse('0d74db42a91077de') + }).ciphertext.toString()) + .toBe('75d186d6bc6905c64f1b2dfdd51f7bfc'); + }); + + test('testVector5', () => { + expect(C.Rabbit.encrypt(C.enc.Hex.parse('00000000000000000000000000000000'), C.enc.Hex.parse('0558abfe51a4f74a9df04396e93c8fe2'), { + iv: C.enc.Hex.parse('167de44bb21980e7') + }).ciphertext.toString()) + .toBe('476e2750c73856c93563b5f546f56a6a'); + }); + + test('testVector6', () => { + expect(C.Rabbit.encrypt(C.enc.Hex.parse('00000000000000000000000000000000'), C.enc.Hex.parse('0a5db00356a9fc4fa2f5489bee4194e7'), { + iv: C.enc.Hex.parse('1f86ed54bb2289f0') + }).ciphertext.toString()) + .toBe('921fcf4983891365a7dc901924b5e24b'); + }); + + test('testVector7', () => { + expect(C.Rabbit.encrypt(C.enc.Hex.parse('00000000000000000000000000000000'), C.enc.Hex.parse('0f62b5085bae0154a7fa4da0f34699ec'), { + iv: C.enc.Hex.parse('288ff65dc42b92f9') + }).ciphertext.toString()) + .toBe('613cb0ba96aff6cacf2a459a102a7f78'); + }); + + test('testMultiPart', () => { + const rabbit = C.algo.Rabbit.createEncryptor(C.enc.Hex.parse('00000000000000000000000000000000')); + const ciphertext1 = rabbit.process(C.enc.Hex.parse('000000000000')); + const ciphertext2 = rabbit.process(C.enc.Hex.parse('0000000000')); + const ciphertext3 = rabbit.process(C.enc.Hex.parse('0000000000')); + const ciphertext4 = rabbit.finalize(); + + expect(ciphertext1.concat(ciphertext2).concat(ciphertext3).concat(ciphertext4).toString()) + .toBe('02f74a1c26456bf5ecd6a536f05457b1'); + }); + + test('testInputIntegrity', () => { + const message = C.enc.Hex.parse('00000000000000000000000000000000'); + const key = C.enc.Hex.parse('00000000000000000000000000000000'); + const iv = C.enc.Hex.parse('0000000000000000'); + + const expectedMessage = message.toString(); + const expectedKey = key.toString(); + const expectedIv = iv.toString(); + + C.Rabbit.encrypt(message, key, { + iv + }); + + expect(message.toString()).toBe(expectedMessage); + expect(key.toString()).toBe(expectedKey); + expect(iv.toString()).toBe(expectedIv); + }); + + test('testHelper', () => { + // Save original random method + const { + random + } = C.lib.WordArray; + + // Replace random method with one that returns a predictable value + C.lib.WordArray.random = (nBytes) => { + const words = []; + for (let i = 0; i < nBytes; i += 4) { + words.push([0x11223344]); + } + + return new C.lib.WordArray(words, nBytes); + }; + + expect(C.Rabbit.encrypt('Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).ciphertext.toString()) + .toBe(C.algo.Rabbit.createEncryptor(C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).finalize('Hi There').toString()); + expect(C.Rabbit.encrypt('Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()) + .toBe(C.lib.SerializableCipher.encrypt(C.algo.Rabbit, 'Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()); + expect(C.Rabbit.encrypt('Hi There', 'Jefe', { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()) + .toBe(C.lib.PasswordBasedCipher.encrypt(C.algo.Rabbit, 'Hi There', 'Jefe', { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()); + + // Restore random method + C.lib.WordArray.random = random; + }); +}); \ No newline at end of file diff --git a/test/algo.rc4.profile.test.js b/test/algo.rc4.profile.test.js new file mode 100644 index 0000000..2967a95 --- /dev/null +++ b/test/algo.rc4.profile.test.js @@ -0,0 +1,24 @@ +import C from '../src/index'; + +const data = {}; +beforeAll(() => { + data.key = C.enc.Hex.parse('000102030405060708090a0b0c0d0e0f'); +}); + +describe('algo-rc4-profile', () => { + test('profileSinglePartMessage', () => { + let singlePartMessage = ''; + for (let i = 0; i < 500; i++) { + singlePartMessage += '12345678901234567890123456789012345678901234567890'; + } + C.algo.RC4.createEncryptor(data.key).finalize(singlePartMessage) + ''; + }); + + test('profileMultiPartMessage', () => { + let rc4 = C.algo.RC4.createEncryptor(data.key); + for (let i = 0; i < 500; i++) { + rc4.process('12345678901234567890123456789012345678901234567890') + ''; + } + rc4.finalize() + ''; + }); +}); \ No newline at end of file diff --git a/test/algo.rc4.test.js b/test/algo.rc4.test.js new file mode 100644 index 0000000..38a90ff --- /dev/null +++ b/test/algo.rc4.test.js @@ -0,0 +1,89 @@ +import C from '../src/index'; + +describe('rc4', () => { + test('testVector1', () => { + expect(C.RC4.encrypt(C.enc.Hex.parse('0000000000000000'), C.enc.Hex.parse('0123456789abcdef')).ciphertext.toString()) + .toBe('7494c2e7104b0879'); + }); + + test('testVector2', () => { + expect(C.RC4.encrypt(C.enc.Hex.parse('dcee4cf92c'), C.enc.Hex.parse('618a63d2fb')).ciphertext.toString()) + .toBe('f13829c9de'); + }); + + test('drop', () => { + expect(C.RC4Drop.encrypt(C.enc.Hex.parse('0000000000000000'), C.enc.Hex.parse('0123456789abcdef'), { + drop: 2 + }).ciphertext.toString()) + .toBe(C.RC4.encrypt(C.enc.Hex.parse('00000000000000000000000000000000'), C.enc.Hex.parse('0123456789abcdef')).ciphertext.toString().substr(16)); + }); + + test('multi part', () => { + const rabbit = C.algo.RC4.createEncryptor(C.enc.Hex.parse('0123456789abcdef')); + const ciphertext1 = rabbit.process(C.enc.Hex.parse('00000000')); + const ciphertext2 = rabbit.process(C.enc.Hex.parse('0000')); + const ciphertext3 = rabbit.process(C.enc.Hex.parse('0000')); + const ciphertext4 = rabbit.finalize(); + + expect(ciphertext1.concat(ciphertext2).concat(ciphertext3).concat(ciphertext4).toString()) + .toBe('7494c2e7104b0879'); + }); + + test('input integrity', () => { + const message = C.enc.Hex.parse('0000000000000000'); + const key = C.enc.Hex.parse('0123456789abcdef'); + + const expectedMessage = message.toString(); + const expectedKey = key.toString(); + + C.RC4.encrypt(message, key); + + expect(message.toString()).toBe(expectedMessage); + expect(key.toString()).toBe(expectedKey); + }); + + test('test helper', () => { + // Save original random method + const { + random + } = C.lib.WordArray; + + // Replace random method with one that returns a predictable value + C.lib.WordArray.random = (nBytes) => { + const words = []; + for (let i = 0; i < nBytes; i += 4) { + words.push([0x11223344]); + } + + return new C.lib.WordArray(words, nBytes); + }; + + expect(C.RC4.encrypt('Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).ciphertext.toString()) + .toBe(C.algo.RC4.createEncryptor(C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).finalize('Hi There').toString()); + expect(C.RC4.encrypt('Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()) + .toBe(C.lib.SerializableCipher.encrypt(C.algo.RC4, 'Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()); + expect(C.RC4.encrypt('Hi There', 'Jefe', { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()) + .toBe(C.lib.PasswordBasedCipher.encrypt(C.algo.RC4, 'Hi There', 'Jefe', { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()); + + // Restore random method + C.lib.WordArray.random = random; + }); +}); \ No newline at end of file diff --git a/test/algo.ripemd160.test.js b/test/algo.ripemd160.test.js new file mode 100644 index 0000000..d0614fd --- /dev/null +++ b/test/algo.ripemd160.test.js @@ -0,0 +1,16 @@ +import C from '../src/index'; + +const VERTOR_TEST_CONFIG = [ + [1, 'The quick brown fox jumps over the lazy dog', '37f332f68db77bd9d7edd4969571ad671cf9dd3b'], + [2, 'The quick brown fox jumps over the lazy cog', '132072df690933835eb8b6ad0b77e7b6f14acad7'], + [3, '', '9c1185a5c5e9fc54612808977ee8f548b2258d31'] +]; + +describe('algo-ripemd160-test', () => { + test.each(VERTOR_TEST_CONFIG)( + 'testVector%i', + (a, b, expected) => { + expect(C.RIPEMD160(b).toString()).toBe(expected); + } + ); +}); \ No newline at end of file diff --git a/test/algo.sha1.profile.test.js b/test/algo.sha1.profile.test.js new file mode 100644 index 0000000..352ed59 --- /dev/null +++ b/test/algo.sha1.profile.test.js @@ -0,0 +1,20 @@ +import C from '../src/index'; + +describe('algo-sha1-profile', () => { + test('profileSinglePartMessage', () => { + let singlePartMessage = ''; + for (let i = 0; i < 500; i++) { + singlePartMessage += '12345678901234567890123456789012345678901234567890'; + } + + new C.algo.SHA1().finalize(singlePartMessage) + ''; + }); + + test('profileMultiPartMessage', () => { + let sha1 = new C.algo.SHA1(); + for (let i = 0; i < 500; i++) { + sha1.update('12345678901234567890123456789012345678901234567890'); + } + sha1.finalize() + ''; + }); +}); \ No newline at end of file diff --git a/test/algo.sha1.test.js b/test/algo.sha1.test.js new file mode 100644 index 0000000..c5744e7 --- /dev/null +++ b/test/algo.sha1.test.js @@ -0,0 +1,56 @@ +import C from '../src/index'; + +const VECTOR_CONFIG_TEST = [ + [1, '', 'da39a3ee5e6b4b0d3255bfef95601890afd80709'], + [2, 'a', '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8'], + [3, 'abc', 'a9993e364706816aba3e25717850c26c9cd0d89d'], + [4, 'message digest', 'c12252ceda8be8994d5fa0290a47231c1d16aae3'], + [5, 'abcdefghijklmnopqrstuvwxyz', '32d10c7b8cf96570ca04ce37f2a19d84240d3a89'], + [6, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', '761c457bf73b14d27e9e9265c46f4b4dda11f940'], + [7, '12345678901234567890123456789012345678901234567890123456789012345678901234567890', '50abf5706a150990a08b2c5ea40fa0e585554732'] +]; + +describe('algo-sha1-test', () => { + test.each(VECTOR_CONFIG_TEST)( + 'testVector%i', + (a, b, expected) => { + expect(C.SHA1(b).toString()).toBe(expected); + } + ); + + test('testUpdateAndLongMessage', () => { + const sha1 = new C.algo.SHA1(); + for (let i = 0; i < 100; i++) { + sha1.update('12345678901234567890123456789012345678901234567890'); + } + + expect(sha1.finalize().toString()).toBe('85e4c4b3933d5553ebf82090409a9d90226d845c'); + }); + + test('testClone', () => { + const sha1 = new C.algo.SHA1(); + + expect(sha1.update('a').clone().finalize().toString()).toBe(C.SHA1('a').toString()); + expect(sha1.update('b').clone().finalize().toString()).toBe(C.SHA1('ab').toString()); + expect(sha1.update('c').clone().finalize().toString()).toBe(C.SHA1('abc').toString()); + }); + + test('testInputIntegrity', () => { + const message = new C.lib.WordArray([0x12345678]); + + const expected = message.toString(); + + C.SHA1(message); + + expect(message.toString()).toBe(expected); + }); + + test('testHelper', () => { + expect(C.SHA1('').toString()).toBe(new C.algo.SHA1().finalize('').toString()); + }); + + test('testHmacHelper', () => { + expect(C.HmacSHA1('Hi There', C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).toString()) + .toBe(new C.algo.HMAC(C.algo.SHA1, C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).finalize('Hi There').toString()); + }); +}); \ No newline at end of file diff --git a/test/algo.sha224.test.js b/test/algo.sha224.test.js new file mode 100644 index 0000000..3cf1aa1 --- /dev/null +++ b/test/algo.sha224.test.js @@ -0,0 +1,16 @@ +import C from '../src/index'; + +const VECTOR_TEST_CONFIG = [ + [1, '', 'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f'], + [1, 'The quick brown fox jumps over the lazy dog', '730e109bd7a8a32b1cb9d9a09aa2325d2430587ddbc0c38bad911525'], + [1, 'The quick brown fox jumps over the lazy dog.', '619cba8e8e05826e9b8c519c0a5c68f4fb653e8a3d8aa04bb2c8cd4c'] +]; + +describe('algo-sha224-test', () => { + test.each(VECTOR_TEST_CONFIG)( + 'testVector%i', + (a, b, expected) => { + expect(C.SHA224(b).toString()).toBe(expected); + } + ); +}); \ No newline at end of file diff --git a/test/algo.sha256.profile.test.js b/test/algo.sha256.profile.test.js new file mode 100644 index 0000000..5875baf --- /dev/null +++ b/test/algo.sha256.profile.test.js @@ -0,0 +1,19 @@ +import C from '../src/index'; + +describe('algo-sha256-profile', () => { + test('profileSinglePartMessage', () => { + let singlePartMessage = ''; + for (let i = 0; i < 500; i++) { + singlePartMessage += '12345678901234567890123456789012345678901234567890'; + } + new C.algo.SHA256().finalize(singlePartMessage) + ''; + }); + + test('profileMultiPartMessage', () => { + let sha256 = new C.algo.SHA256(); + for (let i = 0; i < 500; i++) { + sha256.update('12345678901234567890123456789012345678901234567890'); + } + sha256.finalize() + ''; + }); +}); \ No newline at end of file diff --git a/test/algo.sha256.test.js b/test/algo.sha256.test.js new file mode 100644 index 0000000..d5a40d6 --- /dev/null +++ b/test/algo.sha256.test.js @@ -0,0 +1,50 @@ +import C from '../src/index'; + +const VECTOR_CONFIG_TEST = [ + [1, '', 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'], + [2, 'a', 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb'], + [3, 'abc', 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'], + [4, 'message digest', 'f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650'], + [5, 'abcdefghijklmnopqrstuvwxyz', '71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73'], + [6, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 'db4bfcbd4da0cd85a60c3c37d3fbd8805c77f15fc6b1fdfe614ee0a7c8fdb4c0'], + [7, '12345678901234567890123456789012345678901234567890123456789012345678901234567890', 'f371bc4a311f2b009eef952dd83ca80e2b60026c8e935592d0f9c308453c813e'] +]; + +describe('algo-sha256-test', () => { + test.each(VECTOR_CONFIG_TEST)( + 'testVector%i', + (a, b, expected) => { + expect(C.SHA256(b).toString()).toBe(expected); + } + ); + + test('testUpdateAndLongMessage', () => { + let sha256 = new C.algo.SHA256(); + for (let i = 0; i < 100; i++) { + sha256.update('12345678901234567890123456789012345678901234567890'); + } + expect(sha256.finalize().toString()).toBe('f8146961d9b73d8da49ccd526fca65439cdd5b402f76971556d5f52fd129843e'); + }); + + test('testClone', () => { + let sha256 = new C.algo.SHA256(); + expect(sha256.update('a').clone().finalize().toString()).toBe(C.SHA256('a').toString()); + expect(sha256.update('b').clone().finalize().toString()).toBe(C.SHA256('ab').toString()); + expect(sha256.update('c').clone().finalize().toString()).toBe(C.SHA256('abc').toString()); + }); + + test('testInputIntegrity', () => { + let message = new C.lib.WordArray([0x12345678]); + let expected = message.toString(); + C.SHA256(message); + expect(message.toString()).toBe(expected); + }); + + test('testHelper', () => { + expect(C.SHA256('').toString()).toBe(new C.algo.SHA256().finalize('').toString()); + }); + + test('testHmacHelper', () => { + expect(C.HmacSHA256('Hi There', C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).toString()).toBe(new C.algo.HMAC(C.algo.SHA256, C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).finalize('Hi There').toString()); + }); +}); \ No newline at end of file diff --git a/test/algo.sha3.profile.test.js b/test/algo.sha3.profile.test.js new file mode 100644 index 0000000..0a4518c --- /dev/null +++ b/test/algo.sha3.profile.test.js @@ -0,0 +1,20 @@ +import C from '../src/index'; + +describe('algo-sha3-profile', () => { + test('profileSinglePartMessage', () => { + let singlePartMessage = ''; + for (let i = 0; i < 500; i++) { + singlePartMessage += '12345678901234567890123456789012345678901234567890'; + } + + new C.algo.SHA3().finalize(singlePartMessage) + ''; + }); + + test('profileMultiPartMessage', () => { + let sha3 = new C.algo.SHA3(); + for (let i = 0; i < 500; i++) { + sha3.update('12345678901234567890123456789012345678901234567890'); + } + sha3.finalize() + ''; + }); +}); \ No newline at end of file diff --git a/test/algo.sha3.test.js b/test/algo.sha3.test.js new file mode 100644 index 0000000..9df38a1 --- /dev/null +++ b/test/algo.sha3.test.js @@ -0,0 +1,96 @@ +import C from '../src/index'; + +describe('algo-sha3-test', () => { + test('testVector1', () => { + expect(C.SHA3('', { + outputLength: 512 + }).toString()) + .toBe('0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e'); + }); + + test('testVector2', () => { + expect(C.SHA3(C.enc.Hex.parse('3a3a819c48efde2ad914fbf00e18ab6bc4f14513ab27d0c178a188b61431e7f5623cb66b23346775d386b50e982c493adbbfc54b9a3cd383382336a1a0b2150a15358f336d03ae18f666c7573d55c4fd181c29e6ccfde63ea35f0adf5885cfc0a3d84a2b2e4dd24496db789e663170cef74798aa1bbcd4574ea0bba40489d764b2f83aadc66b148b4a0cd95246c127d5871c4f11418690a5ddf01246a0c80a43c70088b6183639dcfda4125bd113a8f49ee23ed306faac576c3fb0c1e256671d817fc2534a52f5b439f72e424de376f4c565cca82307dd9ef76da5b7c4eb7e085172e328807c02d011ffbf33785378d79dc266f6a5be6bb0e4a92eceebaeb1'), { + outputLength: 512 + }).toString()) + .toBe('81950e7096d31d4f22e3db71cac725bf59e81af54c7ca9e6aeee71c010fc5467466312a01aa5c137cfb140646941556796f612c9351268737c7e9a2b9631d1fa'); + }); + + test('testVector3', () => { + expect(C.SHA3('', { + outputLength: 384 + }).toString()) + .toBe('2c23146a63a29acf99e73b88f8c24eaa7dc60aa771780ccc006afbfa8fe2479b2dd2b21362337441ac12b515911957ff'); + }); + + test('testVector4', () => { + expect(C.SHA3(C.enc.Hex.parse('3a3a819c48efde2ad914fbf00e18ab6bc4f14513ab27d0c178a188b61431e7f5623cb66b23346775d386b50e982c493adbbfc54b9a3cd383382336a1a0b2150a15358f336d03ae18f666c7573d55c4fd181c29e6ccfde63ea35f0adf5885cfc0a3d84a2b2e4dd24496db789e663170cef74798aa1bbcd4574ea0bba40489d764b2f83aadc66b148b4a0cd95246c127d5871c4f11418690a5ddf01246a0c80a43c70088b6183639dcfda4125bd113a8f49ee23ed306faac576c3fb0c1e256671d817fc2534a52f5b439f72e424de376f4c565cca82307dd9ef76da5b7c4eb7e085172e328807c02d011ffbf33785378d79dc266f6a5be6bb0e4a92eceebaeb1'), { + outputLength: 384 + }).toString()) + .toBe('6bff1c8405a3fe594e360e3bccea1ebcd509310dc79b9e45c263783d7a5dd662c6789b18bd567dbdda1554f5bee6a860'); + }); + + test('testVector5', () => { + expect(C.SHA3('', { + outputLength: 256 + }).toString()) + .toBe('c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'); + }); + + test('testVector6', () => { + expect(C.SHA3(C.enc.Hex.parse('3a3a819c48efde2ad914fbf00e18ab6bc4f14513ab27d0c178a188b61431e7f5623cb66b23346775d386b50e982c493adbbfc54b9a3cd383382336a1a0b2150a15358f336d03ae18f666c7573d55c4fd181c29e6ccfde63ea35f0adf5885cfc0a3d84a2b2e4dd24496db789e663170cef74798aa1bbcd4574ea0bba40489d764b2f83aadc66b148b4a0cd95246c127d5871c4f11418690a5ddf01246a0c80a43c70088b6183639dcfda4125bd113a8f49ee23ed306faac576c3fb0c1e256671d817fc2534a52f5b439f72e424de376f4c565cca82307dd9ef76da5b7c4eb7e085172e328807c02d011ffbf33785378d79dc266f6a5be6bb0e4a92eceebaeb1'), { + outputLength: 256 + }).toString()) + .toBe('348fb774adc970a16b1105669442625e6adaa8257a89effdb5a802f161b862ea'); + }); + + test('testVector7', () => { + expect(C.SHA3('', { + outputLength: 224 + }).toString()) + .toBe('f71837502ba8e10837bdd8d365adb85591895602fc552b48b7390abd'); + }); + + test('testVector8', () => { + expect(C.SHA3(C.enc.Hex.parse('3a3a819c48efde2ad914fbf00e18ab6bc4f14513ab27d0c178a188b61431e7f5623cb66b23346775d386b50e982c493adbbfc54b9a3cd383382336a1a0b2150a15358f336d03ae18f666c7573d55c4fd181c29e6ccfde63ea35f0adf5885cfc0a3d84a2b2e4dd24496db789e663170cef74798aa1bbcd4574ea0bba40489d764b2f83aadc66b148b4a0cd95246c127d5871c4f11418690a5ddf01246a0c80a43c70088b6183639dcfda4125bd113a8f49ee23ed306faac576c3fb0c1e256671d817fc2534a52f5b439f72e424de376f4c565cca82307dd9ef76da5b7c4eb7e085172e328807c02d011ffbf33785378d79dc266f6a5be6bb0e4a92eceebaeb1'), { + outputLength: 224 + }).toString()) + .toBe('5af56987ea9cf11fcd0eac5ebc14b037365e9b1123e31cb2dfc7929a'); + }); + + test('testDefaultOutputLength', () => { + expect(C.SHA3('').toString()) + .toBe('0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e'); + }); + + test('testClone', () => { + const sha3 = new C.algo.SHA3(); + + expect(sha3.update('a').clone().finalize().toString()).toBe(C.SHA3('a').toString()); + expect(sha3.update('b').clone().finalize().toString()).toBe(C.SHA3('ab').toString()); + expect(sha3.update('c').clone().finalize().toString()).toBe(C.SHA3('abc').toString()); + }); + + test('testInputIntegrity', () => { + const message = new C.lib.WordArray([0x12345678]); + + const expected = message.toString(); + + C.SHA3(message); + + expect(message.toString()).toBe(expected); + }); + + test('testHelper', () => { + expect(C.SHA3('', { + outputLength: 256 + }).toString()) + .toBe(new C.algo.SHA3({ + outputLength: 256 + }).finalize('').toString()); + }); + + test('testHmacHelper', () => { + expect(C.HmacSHA3('Hi There', C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).toString()) + .toBe(new C.algo.HMAC(C.algo.SHA3, C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).finalize('Hi There').toString()); + }); +}); \ No newline at end of file diff --git a/test/algo.sha384.test.js b/test/algo.sha384.test.js new file mode 100644 index 0000000..6fd7e52 --- /dev/null +++ b/test/algo.sha384.test.js @@ -0,0 +1,53 @@ +import C from '../src/index'; + +const VECTOR_CONFIG_TEST = [ + [1, '', '38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b'], + [2, 'The quick brown fox jumps over the lazy dog', 'ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1'], + [3, 'The quick brown fox jumps over the lazy dog.', 'ed892481d8272ca6df370bf706e4d7bc1b5739fa2177aae6c50e946678718fc67a7af2819a021c2fc34e91bdb63409d7'] +]; + +describe('algo-sha384-test', () => { + test.each(VECTOR_CONFIG_TEST)( + 'testVector%i', + (a, b, expected) => { + expect(C.SHA384(b).toString()).toBe(expected); + } + ); + + test('testUpdateAndLongMessage', () => { + const sha384 = new C.algo.SHA384(); + for (let i = 0; i < 100; i++) { + sha384.update('12345678901234567890123456789012345678901234567890'); + } + + expect(sha384.finalize().toString()) + .toBe('297a519246d6f639a4020119e1f03fc8d77171647b2ff75ea4125b7150fed0cdcc93f8dca1c3c6a624d5e88d780d82cd'); + }); + + test('testClone', () => { + const sha384 = new C.algo.SHA384(); + + expect(sha384.update('a').clone().finalize().toString()).toBe(C.SHA384('a').toString()); + expect(sha384.update('b').clone().finalize().toString()).toBe(C.SHA384('ab').toString()); + expect(sha384.update('c').clone().finalize().toString()).toBe(C.SHA384('abc').toString()); + }); + + test('testInputIntegrity', () => { + const message = new C.lib.WordArray([0x12345678]); + + const expected = message.toString(); + + C.SHA384(message); + + expect(message.toString()).toBe(expected); + }); + + test('testHelper', () => { + expect(C.SHA384('').toString()).toBe(new C.algo.SHA384().finalize('').toString()); + }); + + test('testHmacHelper', () => { + expect(C.HmacSHA384('Hi There', C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).toString()) + .toBe(new C.algo.HMAC(C.algo.SHA384, C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).finalize('Hi There').toString()); + }); +}); \ No newline at end of file diff --git a/test/algo.sha512.profile.test.js b/test/algo.sha512.profile.test.js new file mode 100644 index 0000000..189bd50 --- /dev/null +++ b/test/algo.sha512.profile.test.js @@ -0,0 +1,19 @@ +import C from '../src/index'; + +describe('algo-sha512-profile', () => { + test('profileSinglePartMessage', () => { + let singlePartMessage = ''; + for (let i = 0; i < 500; i++) { + singlePartMessage += '12345678901234567890123456789012345678901234567890'; + } + new C.algo.SHA512().finalize(singlePartMessage) + ''; + }); + + test('profileMultiPartMessage', () => { + let sha512 = new C.algo.SHA512(); + for (let i = 0; i < 500; i++) { + sha512.update('12345678901234567890123456789012345678901234567890'); + } + sha512.finalize() + ''; + }); +}); \ No newline at end of file diff --git a/test/algo.sha512.test.js b/test/algo.sha512.test.js new file mode 100644 index 0000000..7b0f9ce --- /dev/null +++ b/test/algo.sha512.test.js @@ -0,0 +1,53 @@ +import C from '../src/index'; + +const VECTOR_CONFIG_TEST = [ + [1, '', 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e'], + [2, 'The quick brown fox jumps over the lazy dog', '07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6'], + [3, 'The quick brown fox jumps over the lazy dog.', '91ea1245f20d46ae9a037a989f54f1f790f0a47607eeb8a14d12890cea77a1bbc6c7ed9cf205e67b7f2b8fd4c7dfd3a7a8617e45f3c463d481c7e586c39ac1ed'] +]; + +describe('algo-sha512-test', () => { + test.each(VECTOR_CONFIG_TEST)( + 'testVector%i', + (a, b, expected) => { + expect(C.SHA512(b).toString()).toBe(expected); + } + ); + + test('testUpdateAndLongMessage', () => { + const sha512 = new C.algo.SHA512(); + for (let i = 0; i < 100; i++) { + sha512.update('12345678901234567890123456789012345678901234567890'); + } + + expect(sha512.finalize().toString()) + .toBe('9bc64f37c54606dff234b6607e06683c7ba248558d0ec74a11525d9f59e0be566489cc9413c00ca5e9db705fc52ba71214bcf118f65072fe284af8f8cf9500af'); + }); + + test('testClone', () => { + const sha512 = new C.algo.SHA512(); + + expect(sha512.update('a').clone().finalize().toString()).toBe(C.SHA512('a').toString()); + expect(sha512.update('b').clone().finalize().toString()).toBe(C.SHA512('ab').toString()); + expect(sha512.update('c').clone().finalize().toString()).toBe(C.SHA512('abc').toString()); + }); + + test('testInputIntegrity', () => { + const message = new C.lib.WordArray([0x12345678]); + + const expected = message.toString(); + + C.SHA512(message); + + expect(message.toString()).toBe(expected); + }); + + test('testHelper', () => { + expect(C.SHA512('').toString()).toBe(new C.algo.SHA512().finalize('').toString()); + }); + + test('testHmacHelper', () => { + expect(C.HmacSHA512('Hi There', C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).toString()) + .toBe(new C.algo.HMAC(C.algo.SHA512, C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).finalize('Hi There').toString()); + }); +}); \ No newline at end of file diff --git a/test/algo.tripledes.profile.test.js b/test/algo.tripledes.profile.test.js new file mode 100644 index 0000000..e5ba48f --- /dev/null +++ b/test/algo.tripledes.profile.test.js @@ -0,0 +1,30 @@ +import C from '../src/index'; + +const data = {}; +beforeAll(() => { + data.key = C.enc.Hex.parse('0001020304050607'); + data.iv = C.enc.Hex.parse('08090a0b0c0d0e0f'); +}); + +describe('algo-tripledes-profile', () => { + test('profileSinglePartMessage', () => { + let singlePartMessage = ''; + for (let i = 0; i < 100; i++) { + singlePartMessage += '12345678901234567890123456789012345678901234567890'; + } + + C.algo.TripleDES.createEncryptor(data.key, { + iv: data.iv + }).finalize(singlePartMessage) + ''; + }); + + test('profileMultiPartMessage', () => { + let des = C.algo.TripleDES.createEncryptor(data.key, { + iv: data.iv + }); + for (let i = 0; i < 100; i++) { + des.process('12345678901234567890123456789012345678901234567890') + ''; + } + des.finalize() + ''; + }); +}); \ No newline at end of file diff --git a/test/algo.tripledes.test.js b/test/algo.tripledes.test.js new file mode 100644 index 0000000..3ee21f5 --- /dev/null +++ b/test/algo.tripledes.test.js @@ -0,0 +1,111 @@ +import C from '../src/index'; + +const ENCRYPT_TEST_CONFIG = [ + [1, '0000000000000000', '800101010101010180010101010101018001010101010101', '95a8d72813daa94d'], + [1, '0000000000000000', '010101010101010201010101010101020101010101010102', '869efd7f9f265a09'], + [1, '8000000000000000', '010101010101010101010101010101010101010101010101', '95f8a5e5dd31d900'], + [1, '0000000000000001', '010101010101010101010101010101010101010101010101', '166b40b44aba4bd6'] +]; + +const DECRYPT_TEST_CONFIG = [ + [1, '95a8d72813daa94d', '800101010101010180010101010101018001010101010101', '0000000000000000'], + [1, '869efd7f9f265a09', '010101010101010201010101010101020101010101010102', '0000000000000000'], + [1, '95f8a5e5dd31d900', '010101010101010101010101010101010101010101010101', '8000000000000000'], + [1, '166b40b44aba4bd6', '010101010101010101010101010101010101010101010101', '0000000000000001'] +]; + +describe('algo-tripledes-test', () => { + test.each(ENCRYPT_TEST_CONFIG)( + 'testEncrypt%i', + (a, b, c, expected) => { + expect(C.TripleDES.encrypt(C.enc.Hex.parse(b), C.enc.Hex.parse(c), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).ciphertext.toString()).toBe(expected); + } + ); + + test.each(DECRYPT_TEST_CONFIG)( + 'testDecrypt%i', + (a, b, c, expected) => { + expect(C.TripleDES.decrypt(new C.lib.CipherParams({ + ciphertext: C.enc.Hex.parse(b) + }), C.enc.Hex.parse(c), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()).toBe(expected); + } + ); + + test('testMultiPart', () => { + let des = C.algo.TripleDES.createEncryptor(C.enc.Hex.parse('000102030405060708090a0b0c0d0e0f1011121314151617'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }); + let ciphertext1 = des.process(C.enc.Hex.parse('001122334455')); + let ciphertext2 = des.process(C.enc.Hex.parse('66778899aa')); + let ciphertext3 = des.process(C.enc.Hex.parse('bbccddeeff')); + let ciphertext4 = des.finalize(); + expect(ciphertext1.concat(ciphertext2).concat(ciphertext3).concat(ciphertext4).toString()).toBe(C.TripleDES.encrypt(C.enc.Hex.parse('00112233445566778899aabbccddeeff'), C.enc.Hex.parse('000102030405060708090a0b0c0d0e0f1011121314151617'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).ciphertext.toString()); + }); + + test('testInputIntegrity', () => { + let message = C.enc.Hex.parse('00112233445566778899aabbccddeeff'); + let key = C.enc.Hex.parse('000102030405060708090a0b0c0d0e0f1011121314151617'); + let iv = C.enc.Hex.parse('08090a0b0c0d0e0f'); + + let expectedMessage = message.toString(); + let expectedKey = key.toString(); + let expectedIv = iv.toString(); + + C.TripleDES.encrypt(message, key, { + iv: iv + }); + + expect(message.toString()).toBe(expectedMessage); + expect(key.toString()).toBe(expectedKey); + expect(iv.toString()).toBe(expectedIv); + }); + + test('testHelper', () => { + // Save original random method + let random = C.lib.WordArray.random; + + // Replace random method with one that returns a predictable value + C.lib.WordArray.random = function (nBytes) { + let words = []; + for (let i = 0; i < nBytes; i += 4) { + words.push([0x11223344]); + } + + return new C.lib.WordArray(words, nBytes); + }; + + expect(C.TripleDES.encrypt('Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).ciphertext.toString()).toBe(C.algo.TripleDES.createEncryptor(C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).finalize('Hi There').toString()); + expect(C.TripleDES.encrypt('Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()).toBe(C.lib.SerializableCipher.encrypt(C.algo.TripleDES, 'Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()); + expect(C.TripleDES.encrypt('Hi There', 'Jefe', { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()).toBe(C.lib.PasswordBasedCipher.encrypt(C.algo.TripleDES, 'Hi There', 'Jefe', { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()); + // Restore random method + C.lib.WordArray.random = random; + }); +}); \ No newline at end of file diff --git a/test/cipher.test.js b/test/cipher.test.js new file mode 100644 index 0000000..32013b5 --- /dev/null +++ b/test/cipher.test.js @@ -0,0 +1,516 @@ +import C from '../src/index'; +import { + isString, + isArray, + isFunction +} from '../src/utils'; + +function extendWithCMAC(C) { + function createExt(C) { + /* + * The MIT License (MIT) + * + * Copyright (c) 2015 artjomb + */ + // put on ext property in CryptoJS + let ext; + if (!Object.prototype.hasOwnProperty.call(C, 'ext')) { + ext = C.ext = {}; + } else { + ext = C.ext; + } + + // Shortcuts + let WordArray = C.lib.WordArray; + + // Constants + ext.const_Zero = new WordArray([0x00000000, 0x00000000, 0x00000000, 0x00000000]); + ext.const_One = new WordArray([0x00000000, 0x00000000, 0x00000000, 0x00000001]); + ext.const_Rb = new WordArray([0x00000000, 0x00000000, 0x00000000, 0x00000087]); // 00..0010000111 + ext.const_Rb_Shifted = new WordArray([0x80000000, 0x00000000, 0x00000000, 0x00000043]); // 100..001000011 + ext.const_nonMSB = new WordArray([0xFFFFFFFF, 0xFFFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF]); // 1^64 || 0^1 || 1^31 || 0^1 || 1^31 + + /** + * Looks into the object to see if it is a WordArray. + * + * @param obj Some object + * + * @returns {boolean} + */ + ext.isWordArray = function (obj) { + return obj && isFunction(obj.clamp) && isFunction(obj.concat) && isArray(obj.words); + }; + + /** + * This padding is a 1 bit followed by as many 0 bits as needed to fill + * up the block. This implementation doesn't work on bits directly, + * but on bytes. Therefore the granularity is much bigger. + */ + C.pad.OneZeroPadding = { + pad: function (data, blocksize) { + // Shortcut + let blockSizeBytes = blocksize * 4; + + // Count padding bytes + let nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Create padding + let paddingWords = []; + for (let i = 0; i < nPaddingBytes; i += 4) { + let paddingWord = 0x00000000; + if (i === 0) { + paddingWord = 0x80000000; + } + paddingWords.push(paddingWord); + } + let padding = new WordArray(paddingWords, nPaddingBytes); + + // Add padding + data.concat(padding); + }, + unpad: function () { + // TODO: implement + } + }; + + /** + * No padding is applied. This is necessary for streaming cipher modes + * like CTR. + */ + C.pad.NoPadding = { + pad: function () {}, + unpad: function () {} + }; + + /** + * Returns the n leftmost bytes of the WordArray. + * + * @param {WordArray} wordArray WordArray to work on + * @param {int} n Bytes to retrieve + * + * @returns new WordArray + */ + ext.leftmostBytes = function (wordArray, n) { + let lmArray = wordArray.clone(); + lmArray.sigBytes = n; + lmArray.clamp(); + return lmArray; + }; + + /** + * Returns the n rightmost bytes of the WordArray. + * + * @param {WordArray} wordArray WordArray to work on + * @param {int} n Bytes to retrieve (must be positive) + * + * @returns new WordArray + */ + ext.rightmostBytes = function (wordArray, n) { + wordArray.clamp(); + let wordSize = 32; + let rmArray = wordArray.clone(); + let bitsToShift = (rmArray.sigBytes - n) * 8; + if (bitsToShift >= wordSize) { + let popCount = Math.floor(bitsToShift / wordSize); + bitsToShift -= popCount * wordSize; + rmArray.words.splice(0, popCount); + rmArray.sigBytes -= popCount * wordSize / 8; + } + if (bitsToShift > 0) { + ext.bitshift(rmArray, bitsToShift); + rmArray.sigBytes -= bitsToShift / 8; + } + return rmArray; + }; + + /** + * Returns the n rightmost words of the WordArray. It assumes + * that the current WordArray has at least n words. + * + * @param {WordArray} wordArray WordArray to work on + * @param {int} n Words to retrieve (must be positive) + * + * @returns popped words as new WordArray + */ + ext.popWords = function (wordArray, n) { + let left = wordArray.words.splice(0, n); + wordArray.sigBytes -= n * 4; + return new WordArray(left); + }; + + /** + * Shifts the array to the left and returns the shifted dropped elements + * as WordArray. The initial WordArray must contain at least n bytes and + * they have to be significant. + * + * @param {WordArray} wordArray WordArray to work on (is modified) + * @param {int} n Bytes to shift (must be positive, default 16) + * + * @returns new WordArray + */ + ext.shiftBytes = function (wordArray, n) { + n = n || 16; + let r = n % 4; + n -= r; + + let shiftedArray = new WordArray(); + for (let i = 0; i < n; i += 4) { + shiftedArray.words.push(wordArray.words.shift()); + wordArray.sigBytes -= 4; + shiftedArray.sigBytes += 4; + } + if (r > 0) { + shiftedArray.words.push(wordArray.words[0]); + shiftedArray.sigBytes += r; + + ext.bitshift(wordArray, r * 8); + wordArray.sigBytes -= r; + } + return shiftedArray; + }; + + /** + * XORs arr2 to the end of arr1 array. This doesn't modify the current + * array aside from clamping. + * + * @param {WordArray} arr1 Bigger array + * @param {WordArray} arr2 Smaller array to be XORed to the end + * + * @returns new WordArray + */ + ext.xorendBytes = function (arr1, arr2) { + // TODO: more efficient + return ext.leftmostBytes(arr1, arr1.sigBytes - arr2.sigBytes) + .concat(ext.xor(ext.rightmostBytes(arr1, arr2.sigBytes), arr2)); + }; + + /** + * Doubling operation on a 128-bit value. This operation modifies the + * passed array. + * + * @param {WordArray} wordArray WordArray to work on + * + * @returns passed WordArray + */ + ext.dbl = function (wordArray) { + let carry = ext.msb(wordArray); + ext.bitshift(wordArray, 1); + ext.xor(wordArray, carry === 1 ? ext.const_Rb : ext.const_Zero); + return wordArray; + }; + + /** + * Inverse operation on a 128-bit value. This operation modifies the + * passed array. + * + * @param {WordArray} wordArray WordArray to work on + * + * @returns passed WordArray + */ + ext.inv = function (wordArray) { + let carry = wordArray.words[4] & 1; + ext.bitshift(wordArray, -1); + ext.xor(wordArray, carry === 1 ? ext.const_Rb_Shifted : ext.const_Zero); + return wordArray; + }; + + /** + * Check whether the word arrays are equal. + * + * @param {WordArray} arr1 Array 1 + * @param {WordArray} arr2 Array 2 + * + * @returns boolean + */ + ext.equals = function (arr1, arr2) { + if (!arr2 || !arr2.words || arr1.sigBytes !== arr2.sigBytes) { + return false; + } + arr1.clamp(); + arr2.clamp(); + let equal = 0; + for (let i = 0; i < arr1.words.length; i++) { + equal |= arr1.words[i] ^ arr2.words[i]; + } + return equal === 0; + }; + + /** + * Retrieves the most significant bit of the WordArray as an Integer. + * + * @param {WordArray} arr + * + * @returns Integer + */ + ext.msb = function (arr) { + return arr.words[0] >>> 31; + }; + } + + function createExtBit(C) { + /* + * The MIT License (MIT) + * + * Copyright (c) 2015 artjomb + */ + // put on ext property in CryptoJS + let ext; + if (!Object.prototype.hasOwnProperty.call(C, 'ext')) { + ext = C.ext = {}; + } else { + ext = C.ext; + } + + /** + * Shifts the array by n bits to the left. Zero bits are added as the + * least significant bits. This operation modifies the current array. + * + * @param {WordArray} wordArray WordArray to work on + * @param {int} n Bits to shift by + * + * @returns the WordArray that was passed in + */ + ext.bitshift = function (wordArray, n) { + let carry = 0, + words = wordArray.words, + wres, + skipped = 0, + carryMask; + if (n > 0) { + while (n > 31) { + // delete first element: + words.splice(0, 1); + + // add `0` word to the back + words.push(0); + + n -= 32; + skipped++; + } + if (n == 0) { + // 1. nothing to shift if the shift amount is on a word boundary + // 2. This has to be done, because the following algorithm computes + // wrong values only for n==0 + return carry; + } + for (let i = words.length - skipped - 1; i >= 0; i--) { + wres = words[i]; + words[i] <<= n; + words[i] |= carry; + carry = wres >>> (32 - n); + } + } else if (n < 0) { + while (n < -31) { + // insert `0` word to the front: + words.splice(0, 0, 0); + + // remove last element: + words.length--; + + n += 32; + skipped++; + } + if (n == 0) { + // nothing to shift if the shift amount is on a word boundary + return carry; + } + n = -n; + carryMask = (1 << n) - 1; + for (let i = skipped; i < words.length; i++) { + wres = words[i] & carryMask; + words[i] >>>= n; + words[i] |= carry; + carry = wres << (32 - n); + } + } + return carry; + }; + + /** + * Negates all bits in the WordArray. This manipulates the given array. + * + * @param {WordArray} wordArray WordArray to work on + * + * @returns the WordArray that was passed in + */ + ext.neg = function (wordArray) { + let words = wordArray.words; + for (let i = 0; i < words.length; i++) { + words[i] = ~words[i]; + } + return wordArray; + }; + + /** + * Applies XOR on both given word arrays and returns a third resulting + * WordArray. The initial word arrays must have the same length + * (significant bytes). + * + * @param {WordArray} wordArray1 WordArray + * @param {WordArray} wordArray2 WordArray + * + * @returns first passed WordArray (modified) + */ + ext.xor = function (wordArray1, wordArray2) { + for (let i = 0; i < wordArray1.words.length; i++) { + wordArray1.words[i] ^= wordArray2.words[i]; + } + return wordArray1; + }; + + /** + * Logical AND between the two passed arrays. Both arrays must have the + * same length. + * + * @param {WordArray} arr1 Array 1 + * @param {WordArray} arr2 Array 2 + * + * @returns new WordArray + */ + ext.bitand = function (arr1, arr2) { + let newArr = arr1.clone(), + tw = newArr.words, + ow = arr2.words; + for (let i = 0; i < tw.length; i++) { + tw[i] &= ow[i]; + } + return newArr; + }; + } + + function createCMAC(C) { + /* + * The MIT License (MIT) + * + * Copyright (c) 2015 artjomb + */ + // Shortcuts + let Base = C.lib.Base; + let WordArray = C.lib.WordArray; + let AES = C.algo.AES; + let ext = C.ext; + let OneZeroPadding = C.pad.OneZeroPadding; + + let CMAC = C.algo.CMAC = class CMAC extends Base { + constructor(key) { + super(); + // generate sub keys... + this._aes = AES.createEncryptor(key, { + iv: new WordArray(), + padding: C.pad.NoPadding + }); + + this._isTwo = false; + + // Step 1 + let L = this._aes.finalize(ext.const_Zero); + + // Step 2 + let K1 = L.clone(); + ext.dbl(K1); + + // Step 3 + let K2; + if (!this._isTwo) { + K2 = K1.clone(); + ext.dbl(K2); + } else { + K2 = L.clone(); + ext.inv(K2); + } + + this._K1 = K1; + this._K2 = K2; + + this._const_Bsize = 16; + + this.reset(); + } + + reset() { + this._x = ext.const_Zero.clone(); + this._counter = 0; + this._buffer = new WordArray(); + } + + update(messageUpdate) { + if (!messageUpdate) { + return this; + } + + // Shortcuts + let buffer = this._buffer; + let bsize = this._const_Bsize; + + if (isString(messageUpdate)) { + messageUpdate = C.enc.Utf8.parse(messageUpdate); + } + + buffer.concat(messageUpdate); + + while (buffer.sigBytes > bsize) { + let M_i = ext.shiftBytes(buffer, bsize); + ext.xor(this._x, M_i); + this._x.clamp(); + this._aes.reset(); + this._x = this._aes.finalize(this._x); + this._counter++; + } + + // Chainable + return this; + } + + finalize(messageUpdate) { + this.update(messageUpdate); + + // Shortcuts + let buffer = this._buffer; + let bsize = this._const_Bsize; + + let M_last = buffer.clone(); + if (buffer.sigBytes === bsize) { + ext.xor(M_last, this._K1); + } else { + OneZeroPadding.pad(M_last, bsize / 4); + ext.xor(M_last, this._K2); + } + + ext.xor(M_last, this._x); + + this.reset(); // Can be used immediately afterwards + + this._aes.reset(); + return this._aes.finalize(M_last); + } + }; + + /** + * Directly invokes the CMAC and returns the calculated MAC. + * + * @param {WordArray} key The key to be used for CMAC + * @param {WordArray|string} message The data to be MAC'ed (either WordArray or UTF-8 encoded string) + * + * @returns {WordArray} MAC + */ + C.CMAC = function (key, message) { + return new CMAC(key).finalize(message); + }; + + C.algo.OMAC1 = CMAC; + C.algo.OMAC2 = new CMAC({ + _isTwo: true + }); + } + + createExt(C); + createExtBit(C); + createCMAC(C); +} + +describe('cipher-core-test', () => { + extendWithCMAC(C); + test('testCMAC', () => { + expect(C.CMAC('69c4e0d86a7b0430d8cdb78070b4c55a', 'Test message').toString()).toBe('35e1872b95ce5d99bb5dbbbbd79b9b9b'); + }); +}); \ No newline at end of file diff --git a/test/des.profile.test.js b/test/des.profile.test.js new file mode 100644 index 0000000..a1293a5 --- /dev/null +++ b/test/des.profile.test.js @@ -0,0 +1,29 @@ +import C from '../src/index'; + +let data = {}; +beforeAll(() => { + data.key = C.enc.Hex.parse('0001020304050607'); + data.iv = C.enc.Hex.parse('08090a0b0c0d0e0f'); +}); + +describe('algo-des-profile', () => { + test('profileSinglePartMessage', () => { + let singlePartMessage = ''; + for (let i = 0; i < 100; i++) { + singlePartMessage += '12345678901234567890123456789012345678901234567890'; + } + C.algo.DES.createEncryptor(data.key, { + iv: data.iv + }).finalize(singlePartMessage) + ''; + }); + + test('profileMultiPartMessage', () => { + let des = C.algo.DES.createEncryptor(data.key, { + iv: data.iv + }); + for (let i = 0; i < 100; i++) { + des.process('12345678901234567890123456789012345678901234567890') + ''; + } + des.finalize() + ''; + }); +}); \ No newline at end of file diff --git a/test/des.test.js b/test/des.test.js new file mode 100644 index 0000000..b9db9be --- /dev/null +++ b/test/des.test.js @@ -0,0 +1,114 @@ +import C from '../src/index'; + +const ENCRYPT_TEST_CONFIG = [ + [1, '0000000000000000', '8000000000000000', '95a8d72813daa94d'], + [2, '0000000000000000', '0000000000002000', '1de5279dae3bed6f'], + [3, '0000000000002000', '0000000000000000', '1d1ca853ae7c0c5f'], + [4, '3232323232323232', '3232323232323232', 'ac978c247863388f'], + [5, '6464646464646464', '6464646464646464', '3af1703d76442789'], + [6, '9696969696969696', '9696969696969696', 'a020003c5554f34c'] +]; + +const DECRYPT_TEST_CONFIG = [ + [1, '95a8d72813daa94d', '8000000000000000', '0000000000000000'], + [2, '1de5279dae3bed6f', '0000000000002000', '0000000000000000'], + [3, '1d1ca853ae7c0c5f', '0000000000000000', '0000000000002000'], + [4, 'ac978c247863388f', '3232323232323232', '3232323232323232'], + [5, '3af1703d76442789', '6464646464646464', '6464646464646464'], + [6, 'a020003c5554f34c', '9696969696969696', '9696969696969696'] +]; + +describe('algo-des-test', () => { + test.each(ENCRYPT_TEST_CONFIG)( + 'testEncrypt%i', + (a, b, c, expected) => { + expect(C.DES.encrypt(C.enc.Hex.parse(b), C.enc.Hex.parse(c), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).ciphertext.toString()).toBe(expected); + } + ); + + test.each(DECRYPT_TEST_CONFIG)( + 'testDercrypt%i', + (a, b, c, expected) => { + expect(C.DES.decrypt(new C.lib.CipherParams({ + ciphertext: C.enc.Hex.parse(b) + }), C.enc.Hex.parse(c), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()).toBe(expected); + } + ); + + test('testMultiPart', () => { + let des = C.algo.DES.createEncryptor(C.enc.Hex.parse('0123456789abcdef'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }); + let ciphertext1 = des.process(C.enc.Hex.parse('001122334455')); + let ciphertext2 = des.process(C.enc.Hex.parse('66778899aa')); + let ciphertext3 = des.process(C.enc.Hex.parse('bbccddeeff')); + let ciphertext4 = des.finalize(); + expect(ciphertext1.concat(ciphertext2).concat(ciphertext3).concat(ciphertext4).toString()).toBe(C.DES.encrypt(C.enc.Hex.parse('00112233445566778899aabbccddeeff'), C.enc.Hex.parse('0123456789abcdef'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).ciphertext.toString()); + }); + + test('testInputIntegrity', () => { + let message = C.enc.Hex.parse('00112233445566778899aabbccddeeff'); + let key = C.enc.Hex.parse('0001020304050607'); + let iv = C.enc.Hex.parse('08090a0b0c0d0e0f'); + + let expectedMessage = message.toString(); + let expectedKey = key.toString(); + let expectedIv = iv.toString(); + + C.DES.encrypt(message, key, { + iv: iv + }); + + expect(message.toString()).toBe(expectedMessage); + expect(key.toString()).toBe(expectedKey); + expect(iv.toString()).toBe(expectedIv); + }); + + test('testHelper', () => { + // Save original random method + let random = C.lib.WordArray.random; + + // Replace random method with one that returns a predictable value + C.lib.WordArray.random = function (nBytes) { + let words = []; + for (let i = 0; i < nBytes; i += 4) { + words.push([0x11223344]); + } + + return new C.lib.WordArray(words, nBytes); + }; + expect(C.DES.encrypt('Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).ciphertext.toString()).toBe(C.algo.DES.createEncryptor(C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).finalize('Hi There').toString()); + expect(C.DES.encrypt('Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()).toBe(C.lib.SerializableCipher.encrypt(C.algo.DES, 'Hi There', C.SHA256('Jefe'), { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()); + expect(C.DES.encrypt('Hi There', 'Jefe', { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()).toBe(C.lib.PasswordBasedCipher.encrypt(C.algo.DES, 'Hi There', 'Jefe', { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).toString()); + // Restore random method + C.lib.WordArray.random = random; + }); +}); \ No newline at end of file diff --git a/test/enc.base64.test.js b/test/enc.base64.test.js new file mode 100644 index 0000000..17a9362 --- /dev/null +++ b/test/enc.base64.test.js @@ -0,0 +1,45 @@ +import C from '../src/index'; + +const STRINGFY_TEST_CONFIG = [ + [0, 0, ''], + [1, 1, 'Zg=='], + [2, 2, 'Zm8='], + [3, 3, 'Zm9v'], + [4, 4, 'Zm9vYg=='], + [5, 5, 'Zm9vYmE='], + [6, 6, 'Zm9vYmFy'] +]; + +const PARSE_TEST_CONFIG = [ + [0, '', 0], + [1, 'Zg==', 1], + [2, 'Zm8=', 2], + [3, 'Zm9v', 3], + [4, 'Zm9vYg==', 4], + [5, 'Zm9vYmE=', 5], + [6, 'Zm9vYmFy', 6] +]; + +describe('enc-base64-test', () => { + test.each(STRINGFY_TEST_CONFIG)( + 'testStringfy%i', + (a, b, expected) => { + expect(C.enc.Base64.stringify(new C.lib.WordArray([0x666f6f62, 0x61720000], b))).toBe(expected); + } + ); + + test('testStringify15', () => { + expect(C.enc.Base64.stringify(new C.lib.WordArray([0x3e3e3e3f, 0x3f3f3e3e, 0x3e3f3f3f, 0x3d2f2b00], 15))).toBe('Pj4+Pz8/Pj4+Pz8/PS8r'); + }); + + test.each(PARSE_TEST_CONFIG)( + 'testParse%i', + (a, b, expected) => { + expect(C.enc.Base64.parse(b).toString()).toBe(new C.lib.WordArray([0x666f6f62, 0x61720000], expected).toString()); + } + ); + + test('testParse15', () => { + expect(C.enc.Base64.parse('Pj4+Pz8/Pj4+Pz8/PS8r').toString()).toBe(new C.lib.WordArray([0x3e3e3e3f, 0x3f3f3e3e, 0x3e3f3f3f, 0x3d2f2b00], 15).toString()); + }); +}); \ No newline at end of file diff --git a/test/enc.hex.test.js b/test/enc.hex.test.js new file mode 100644 index 0000000..206adcf --- /dev/null +++ b/test/enc.hex.test.js @@ -0,0 +1,11 @@ +import C from '../src/index'; + +describe('enc-hex-test', () => { + test('testStringify', () => { + expect(C.enc.Hex.stringify(new C.lib.WordArray([0x12345678]))).toBe('12345678'); + }); + + test('testParse', () => { + expect(C.enc.Hex.parse('12345678').toString()).toBe(new C.lib.WordArray([0x12345678]).toString()); + }); +}); \ No newline at end of file diff --git a/test/enc.latain1.test.js b/test/enc.latain1.test.js new file mode 100644 index 0000000..7ff5293 --- /dev/null +++ b/test/enc.latain1.test.js @@ -0,0 +1,11 @@ +import C from '../src/index'; + +describe('enc-latin1-test', () => { + test('testStringify', () => { + expect( C.enc.Latin1.stringify(new C.lib.WordArray([0x12345678]))).toBe('\x12\x34\x56\x78'); + }); + + test('testParse', () => { + expect(C.enc.Latin1.parse('\x12\x34\x56\x78').toString()).toBe(new C.lib.WordArray([0x12345678]).toString()); + }); +}); \ No newline at end of file diff --git a/test/enc.utf16.test.js b/test/enc.utf16.test.js new file mode 100644 index 0000000..9c074d5 --- /dev/null +++ b/test/enc.utf16.test.js @@ -0,0 +1,41 @@ +import C from '../src/index'; + +const STRINGFY_TEST_CONFIG = [ + [1, 0x007a0000, 2, 'z'], + [2, 0x6c340000, 2, '水'], + [3, 0xd800dc00, 4, '𐀀'], + [4, 0xd834dd1e, 4, '𝄞'], + [5, 0xdbffdffd, 4, '􏿽'] +]; + +const PARSE_TEST_CONFIG = [ + [1, 'z', 0x007a0000, 2], + [2, '水', 0x6c340000, 2], + [3, '𐀀', 0xd800dc00, 4], + [4, '𝄞', 0xd834dd1e, 4], + [5, '􏿽', 0xdbffdffd, 4] +]; + +describe('enc-utf16-test', () => { + test.each(STRINGFY_TEST_CONFIG)( + 'testString%i', + (a, b, c, expected) => { + expect(C.enc.Utf16.stringify(new C.lib.WordArray([b], c))).toBe(expected); + } + ); + + test('testStringifyLE', () => { + expect(C.enc.Utf16LE.stringify(new C.lib.WordArray([0xffdbfddf], 4))).toBe('􏿽'); + }); + + test.each(PARSE_TEST_CONFIG)( + 'testParse%i', + (a, expected, b, c) => { + expect(C.enc.Utf16.parse(expected).toString()).toBe(new C.lib.WordArray([b], c).toString()); + } + ); + + test('testParseLE', () => { + expect(C.enc.Utf16LE.parse('􏿽').toString()).toBe(new C.lib.WordArray([0xffdbfddf], 4).toString()); + }); +}); diff --git a/test/enc.utf8.test.js b/test/enc.utf8.test.js new file mode 100644 index 0000000..f9d3417 --- /dev/null +++ b/test/enc.utf8.test.js @@ -0,0 +1,31 @@ +import C from '../src/index'; + +const STRINGFY_TEST_CONFIG = [ + [1, 1, 0x24000000, '$'], + [2, 2, 0xc2a20000, '¢'], + [3, 3, 0xe282ac00, '€'], + [4, 4, 0xf0a4ada2, '𤭢'] +]; + +const PARSE_TEST_CONFIG = [ + [1, '$', 0x24000000, 1], + [2, '¢', 0xc2a20000, 2], + [3, '€', 0xe282ac00, 3], + [4, '𤭢', 0xf0a4ada2, 4] +]; + +describe('enc-utf8-tes', () => { + test.each(STRINGFY_TEST_CONFIG)( + 'testStringfy%i', + (a, b, c, expected) => { + expect(C.enc.Utf8.stringify(new C.lib.WordArray([c], b))).toBe(expected); + } + ); + + test.each(PARSE_TEST_CONFIG)( + 'testParse%i', + (a, b, c, expected) => { + expect(C.enc.Utf8.parse(b).toString()).toBe(new C.lib.WordArray([c], expected).toString()); + } + ); +}); \ No newline at end of file diff --git a/test/evpkdf.profile.test.js b/test/evpkdf.profile.test.js new file mode 100644 index 0000000..a27db10 --- /dev/null +++ b/test/evpkdf.profile.test.js @@ -0,0 +1,7 @@ +import C from '../src/index'; + +describe('algo-evpkdf-profile', () => { + test('profileKeySize256Iterations20', () => { + new C.algo.EvpKDF({ keySize: 256/32, iterations: 20 }).compute('password', 'ATHENA.MIT.EDUraeburn'); + }); +}); \ No newline at end of file diff --git a/test/evpkdf.test.js b/test/evpkdf.test.js new file mode 100644 index 0000000..028f388 --- /dev/null +++ b/test/evpkdf.test.js @@ -0,0 +1,27 @@ +import C from '../src/index'; + +describe('algo-evpkdf-test', () => { + test('testVector', () => { + expect(C.EvpKDF('password', 'saltsalt', { keySize: (256+128)/32 }).toString()).toBe('fdbdf3419fff98bdb0241390f62a9db35f4aba29d77566377997314ebfc709f20b5ca7b1081f94b1ac12e3c8ba87d05a'); + }); + + // There are no official test vectors that I could find, and the EVP implementation is short on comments. + // Need to use the C code to generate more test vectors. + // The iteration count in particular needs to be tested. + test('testInputIntegrity', () => { + let password = new C.lib.WordArray([0x12345678]); + let salt = new C.lib.WordArray([0x12345678]); + + let expectedPassword = password.toString(); + let expectedSalt = salt.toString(); + + C.EvpKDF(password, salt); + + expect(password.toString()).toBe(expectedPassword); + expect(salt.toString()).toBe(expectedSalt); + }); + + test('testHelper', () => { + expect(C.EvpKDF('password', 'saltsalt', { keySize: (256+128)/32 }).toString()).toBe(new C.algo.EvpKDF({ keySize: (256+128)/32 }).compute('password', 'saltsalt').toString()); + }); +}); \ No newline at end of file diff --git a/test/format.openssl.test.js b/test/format.openssl.test.js new file mode 100644 index 0000000..fe5be04 --- /dev/null +++ b/test/format.openssl.test.js @@ -0,0 +1,40 @@ +import C from '../src/index'; + +const data = {}; +beforeAll(() => { + data.ciphertext = new C.lib.WordArray([0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f]); + data.salt = new C.lib.WordArray([0x01234567, 0x89abcdef]); +}); + +describe('format-openssl-test', () => { + test('testSaltedToString', () => { + expect(C.format.OpenSSL.stringify(new C.lib.CipherParams({ + ciphertext: data.ciphertext, + salt: data.salt + }))).toBe(C.enc.Latin1.parse('Salted__').concat(data.salt).concat(data.ciphertext).toString(C.enc.Base64)); + }); + + test('testUnsaltedToString', () => { + expect(C.format.OpenSSL.stringify(new C.lib.CipherParams({ + ciphertext: data.ciphertext + }))).toBe(data.ciphertext.toString(C.enc.Base64)); + }); + + test('testSaltedFromString', () => { + let openSSLStr = C.format.OpenSSL.stringify(new C.lib.CipherParams({ + ciphertext: data.ciphertext, + salt: data.salt + })); + let cipherParams = C.format.OpenSSL.parse(openSSLStr); + expect(cipherParams.ciphertext.toString()).toBe(data.ciphertext.toString()); + expect(cipherParams.salt.toString()).toBe(data.salt.toString()); + }); + + test('testUnsaltedFromString', () => { + let openSSLStr = C.format.OpenSSL.stringify(new C.lib.CipherParams({ + ciphertext: data.ciphertext + })); + let cipherParams = C.format.OpenSSL.parse(openSSLStr); + expect(cipherParams.ciphertext.toString()).toBe(data.ciphertext.toString()); + }); +}); \ No newline at end of file diff --git a/test/hmac.md5.profile.test.js b/test/hmac.md5.profile.test.js new file mode 100644 index 0000000..97054e6 --- /dev/null +++ b/test/hmac.md5.profile.test.js @@ -0,0 +1,24 @@ +import C from '../src/index'; + +let data = {}; +beforeAll(() => { + data.key = C.lib.WordArray.random(128/8); +}); + +describe('algo-hmac-md5-profile', () => { + test('profileSinglePartMessage', () => { + let singlePartMessage = ''; + for (let i = 0; i < 500; i++) { + singlePartMessage += '12345678901234567890123456789012345678901234567890'; + } + new C.algo.HMAC(C.algo.MD5, data.key).finalize(singlePartMessage) + ''; + }); + + test('profileMultiPartMessage', () => { + let hmac = new C.algo.HMAC(C.algo.MD5, data.key); + for (let i = 0; i < 500; i++) { + hmac.update('12345678901234567890123456789012345678901234567890'); + } + hmac.finalize() + ''; + }); +}); \ No newline at end of file diff --git a/test/hmac.md5.test.js b/test/hmac.md5.test.js new file mode 100644 index 0000000..7bf7124 --- /dev/null +++ b/test/hmac.md5.test.js @@ -0,0 +1,53 @@ +import C from '../src/index'; + +describe('algo-hmac-md5-test', () => { + test('testVector1', () => { + expect(C.HmacMD5('Hi There', C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).toString()).toBe('9294727a3638bb1c13f48ef8158bfc9d'); + }); + + test('testVector2', () => { + expect(C.HmacMD5('what do ya want for nothing?', 'Jefe').toString()).toBe('750c783e6ab0b503eaa86e310a5db738'); + }); + + test('testVector3', () => { + expect(C.HmacMD5(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'), C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')).toString()).toBe('56be34521d144c88dbb8c733f0e8b3f6'); + }); + + test('testVector4', () => { + expect(C.HmacMD5('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A').toString()).toBe('7ee2a3cc979ab19865704644ce13355c'); + }); + + test('testVector5', () => { + expect(C.HmacMD5('abcdefghijklmnopqrstuvwxyz', 'A').toString()).toBe('0e1bd89c43e3e6e3b3f8cf1d5ba4f77a'); + }); + + test('testUpdate', () => { + let hmac = new C.algo.HMAC(C.algo.MD5, C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddd')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddd')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddd')); + expect(hmac.finalize().toString()).toBe(C.HmacMD5(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'), C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')).toString()); + }); + + test('testInputIntegrity', () => { + let message = new C.lib.WordArray([0x12345678]); + let key = new C.lib.WordArray([0x12345678]); + + let expectedMessage = message.toString(); + let expectedKey = key.toString(); + + C.HmacMD5(message, key); + expect(message.toString()).toBe(expectedMessage); + expect(key.toString()).toBe(expectedKey); + }); + + test('testRespectKeySigBytes', () => { + let key = C.lib.WordArray.random(8); + key.sigBytes = 4; + + let keyClamped = key.clone(); + keyClamped.clamp(); + + expect(C.HmacMD5('Message', key).toString()).toBe(C.HmacMD5('Message', keyClamped).toString()); + }); +}); \ No newline at end of file diff --git a/test/hmac.sha224.test.js b/test/hmac.sha224.test.js new file mode 100644 index 0000000..98cebd5 --- /dev/null +++ b/test/hmac.sha224.test.js @@ -0,0 +1,54 @@ +import C from '../src/index'; + +describe('algo-hmac-sha224-test', () => { + test('testVector1', () => { + expect(C.HmacSHA224('Hi There', C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).toString()).toBe('4e841ce7a4ae83fbcf71e3cd64bfbf277f73a14680aae8c518ac7861'); + }); + + test('testVector2', () => { + expect(C.HmacSHA224('what do ya want for nothing?', 'Jefe').toString()).toBe('a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44'); + }); + + test('testVector3', () => { + expect(C.HmacSHA224(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'), C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')).toString()).toBe('cbff7c2716bbaa7c77bed4f491d3e8456cb6c574e92f672b291acf5b'); + }); + + test('testVector4', () => { + expect(C.HmacSHA224('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A').toString()).toBe('61bf669da4fdcd8e5c3bd09ebbb4a986d3d1b298d3ca05c511f7aeff'); + }); + + test('testVector5', () => { + expect(C.HmacSHA224('abcdefghijklmnopqrstuvwxyz', 'A').toString()).toBe('16fc69ada3c3edc1fe9144d6b98d93393833ae442bedf681110a1176'); + }); + + test('testUpdate', () => { + let hmac = new C.algo.HMAC(C.algo.SHA224, C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddd')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddd')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddd')); + expect(hmac.finalize().toString()).toBe(C.HmacSHA224(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'), C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')).toString()); + }); + + test('testInputIntegrity', () => { + let message = new C.lib.WordArray([0x12345678]); + let key = new C.lib.WordArray([0x12345678]); + + let expectedMessage = message.toString(); + let expectedKey = key.toString(); + + C.HmacSHA224(message, key); + + expect(message.toString()).toBe(expectedMessage); + expect(key.toString()).toBe(expectedKey); + }); + + test('testRespectKeySigBytes', () => { + let key = C.lib.WordArray.random(8); + key.sigBytes = 4; + + let keyClamped = key.clone(); + keyClamped.clamp(); + + expect(C.HmacSHA224('Message', key).toString()).toBe(C.HmacSHA224('Message', keyClamped).toString()); + }); +}); \ No newline at end of file diff --git a/test/hmac.sha256.test.js b/test/hmac.sha256.test.js new file mode 100644 index 0000000..bf15e4e --- /dev/null +++ b/test/hmac.sha256.test.js @@ -0,0 +1,53 @@ +import C from '../src/index'; + +describe('algo-hmac-sha256-test', () => { + test('testVector1', () => { + expect(C.HmacSHA256('Hi There', C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).toString()).toBe('492ce020fe2534a5789dc3848806c78f4f6711397f08e7e7a12ca5a4483c8aa6'); + }); + + test('testVector2', () => { + expect(C.HmacSHA256('what do ya want for nothing?', 'Jefe').toString()).toBe('5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843'); + }); + + test('testVector3', () => { + expect(C.HmacSHA256(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'), C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')).toString()).toBe('7dda3cc169743a6484649f94f0eda0f9f2ff496a9733fb796ed5adb40a44c3c1'); + }); + + test('testVector4', () => { + expect(C.HmacSHA256('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A').toString()).toBe('a89dc8178c1184a62df87adaa77bf86e93064863d93c5131140b0ae98b866687'); + }); + + test('testVector5', () => { + expect(C.HmacSHA256('abcdefghijklmnopqrstuvwxyz', 'A').toString()).toBe('d8cb78419c02fe20b90f8b77427dd9f81817a751d74c2e484e0ac5fc4e6ca986'); + }); + + test('testUpdate', () => { + let hmac = new C.algo.HMAC(C.algo.SHA256, C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddd')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddd')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddd')); + + expect(hmac.finalize().toString()).toBe(C.HmacSHA256(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'), C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')).toString()); + }); + + test('testInputIntegrity', () => { + let message = new C.lib.WordArray([0x12345678]); + let key = new C.lib.WordArray([0x12345678]); + + let expectedMessage = message.toString(); + let expectedKey = key.toString(); + + C.HmacSHA256(message, key); + + expect(message.toString()).toBe(expectedMessage); + expect(key.toString()).toBe(expectedKey); + }); + + test('testRespectKeySigBytes', () => { + let key = C.lib.WordArray.random(8); + key.sigBytes = 4; + let keyClamped = key.clone(); + keyClamped.clamp(); + expect(C.HmacSHA256('Message', key).toString()).toBe(C.HmacSHA256('Message', keyClamped).toString()); + }); +}); \ No newline at end of file diff --git a/test/hmac.sha384.test.js b/test/hmac.sha384.test.js new file mode 100644 index 0000000..bc16a9c --- /dev/null +++ b/test/hmac.sha384.test.js @@ -0,0 +1,48 @@ +import C from '../src/index'; + +describe('algo-hmac-sha384-test', () => { + test('testVector1', () => { + expect(C.HmacSHA384('Hi There', C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).toString()).toBe('7afaa633e20d379b02395915fbc385ff8dc27dcd3885e1068ab942eeab52ec1f20ad382a92370d8b2e0ac8b83c4d53bf'); + }); + test('testVector2', () => { + expect(C.HmacSHA384('what do ya want for nothing?', 'Jefe').toString()).toBe('af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649'); + }); + test('testVector3', () => { + expect(C.HmacSHA384(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'), C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')).toString()).toBe('1383e82e28286b91f4cc7afbd13d5b5c6f887c05e7c4542484043a37a5fe45802a9470fb663bd7b6570fe2f503fc92f5'); + }); + test('testVector4', () => { + expect(C.HmacSHA384('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A').toString()).toBe('365dfb271adb8e30fe6c74220b75df1b38c2d19b9d37f2e5a0ec2f3f22bd0406bf5b786e98d81b82c36d3d8a1be6cd07'); + }); + test('testVector5', () => { + expect(C.HmacSHA384('abcdefghijklmnopqrstuvwxyz', 'A').toString()).toBe('a8357d5e84da64140e41545562ae0782e2a58e39c6cd98939fad8d9080e774c84b7eaca4ba07f6dbf0f12eab912c5285'); + }); + + test('testUpdate', () => { + let hmac = new C.algo.HMAC(C.algo.SHA384, C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddd')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddd')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddd')); + expect(hmac.finalize().toString()).toBe(C.HmacSHA384(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'), C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')).toString()); + }); + + test('testInputIntegrity', () => { + let message = new C.lib.WordArray([0x12345678]); + let key = new C.lib.WordArray([0x12345678]); + + let expectedMessage = message.toString(); + let expectedKey = key.toString(); + + C.HmacSHA384(message, key); + expect(message.toString()).toBe(expectedMessage); + expect(key.toString()).toBe(expectedKey); + }); + + test('testRespectKeySigBytes', () => { + let key = C.lib.WordArray.random(8); + key.sigBytes = 4; + + let keyClamped = key.clone(); + keyClamped.clamp(); + expect(C.HmacSHA384('Message', key).toString()).toBe(C.HmacSHA384('Message', keyClamped).toString()); + }); +}); \ No newline at end of file diff --git a/test/hmac.sha512.test.js b/test/hmac.sha512.test.js new file mode 100644 index 0000000..f3a9d68 --- /dev/null +++ b/test/hmac.sha512.test.js @@ -0,0 +1,48 @@ +import C from '../src/index'; + +describe('algo-hmac-sha512-test', () => { + test('testVector1', () => { + expect(C.HmacSHA512('Hi There', C.enc.Hex.parse('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b')).toString()).toBe('7641c48a3b4aa8f887c07b3e83f96affb89c978fed8c96fcbbf4ad596eebfe496f9f16da6cd080ba393c6f365ad72b50d15c71bfb1d6b81f66a911786c6ce932'); + }); + test('testVector2', () => { + expect(C.HmacSHA512('what do ya want for nothing?', 'Jefe').toString()).toBe('164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737'); + }); + test('testVector3', () => { + expect(C.HmacSHA512(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'), C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')).toString()).toBe('ad9b5c7de72693737cd5e9d9f41170d18841fec1201c1c1b02e05cae116718009f771cad9946ddbf7e3cde3e818d9ae85d91b2badae94172d096a44a79c91e86'); + }); + test('testVector4', () => { + expect(C.HmacSHA512('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'A').toString()).toBe('a303979f7c94bb39a8ab6ce05cdbe28f0255da8bb305263e3478ef7e855f0242729bf1d2be55398f14da8e63f0302465a8a3f76c297bd584ad028d18ed7f0195'); + }); + test('testVector5', () => { + expect(C.HmacSHA512('abcdefghijklmnopqrstuvwxyz', 'A').toString()).toBe('8c2d56f7628325e62124c0a870ad98d101327fc42696899a06ce0d7121454022fae597e42c25ac3a4c380fd514f553702a5b0afaa9b5a22050902f024368e9d9'); + }); + + test('testUpdate', () => { + let hmac = new C.algo.HMAC(C.algo.SHA512, C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddd')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddd')); + hmac.update(C.enc.Hex.parse('dddddddddddddddddddddddddddddddd')); + expect(hmac.finalize().toString()).toBe(C.HmacSHA512(C.enc.Hex.parse('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'), C.enc.Hex.parse('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')).toString()); + }); + + test('testInputIntegrity', () => { + let message = new C.lib.WordArray([0x12345678]); + let key = new C.lib.WordArray([0x12345678]); + + let expectedMessage = message.toString(); + let expectedKey = key.toString(); + + C.HmacSHA512(message, key); + expect(message.toString()).toBe(expectedMessage); + expect(key.toString()).toBe(expectedKey); + }); + + test('testRespectKeySigBytes', () => { + let key = C.lib.WordArray.random(8); + key.sigBytes = 4; + + let keyClamped = key.clone(); + keyClamped.clamp(); + expect(C.HmacSHA512('Message', key).toString()).toBe(C.HmacSHA512('Message', keyClamped).toString()); + }) +}); \ No newline at end of file diff --git a/test/kdf.openssl.test.js b/test/kdf.openssl.test.js new file mode 100644 index 0000000..a14fd86 --- /dev/null +++ b/test/kdf.openssl.test.js @@ -0,0 +1,10 @@ +import C from '../src/index'; + +describe('kdf-openssl-test', () => { + test('testVector', () => { + let derivedParams = C.kdf.OpenSSL.execute('password', 256 / 32, 128 / 32, C.enc.Hex.parse('0a9d8620cf7219f1')); + expect(derivedParams.key.toString()).toBe('50f32e0ec9408e02ff42364a52aac95c3694fc027256c6f488bf84b8e60effcd'); + expect(derivedParams.iv.toString()).toBe('81381e39b94fd692dff7e2239a298cb6'); + expect(derivedParams.salt.toString()).toBe('0a9d8620cf7219f1'); + }); +}); \ No newline at end of file diff --git a/test/lib.base.test.js b/test/lib.base.test.js new file mode 100644 index 0000000..3b9ad4d --- /dev/null +++ b/test/lib.base.test.js @@ -0,0 +1,57 @@ +import C from '../src/index'; + +let data = {}; +beforeAll(() => { + data.mixins = { + mixinMethod: function () {} + }; + data.Obj = class Obj extends C.lib.Base { + constructor(arg) { + super(); + this.initFired = true; + this.initArg = arg; + } + toString() { + return 'ObjToString'; + } + }; + data.obj = new data.Obj('argValue'); + data.obj.mixIn(data.mixins); + data.objClone = data.obj.clone(); +}); + +describe('lib-base-test', () => { + test('testClassInheritance', () => { + expect(data.Obj.__proto__).toBe(C.lib.Base); + }); + + test('testObjectInheritance', () => { + expect(data.obj.__proto__.__proto__).toBe(C.lib.Base.prototype); + }); + + test('testToString', () => { + expect(data.obj.toString()).toBe('ObjToString'); + }); + + test('testConstructor', () => { + expect(data.obj.initFired).toBeTruthy(); + expect(data.obj.initArg).toBe('argValue'); + }); + + test('testMixIn', () => { + expect(data.obj.mixinMethod).toBe(data.mixins.mixinMethod); + }); + + test('testCloneDistinct', () => { + expect(data.objClone).not.toBe(data.obj); + }); + + test('testCloneCopy', () => { + expect(data.objClone.initArg).toBe(data.obj.initArg); + }); + + test('testCloneIndependent', () => { + data.obj.initArg = 'newValue'; + expect(data.objClone.initArg).not.toBe(data.obj.initArg); + }); +}); \ No newline at end of file diff --git a/test/lib.cipherparams.test.js b/test/lib.cipherparams.test.js new file mode 100644 index 0000000..18adc76 --- /dev/null +++ b/test/lib.cipherparams.test.js @@ -0,0 +1,52 @@ +import C from '../src/index'; + +const data = {}; +beforeAll(() => { + data.ciphertext = C.enc.Hex.parse('000102030405060708090a0b0c0d0e0f'); + data.key = C.enc.Hex.parse('101112131415161718191a1b1c1d1e1f'); + data.iv = C.enc.Hex.parse('202122232425262728292a2b2c2d2e2f'); + data.salt = C.enc.Hex.parse('0123456789abcdef'); + data.algorithm = C.algo.AES; + data.mode = C.mode.CBC; + data.padding = C.pad.PKCS7; + data.blockSize = data.algorithm.blockSize; + data.formatter = C.format.OpenSSL; + data.cipherParams = new C.lib.CipherParams({ + ciphertext: data.ciphertext, + key: data.key, + iv: data.iv, + salt: data.salt, + algorithm: data.algorithm, + mode: data.mode, + padding: data.padding, + blockSize: data.blockSize, + formatter: data.formatter + }); +}); + +describe('lib-cipherparams-test', () => { + test('testInit', () => { + expect(data.cipherParams.ciphertext).toBe(data.ciphertext); + expect(data.cipherParams.key).toBe(data.key); + expect(data.cipherParams.iv).toBe(data.iv); + expect(data.cipherParams.salt).toBe(data.salt); + expect(data.cipherParams.algorithm).toBe(data.algorithm); + expect(data.cipherParams.mode).toBe(data.mode); + expect(data.cipherParams.padding).toBe(data.padding); + expect(data.cipherParams.blockSize).toBe(data.blockSize); + expect(data.cipherParams.formatter).toBe(data.formatter); + }); + + test('testToString0', () => { + expect(data.cipherParams.toString()).toBe(C.format.OpenSSL.stringify(data.cipherParams)); + }); + + test('testToString1', () => { + const JsonFormatter = { + stringify: function (cipherParams) { + return '{ ct: ' + cipherParams.ciphertext + ', iv: ' + cipherParams.iv + ' }'; + } + }; + expect(data.cipherParams.toString(JsonFormatter)).toBe(JsonFormatter.stringify(data.cipherParams)); + }); +}); \ No newline at end of file diff --git a/test/lib.passwordbasedcipher.test.js b/test/lib.passwordbasedcipher.test.js new file mode 100644 index 0000000..9d54e05 --- /dev/null +++ b/test/lib.passwordbasedcipher.test.js @@ -0,0 +1,21 @@ +import C from '../src/index'; + +describe('lib-passwordbasedcipher-test', () => { + test('testEncrypt', () => { + // Compute actual + let actual = C.lib.PasswordBasedCipher.encrypt(C.algo.AES, 'Hello, World!', 'password'); + + // Compute expected + let aes = C.algo.AES.createEncryptor(actual.key, { + iv: actual.iv + }); + let expected = aes.finalize('Hello, World!'); + expect(actual.ciphertext.toString()).toBe(expected.toString()); + }); + + test('testDecrypt', () => { + let ciphertext = C.lib.PasswordBasedCipher.encrypt(C.algo.AES, 'Hello, World!', 'password'); + let plaintext = C.lib.PasswordBasedCipher.decrypt(C.algo.AES, ciphertext, 'password'); + expect(plaintext.toString(C.enc.Utf8)).toBe('Hello, World!'); + }); +}); \ No newline at end of file diff --git a/test/lib.serializablecipher.test.js b/test/lib.serializablecipher.test.js new file mode 100644 index 0000000..b7b00c8 --- /dev/null +++ b/test/lib.serializablecipher.test.js @@ -0,0 +1,51 @@ +import C from '../src/index'; + +let data = {}; + +beforeAll(() => { + data.message = new C.lib.WordArray([0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f]); + data.key = new C.lib.WordArray([0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f]); + data.iv = new C.lib.WordArray([0x20212223, 0x24252627, 0x28292a2b, 0x2c2d2e2f]); +}); + +describe('lib-serializablecipher-test', () => { + test('testEncrypt', () => { + // Compute expected + let aes = C.algo.AES.createEncryptor(data.key, { + iv: data.iv + }); + let ciphertext = aes.finalize(data.message); + let expected = new C.lib.CipherParams({ + ciphertext: ciphertext, + key: data.key, + iv: data.iv, + algorithm: C.algo.AES, + mode: aes.cfg.mode, + padding: aes.cfg.padding, + blockSize: aes.blockSize, + formatter: C.format.OpenSSL + }); + let actual = C.lib.SerializableCipher.encrypt(C.algo.AES, data.message, data.key, { + iv: data.iv + }); + + expect(actual.toString()).toBe(expected.toString()); + expect(actual.ciphertext.toString()).toBe(expected.ciphertext.toString()); + expect(actual.key.toString()).toBe(expected.key.toString()); + expect(actual.iv.toString()).toBe(expected.iv.toString()); + expect(actual.algorithm).toBe(expected.algorithm); + expect(actual.mode).toBe(expected.mode); + expect(actual.padding).toBe(expected.padding); + expect(actual.blockSize).toBe(expected.blockSize); + }); + + test('testDecrypt', () => { + let encrypted = C.lib.SerializableCipher.encrypt(C.algo.AES, data.message, data.key, { + iv: data.iv + }) + ''; + let decrypted = C.lib.SerializableCipher.decrypt(C.algo.AES, encrypted, data.key, { + iv: data.iv + }); + expect(decrypted.toString()).toBe(data.message.toString()); + }); +}); \ No newline at end of file diff --git a/test/lib.typedarrays.test.js b/test/lib.typedarrays.test.js new file mode 100644 index 0000000..cfb4901 --- /dev/null +++ b/test/lib.typedarrays.test.js @@ -0,0 +1,50 @@ +import C from '../src/index'; + +const data = {}; +beforeAll(() => { + data.buffer = new ArrayBuffer(8); + + let uint8View = new Uint8Array(data.buffer); + uint8View[0] = 0x01; + uint8View[1] = 0x23; + uint8View[2] = 0x45; + uint8View[3] = 0x67; + uint8View[4] = 0x89; + uint8View[5] = 0xab; + uint8View[6] = 0xcd; + uint8View[7] = 0xef; +}); + +describe('lib-wordarray-test', () => { + test('testInt8Array', () => { + expect(new C.lib.WordArray(new Int8Array(data.buffer)).toString()).toBe('0123456789abcdef'); + }); + + test('testUint8Array', () => { + expect(new C.lib.WordArray(new Uint8Array(data.buffer)).toString()).toBe('0123456789abcdef'); + }); + + test('testUint8ClampedArray', () => { + expect(new C.lib.WordArray(new Uint8ClampedArray(data.buffer)).toString()).toBe('0123456789abcdef'); + }); + + test('testInt16Array', () => { + expect(new C.lib.WordArray(new Int16Array(data.buffer)).toString()).toBe('0123456789abcdef'); + }); + + test('testUint16Array', () => { + expect(new C.lib.WordArray(new Uint16Array(data.buffer)).toString()).toBe('0123456789abcdef'); + }); + + test('testInt32Array', () => { + expect(new C.lib.WordArray(new Int32Array(data.buffer)).toString()).toBe('0123456789abcdef'); + }); + + test('testUint32Array', () => { + expect(new C.lib.WordArray(new Uint32Array(data.buffer)).toString()).toBe('0123456789abcdef'); + }); + + test('testPartialView', () => { + expect(new C.lib.WordArray(new Int16Array(data.buffer, 2, 2)).toString()).toBe('456789ab'); + }); +}); \ No newline at end of file diff --git a/test/lib.wordarray.test.js b/test/lib.wordarray.test.js new file mode 100644 index 0000000..a3a02e5 --- /dev/null +++ b/test/lib.wordarray.test.js @@ -0,0 +1,76 @@ +import C from '../src/index'; + +describe('lib-wordarray-test', () => { + test('testInit0', () => { + expect(new C.lib.WordArray().toString()).toBe(''); + }); + + test('testInit1', () => { + expect(new C.lib.WordArray([0x12345678]).toString()).toBe('12345678'); + }); + + test('testInit2', () => { + expect(new C.lib.WordArray([0x12345678], 2).toString()).toBe('1234'); + }); + + test('testToStringPassedEncoder', () => { + expect(new C.lib.WordArray([0x12345678]).toString(C.enc.Latin1)).toBe('\x12\x34\x56\x78'); + }); + + test('testToStringDefaultEncoder', () => { + expect(new C.lib.WordArray([0x12345678]).toString()).toBe('12345678'); + }); + + test('testConcat3', () => { + let wordArray1 = new C.lib.WordArray([0x12345678], 3); + let wordArray2 = new C.lib.WordArray([0x12345678], 3); + expect(wordArray1.concat(wordArray2).toString()).toBe('123456123456'); + expect(wordArray1.toString()).toBe('123456123456'); + }); + + test('testConcat4', () => { + let wordArray1 = new C.lib.WordArray([0x12345678], 4); + let wordArray2 = new C.lib.WordArray([0x12345678], 3); + expect(wordArray1.concat(wordArray2).toString()).toBe('12345678123456'); + expect(wordArray1.toString()).toBe('12345678123456'); + }); + + test('testConcat5', () => { + let wordArray1 = new C.lib.WordArray([0x12345678], 5); + let wordArray2 = new C.lib.WordArray([0x12345678], 3); + expect(wordArray1.concat(wordArray2).toString()).toBe('1234567800123456'); + expect(wordArray1.toString()).toBe('1234567800123456'); + }); + + test('testConcatLong', () => { + let wordArray1 = new C.lib.WordArray(); + let wordArray2 = new C.lib.WordArray(); + let wordArray3 = new C.lib.WordArray(); + for (let i = 0; i < 500000; i++) { + wordArray2.words[i] = i; + wordArray3.words[i] = i; + } + wordArray2.sigBytes = 500000; + wordArray3.sigBytes = 500000; + const expected = wordArray2.toString() + wordArray3.toString(); + expect(wordArray1.concat(wordArray2.concat(wordArray3)).toString()).toBe(expected); + }); + + test('testClamp', () => { + let wordArray = new C.lib.WordArray([0x12345678, 0x12345678], 3); + wordArray.clamp(); + expect(wordArray.words.toString()).toBe([0x12345600].toString()); + }); + + test('testClone', () => { + let wordArray = new C.lib.WordArray([0x12345678]); + let clone = wordArray.clone(); + clone.words[0] = 0; + expect(clone.toString()).not.toBe(wordArray.toString()); + }); + + test('testRandom', () => { + expect(C.lib.WordArray.random(8).toString()).not.toBe(C.lib.WordArray.random(8).toString()); + expect(C.lib.WordArray.random(8).sigBytes).toBe(8); + }); +}); \ No newline at end of file diff --git a/test/md5.profile.test.js b/test/md5.profile.test.js index a198fbf..9ca91d1 100644 --- a/test/md5.profile.test.js +++ b/test/md5.profile.test.js @@ -17,7 +17,7 @@ describe('algo-md5-profile', () => { test('profileMultiPartMessage', () => { let i = 0; - const md5 =new C.algo.MD5(); + const md5 = new C.algo.MD5(); while (i < 500) { md5.update('12345678901234567890123456789012345678901234567890'); i++; diff --git a/test/md5.test.js b/test/md5.test.js index fbb219b..51dd682 100644 --- a/test/md5.test.js +++ b/test/md5.test.js @@ -1,4 +1,4 @@ -import C from '../src/index.js'; +import C from '../src/index'; const VECTOR_TEST_CONFIG = [ [1, '', 'd41d8cd98f00b204e9800998ecf8427e'], @@ -17,17 +17,15 @@ const CLONE_TEST_CONFIG = [ ]; describe('algo-md5-test', () => { - describe.each(VECTOR_TEST_CONFIG)( + test.each(VECTOR_TEST_CONFIG)( 'testVector%i', - (a, b, expected) => { - test(`return ${expected}`, () => { - expect(C.MD5(b).toString()).toBe(expected); - }); + (a,b, expected) => { + expect(C.MD5(b).toString()).toBe(expected); } ); describe('testClone', () => { - const md5 =new C.algo.MD5(); + const md5 = new C.algo.MD5(); test.each(CLONE_TEST_CONFIG)( 'return %s, %s', (a, expected) => { @@ -38,7 +36,7 @@ describe('algo-md5-test', () => { test('testUpdateAndLongMessage', () => { let i = 0; - const md5 =new C.algo.MD5(); + const md5 = new C.algo.MD5(); while (i < 100) { md5.update('12345678901234567890123456789012345678901234567890'); i++; @@ -47,7 +45,7 @@ describe('algo-md5-test', () => { }); test('testInputIntegrity', () => { - const message =new C.lib.WordArray([0x12345678]); + const message = new C.lib.WordArray([0x12345678]); const expected = message.toString(); C.MD5(message); expect(message.toString()).toBe(expected); diff --git a/test/mode.cbc.test.js b/test/mode.cbc.test.js new file mode 100644 index 0000000..899f57a --- /dev/null +++ b/test/mode.cbc.test.js @@ -0,0 +1,53 @@ +import C from '../src/index'; + +let data = {}; +beforeAll(() => { + data.message = new C.lib.WordArray([ + 0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, + 0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f + ]); + data.key = new C.lib.WordArray([0x20212223, 0x24252627, 0x28292a2b, 0x2c2d2e2f]); + data.iv = new C.lib.WordArray([0x30313233, 0x34353637, 0x38393a3b, 0x3c3d3e3f]); +}); + +describe('mode-cbc-test', () => { + test('testEncryptor', () => { + // Compute expected + let expected = data.message.clone(); + let aes = C.algo.AES.createEncryptor(data.key); + + // First block XORed with IV, then encrypted + for (let i = 0; i < 4; i++) { + expected.words[i] ^= data.iv.words[i]; + } + aes.encryptBlock(expected.words, 0); + + // Subsequent blocks XORed with previous crypted block, then encrypted + for (let i = 4; i < 8; i++) { + expected.words[i] ^= expected.words[i - 4]; + } + aes.encryptBlock(expected.words, 4); + + // Compute actual + let actual = C.AES.encrypt(data.message, data.key, { + iv: data.iv, + mode: C.mode.CBC, + padding: C.pad.NoPadding + }).ciphertext; + expect(actual.toString()).toBe(expected.toString()); + }); + + test('testDecryptor', () => { + let encrypted = C.AES.encrypt(data.message, data.key, { + iv: data.iv, + mode: C.mode.CBC, + padding: C.pad.NoPadding + }); + let decrypted = C.AES.decrypt(encrypted, data.key, { + iv: data.iv, + mode: C.mode.CBC, + padding: C.pad.NoPadding + }); + expect(decrypted.toString()).toBe(data.message.toString()); + }); +}); \ No newline at end of file diff --git a/test/mode.cfb.test.js b/test/mode.cfb.test.js new file mode 100644 index 0000000..f1a5c95 --- /dev/null +++ b/test/mode.cfb.test.js @@ -0,0 +1,56 @@ +import C from '../src/index'; + +const data = {}; +beforeAll(() => { + data.message = new C.lib.WordArray([ + 0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, + 0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f + ]); + data.key = new C.lib.WordArray([0x20212223, 0x24252627, 0x28292a2b, 0x2c2d2e2f]); + data.iv = new C.lib.WordArray([0x30313233, 0x34353637, 0x38393a3b, 0x3c3d3e3f]); +}); + +describe('mode-cfb-test', () => { + test('testEncryptor', () => { + // Compute expected + let expected = data.message.clone(); + let aes = C.algo.AES.createEncryptor(data.key); + + // First block XORed with encrypted IV + let keystream = data.iv.words.slice(0); + aes.encryptBlock(keystream, 0); + for (let i = 0; i < 4; i++) { + expected.words[i] ^= keystream[i]; + } + + // Subsequent blocks XORed with encrypted previous crypted block + keystream = expected.words.slice(0, 4); + aes.encryptBlock(keystream, 0); + for (let i = 4; i < 8; i++) { + expected.words[i] ^= keystream[i % 4]; + } + + // Compute actual + let actual = C.AES.encrypt(data.message, data.key, { + iv: data.iv, + mode: C.mode.CFB, + padding: C.pad.NoPadding + }).ciphertext; + expect(actual.toString()).toBe(expected.toString()); + }); + + test('testDecryptor', () => { + let encrypted = C.AES.encrypt(data.message, data.key, { + iv: data.iv, + mode: C.mode.CFB, + padding: C.pad.NoPadding + }); + let decrypted = C.AES.decrypt(encrypted, data.key, { + iv: data.iv, + mode: C.mode.CFB, + padding: C.pad.NoPadding + }); + expect(decrypted.toString()).toBe(data.message.toString()); + + }); +}); \ No newline at end of file diff --git a/test/mode.ctr.test.js b/test/mode.ctr.test.js new file mode 100644 index 0000000..0b9bc26 --- /dev/null +++ b/test/mode.ctr.test.js @@ -0,0 +1,59 @@ +import C from '../src/index'; + +const data = {}; +beforeAll(() => { + data.message = new C.lib.WordArray([ + 0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, + 0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f + ]); + data.key = new C.lib.WordArray([0x20212223, 0x24252627, 0x28292a2b, 0x2c2d2e2f]); + data.iv = new C.lib.WordArray([0x30313233, 0x34353637, 0x38393a3b, 0x3c3d3e3f]); +}); + +describe('mode-ctr-test', () => { + test('testEncryptor', () => { + // Compute expected + let expected = data.message.clone(); + let aes = C.algo.AES.createEncryptor(data.key); + + // Counter initialized with IV + let counter = data.iv.words.slice(0); + + // First block XORed with encrypted counter + let keystream = counter.slice(0); + aes.encryptBlock(keystream, 0); + for (let i = 0; i < 4; i++) { + expected.words[i] ^= keystream[i]; + } + + // Subsequent blocks XORed with encrypted incremented counter + counter[3]++; + keystream = counter.slice(0); + aes.encryptBlock(keystream, 0); + for (let i = 4; i < 8; i++) { + expected.words[i] ^= keystream[i % 4]; + } + + // Compute actual + let actual = C.AES.encrypt(data.message, data.key, { + iv: data.iv, + mode: C.mode.CTR, + padding: C.pad.NoPadding + }).ciphertext; + expect(actual.toString()).toBe(expected.toString()); + }); + + test('testDecryptor', () => { + let encrypted = C.AES.encrypt(data.message, data.key, { + iv: data.iv, + mode: C.mode.CTR, + padding: C.pad.NoPadding + }); + let decrypted = C.AES.decrypt(encrypted, data.key, { + iv: data.iv, + mode: C.mode.CTR, + padding: C.pad.NoPadding + }); + expect(decrypted.toString()).toBe(data.message.toString()); + }); +}); \ No newline at end of file diff --git a/test/mode.ecb.test.js b/test/mode.ecb.test.js new file mode 100644 index 0000000..3670c48 --- /dev/null +++ b/test/mode.ecb.test.js @@ -0,0 +1,40 @@ +import C from '../src/index'; + +const data = {}; + +beforeAll(() => { + data.message = new C.lib.WordArray([ + 0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, + 0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f + ]); + data.key = new C.lib.WordArray([0x20212223, 0x24252627, 0x28292a2b, 0x2c2d2e2f]); +}); + +describe('node-ecb-test', () => { + test('testEncryptor', () => { + // Compute expected + let expected = data.message.clone(); + let aes = C.algo.AES.createEncryptor(data.key); + aes.encryptBlock(expected.words, 0); + aes.encryptBlock(expected.words, 4); + + // Compute actual + let actual = C.AES.encrypt(data.message, data.key, { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }).ciphertext; + expect(actual.toString()).toBe(expected.toString()); + }); + + test('testDecryptor', () => { + let encrypted = C.AES.encrypt(data.message, data.key, { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }); + let decrypted = C.AES.decrypt(encrypted, data.key, { + mode: C.mode.ECB, + padding: C.pad.NoPadding + }); + expect(decrypted.toString()).toBe(data.message.toString()); + }); +}); \ No newline at end of file diff --git a/test/mode.ofb.test.js b/test/mode.ofb.test.js new file mode 100644 index 0000000..926b877 --- /dev/null +++ b/test/mode.ofb.test.js @@ -0,0 +1,55 @@ +import C from '../src/index'; + +const data = {}; + +beforeAll(() => { + data.message = new C.lib.WordArray([ + 0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, + 0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f + ]); + data.key = new C.lib.WordArray([0x20212223, 0x24252627, 0x28292a2b, 0x2c2d2e2f]); + data.iv = new C.lib.WordArray([0x30313233, 0x34353637, 0x38393a3b, 0x3c3d3e3f]); +}); + +describe('mode-ofb-test', () => { + test('testEncryptor', () => { + // Compute expected + let expected = data.message.clone(); + let aes = C.algo.AES.createEncryptor(data.key); + + // First block XORed with encrypted IV + let keystream = data.iv.words.slice(0); + aes.encryptBlock(keystream, 0); + for (let i = 0; i < 4; i++) { + expected.words[i] ^= keystream[i]; + } + + // Subsequent blocks XORed with encrypted previous keystream + aes.encryptBlock(keystream, 0); + for (let i = 4; i < 8; i++) { + expected.words[i] ^= keystream[i % 4]; + } + + // Compute actual + let actual = C.AES.encrypt(data.message, data.key, { + iv: data.iv, + mode: C.mode.OFB, + padding: C.pad.NoPadding + }).ciphertext; + expect(actual.toString()).toBe(expected.toString()); + }); + + test('testDecryptor', () => { + let encrypted = C.AES.encrypt(data.message, data.key, { + iv: data.iv, + mode: C.mode.OFB, + padding: C.pad.NoPadding + }); + let decrypted = C.AES.decrypt(encrypted, data.key, { + iv: data.iv, + mode: C.mode.OFB, + padding: C.pad.NoPadding + }); + expect(decrypted.toString()).toBe(data.message.toString()); + }) +}); \ No newline at end of file diff --git a/test/pad.ansix923.test.js b/test/pad.ansix923.test.js new file mode 100644 index 0000000..1cfef36 --- /dev/null +++ b/test/pad.ansix923.test.js @@ -0,0 +1,21 @@ +import C from '../src/index'; + +describe('pad-ansix923-test', () => { + test('testPad', () => { + let data = new C.lib.WordArray([0xdddddd00], 3); + C.pad.AnsiX923.pad(data, 2); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd00, 0x00000005]).toString()); + }); + + test('testPadClamp', () => { + let data = new C.lib.WordArray([0xdddddddd, 0xdddddddd], 3); + C.pad.AnsiX923.pad(data, 2); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd00, 0x00000005]).toString()); + }); + + test('testUnpad', () => { + let data = new C.lib.WordArray([0xdddddd00, 0x00000005]); + C.pad.AnsiX923.unpad(data); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd00], 3).toString()); + }); +}); \ No newline at end of file diff --git a/test/pad.iso10126.test.js b/test/pad.iso10126.test.js new file mode 100644 index 0000000..b9921ec --- /dev/null +++ b/test/pad.iso10126.test.js @@ -0,0 +1,42 @@ +import C from '../src/index'; + +const _data = {}; +beforeAll(() => { + // Save original random method + _data.random = C.lib.WordArray.random; + + // Replace random method with one that returns a predictable value + C.lib.WordArray.random = function (nBytes) { + let words = []; + for (let i = 0; i < nBytes; i += 4) { + words.push([0x11223344]); + } + + return new C.lib.WordArray(words, nBytes); + }; +}); + +afterAll(() => { + // Restore random method + C.lib.WordArray.random = _data.random; +}); + +describe('pad-iso10126-test', () => { + test('testPad', () => { + let data = new C.lib.WordArray([0xdddddd00], 3); + C.pad.Iso10126.pad(data, 2); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd11, 0x22334405]).toString()); + }); + + test('testPadClamp', () => { + let data = new C.lib.WordArray([0xdddddddd, 0xdddddddd], 3); + C.pad.Iso10126.pad(data, 2); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd11, 0x22334405]).toString()); + }); + + test('testUnpad', () => { + let data = new C.lib.WordArray([0xdddddd11, 0x22334405]); + C.pad.Iso10126.unpad(data); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd00], 3).toString()); + }); +}); \ No newline at end of file diff --git a/test/pad.iso97971.test.js b/test/pad.iso97971.test.js new file mode 100644 index 0000000..c3d367b --- /dev/null +++ b/test/pad.iso97971.test.js @@ -0,0 +1,27 @@ +import C from '../src/index'; + +describe('pad-iso97971-test', () => { + test('testPad1', () => { + let data = new C.lib.WordArray([0xdddddd00], 3); + C.pad.Iso97971.pad(data, 1); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd80]).toString()); + }); + + test('testPad2', () => { + let data = new C.lib.WordArray([0xdddddd00], 3); + C.pad.Iso97971.pad(data, 2); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd80, 0x00000000]).toString()); + }); + + test('testPadClamp', () => { + let data = new C.lib.WordArray([0xdddddddd, 0xdddddddd], 3); + C.pad.Iso97971.pad(data, 2); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd80, 0x00000000]).toString()); + }); + + test('testUnpad', () => { + let data = new C.lib.WordArray([0xdddddd80, 0x00000000]); + C.pad.Iso97971.unpad(data); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd00], 3).toString()); + }); +}); \ No newline at end of file diff --git a/test/pad.pkcs7.test.js b/test/pad.pkcs7.test.js new file mode 100644 index 0000000..03ae1b5 --- /dev/null +++ b/test/pad.pkcs7.test.js @@ -0,0 +1,21 @@ +import C from '../src/index'; + +describe('pad-pkcs7-test', () => { + test('testPad', () => { + let data = new C.lib.WordArray([0xdddddd00], 3); + C.pad.Pkcs7.pad(data, 2); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd05, 0x05050505]).toString()); + }); + + test('testPadClamp', () => { + const data = new C.lib.WordArray([0xdddddddd, 0xdddddddd], 3); + C.pad.Pkcs7.pad(data, 2); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd05, 0x05050505]).toString()); + }); + + test('testUnpad', () => { + let data = new C.lib.WordArray([0xdddddd05, 0x05050505]); + C.pad.Pkcs7.unpad(data); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd00], 3).toString()); + }); +}); \ No newline at end of file diff --git a/test/pad.zeropadding.test.js b/test/pad.zeropadding.test.js new file mode 100644 index 0000000..039ceda --- /dev/null +++ b/test/pad.zeropadding.test.js @@ -0,0 +1,21 @@ +import C from '../src/index'; + +describe('pad-zeropadding-test', () => { + test('testPad', () => { + let data = new C.lib.WordArray([0xdddddd00], 3); + C.pad.ZeroPadding.pad(data, 2); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd00, 0x00000000]).toString()); + }); + + test('testPadClamp', () => { + let data = new C.lib.WordArray([0xdddddddd, 0xdddddddd], 3); + C.pad.ZeroPadding.pad(data, 2); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd00, 0x00000000]).toString()); + }); + + test('testUnpad', () => { + let data = new C.lib.WordArray([0xdddddd00, 0x00000000]); + C.pad.ZeroPadding.unpad(data); + expect(data.toString()).toBe(new C.lib.WordArray([0xdddddd00], 3).toString()); + }); +}); \ No newline at end of file diff --git a/test/x64.word.test.js b/test/x64.word.test.js new file mode 100644 index 0000000..df788d8 --- /dev/null +++ b/test/x64.word.test.js @@ -0,0 +1,9 @@ +import C from '../src/index'; + +describe('x64-word-test', () => { + test('testInit', () => { + let word = new C.x64.Word(0x00010203, 0x04050607); + expect(word.high).toBe(0x00010203); + expect(word.low).toBe(0x04050607); + }); +}); \ No newline at end of file diff --git a/test/x64.wordarray.test.js b/test/x64.wordarray.test.js new file mode 100644 index 0000000..7b0ed8e --- /dev/null +++ b/test/x64.wordarray.test.js @@ -0,0 +1,24 @@ +import C from '../src/index'; + +describe('x64-wordarray-test', () => { + test('testInit0', () => { + expect(new C.x64.WordArray().toX32().toString()).toBe(''); + }); + + test('testInit1', () => { + let wordArray = new C.x64.WordArray([ + new C.x64.Word(0x00010203, 0x04050607), + new C.x64.Word(0x18191a1b, 0x1c1d1e1f) + ]); + expect(wordArray.toX32().toString()).toBe('000102030405060718191a1b1c1d1e1f'); + }); + + test('testInit2', () => { + let wordArray = new C.x64.WordArray([ + new C.x64.Word(0x00010203, 0x04050607), + new C.x64.Word(0x18191a1b, 0x1c1d1e1f) + ], 10); + + expect(wordArray.toX32().toString()).toBe('00010203040506071819'); + }); +}); \ No newline at end of file