diff options
author | Steve Thomas <Sc00bz@users.noreply.github.com> | 2015-11-01 14:36:28 -0600 |
---|---|---|
committer | Steve Thomas <Sc00bz@users.noreply.github.com> | 2015-11-01 14:36:28 -0600 |
commit | db7e40d449ab4ae65bacf267b38d1bf836b4fe54 (patch) | |
tree | f6f815673f56490c8ae3bfdc8ed9a5f4d9996f76 /core/gcm.js | |
parent | 025908ef2b091faab75f60188654f789b691ff0d (diff) | |
parent | f9a2494fae0dcddef493de453aa6ab69caa987cf (diff) | |
download | sjcl-db7e40d449ab4ae65bacf267b38d1bf836b4fe54.zip sjcl-db7e40d449ab4ae65bacf267b38d1bf836b4fe54.tar.gz sjcl-db7e40d449ab4ae65bacf267b38d1bf836b4fe54.tar.bz2 |
Merge pull request #1 from bitwiseshiftleft/master
Update
Diffstat (limited to 'core/gcm.js')
-rw-r--r-- | core/gcm.js | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/core/gcm.js b/core/gcm.js new file mode 100644 index 0000000..61bf59d --- /dev/null +++ b/core/gcm.js @@ -0,0 +1,183 @@ +/** @fileOverview GCM mode implementation. + * + * @author Juho Vähä-Herttua + */ + +/** @namespace Galois/Counter mode. */ +sjcl.mode.gcm = { + /** The name of the mode. + * @constant + */ + name: "gcm", + + /** Encrypt in GCM mode. + * @static + * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. + * @param {bitArray} plaintext The plaintext data. + * @param {bitArray} iv The initialization value. + * @param {bitArray} [adata=[]] The authenticated data. + * @param {Number} [tlen=128] The desired tag length, in bits. + * @return {bitArray} The encrypted data, an array of bytes. + */ + encrypt: function (prf, plaintext, iv, adata, tlen) { + var out, data = plaintext.slice(0), w=sjcl.bitArray; + tlen = tlen || 128; + adata = adata || []; + + // encrypt and tag + out = sjcl.mode.gcm._ctrMode(true, prf, data, adata, iv, tlen); + + return w.concat(out.data, out.tag); + }, + + /** Decrypt in GCM mode. + * @static + * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. + * @param {bitArray} ciphertext The ciphertext data. + * @param {bitArray} iv The initialization value. + * @param {bitArray} [adata=[]] The authenticated data. + * @param {Number} [tlen=128] The desired tag length, in bits. + * @return {bitArray} The decrypted data. + */ + decrypt: function (prf, ciphertext, iv, adata, tlen) { + var out, data = ciphertext.slice(0), tag, w=sjcl.bitArray, l=w.bitLength(data); + tlen = tlen || 128; + adata = adata || []; + + // Slice tag out of data + if (tlen <= l) { + tag = w.bitSlice(data, l-tlen); + data = w.bitSlice(data, 0, l-tlen); + } else { + tag = data; + data = []; + } + + // decrypt and tag + out = sjcl.mode.gcm._ctrMode(false, prf, data, adata, iv, tlen); + + if (!w.equal(out.tag, tag)) { + throw new sjcl.exception.corrupt("gcm: tag doesn't match"); + } + return out.data; + }, + + /* Compute the galois multiplication of X and Y + * @private + */ + _galoisMultiply: function (x, y) { + var i, j, xi, Zi, Vi, lsb_Vi, w=sjcl.bitArray, xor=w._xor4; + + Zi = [0,0,0,0]; + Vi = y.slice(0); + + // Block size is 128 bits, run 128 times to get Z_128 + for (i=0; i<128; i++) { + xi = (x[Math.floor(i/32)] & (1 << (31-i%32))) !== 0; + if (xi) { + // Z_i+1 = Z_i ^ V_i + Zi = xor(Zi, Vi); + } + + // Store the value of LSB(V_i) + lsb_Vi = (Vi[3] & 1) !== 0; + + // V_i+1 = V_i >> 1 + for (j=3; j>0; j--) { + Vi[j] = (Vi[j] >>> 1) | ((Vi[j-1]&1) << 31); + } + Vi[0] = Vi[0] >>> 1; + + // If LSB(V_i) is 1, V_i+1 = (V_i >> 1) ^ R + if (lsb_Vi) { + Vi[0] = Vi[0] ^ (0xe1 << 24); + } + } + return Zi; + }, + + _ghash: function(H, Y0, data) { + var Yi, i, l = data.length; + + Yi = Y0.slice(0); + for (i=0; i<l; i+=4) { + Yi[0] ^= 0xffffffff&data[i]; + Yi[1] ^= 0xffffffff&data[i+1]; + Yi[2] ^= 0xffffffff&data[i+2]; + Yi[3] ^= 0xffffffff&data[i+3]; + Yi = sjcl.mode.gcm._galoisMultiply(Yi, H); + } + return Yi; + }, + + /** GCM CTR mode. + * Encrypt or decrypt data and tag with the prf in GCM-style CTR mode. + * @param {Boolean} encrypt True if encrypt, false if decrypt. + * @param {Object} prf The PRF. + * @param {bitArray} data The data to be encrypted or decrypted. + * @param {bitArray} iv The initialization vector. + * @param {bitArray} adata The associated data to be tagged. + * @param {Number} tlen The length of the tag, in bits. + */ + _ctrMode: function(encrypt, prf, data, adata, iv, tlen) { + var H, J0, S0, enc, i, ctr, tag, last, l, bl, abl, ivbl, w=sjcl.bitArray; + + // Calculate data lengths + l = data.length; + bl = w.bitLength(data); + abl = w.bitLength(adata); + ivbl = w.bitLength(iv); + + // Calculate the parameters + H = prf.encrypt([0,0,0,0]); + if (ivbl === 96) { + J0 = iv.slice(0); + J0 = w.concat(J0, [1]); + } else { + J0 = sjcl.mode.gcm._ghash(H, [0,0,0,0], iv); + J0 = sjcl.mode.gcm._ghash(H, J0, [0,0,Math.floor(ivbl/0x100000000),ivbl&0xffffffff]); + } + S0 = sjcl.mode.gcm._ghash(H, [0,0,0,0], adata); + + // Initialize ctr and tag + ctr = J0.slice(0); + tag = S0.slice(0); + + // If decrypting, calculate hash + if (!encrypt) { + tag = sjcl.mode.gcm._ghash(H, S0, data); + } + + // Encrypt all the data + for (i=0; i<l; i+=4) { + ctr[3]++; + enc = prf.encrypt(ctr); + data[i] ^= enc[0]; + data[i+1] ^= enc[1]; + data[i+2] ^= enc[2]; + data[i+3] ^= enc[3]; + } + data = w.clamp(data, bl); + + // If encrypting, calculate hash + if (encrypt) { + tag = sjcl.mode.gcm._ghash(H, S0, data); + } + + // Calculate last block from bit lengths, ugly because bitwise operations are 32-bit + last = [ + Math.floor(abl/0x100000000), abl&0xffffffff, + Math.floor(bl/0x100000000), bl&0xffffffff + ]; + + // Calculate the final tag block + tag = sjcl.mode.gcm._ghash(H, tag, last); + enc = prf.encrypt(J0); + tag[0] ^= enc[0]; + tag[1] ^= enc[1]; + tag[2] ^= enc[2]; + tag[3] ^= enc[3]; + + return { tag:w.bitSlice(tag, 0, tlen), data:data }; + } +}; |