summaryrefslogtreecommitdiffstats
path: root/core/gcm.js
diff options
context:
space:
mode:
Diffstat (limited to 'core/gcm.js')
-rw-r--r--core/gcm.js183
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 };
+ }
+};