/** @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 };
}
};