Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
187 lines (161 sloc)
5.07 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** @fileOverview GCM mode implementation. | |
| * | |
| * @author Juho Vähä-Herttua | |
| */ | |
| /** | |
| * Galois/Counter mode. | |
| * @namespace | |
| */ | |
| 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 }; | |
| } | |
| }; |