/** @fileOverview Really fast & small implementation of CCM using JS' array buffers * * @author Marco Munizaga */ /** * CTR mode with CBC MAC. * @namespace */ sjcl.arrayBuffer = sjcl.arrayBuffer || {}; //patch arraybuffers if they don't exist if (typeof(ArrayBuffer) === 'undefined') { (function(globals){ "use strict"; globals.ArrayBuffer = function(){}; globals.DataView = function(){}; }(this)); } sjcl.arrayBuffer.ccm = { mode: "ccm", defaults: { tlen:128 //this is M in the NIST paper }, /** Encrypt in CCM mode. Meant to return the same exact thing as the bitArray ccm to work as a drop in replacement * @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=64] the desired tag length, in bits. * @return {bitArray} The encrypted data, an array of bytes. */ compat_encrypt: function(prf, plaintext, iv, adata, tlen){ var plaintext_buffer = sjcl.codec.arrayBuffer.fromBits(plaintext, true, 16), ol = sjcl.bitArray.bitLength(plaintext)/8, encrypted_obj, ct, tag; tlen = tlen || 64; adata = adata || []; encrypted_obj = sjcl.arrayBuffer.ccm.encrypt(prf, plaintext_buffer, iv, adata, tlen, ol); ct = sjcl.codec.arrayBuffer.toBits(encrypted_obj.ciphertext_buffer); ct = sjcl.bitArray.clamp(ct, ol*8); return sjcl.bitArray.concat(ct, encrypted_obj.tag); }, /** Decrypt in CCM mode. Meant to imitate the bitArray ccm * @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=[]] adata The authenticated data. * @param {Number} [tlen=64] tlen the desired tag length, in bits. * @return {bitArray} The decrypted data. */ compat_decrypt: function(prf, ciphertext, iv, adata, tlen){ tlen = tlen || 64; adata = adata || []; var L, i, w=sjcl.bitArray, ol = w.bitLength(ciphertext), out = w.clamp(ciphertext, ol - tlen), tag = w.bitSlice(ciphertext, ol - tlen), tag2, ciphertext_buffer = sjcl.codec.arrayBuffer.fromBits(out, true, 16); var plaintext_buffer = sjcl.arrayBuffer.ccm.decrypt(prf, ciphertext_buffer, iv, tag, adata, tlen, (ol-tlen)/8); return sjcl.bitArray.clamp(sjcl.codec.arrayBuffer.toBits(plaintext_buffer), ol-tlen); }, /** Really fast ccm encryption, uses arraybufer and mutates the plaintext buffer * @static * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. * @param {ArrayBuffer} plaintext_buffer The plaintext data. * @param {bitArray} iv The initialization value. * @param {ArrayBuffer} [adata=[]] The authenticated data. * @param {Number} [tlen=128] the desired tag length, in bits. * @return {ArrayBuffer} The encrypted data, in the same array buffer as the given plaintext, but given back anyways */ encrypt: function(prf, plaintext_buffer, iv, adata, tlen, ol){ var auth_blocks, mac, L, w = sjcl.bitArray, ivl = w.bitLength(iv) / 8; //set up defaults adata = adata || []; tlen = tlen || sjcl.arrayBuffer.ccm.defaults.tlen; ol = ol || plaintext_buffer.byteLength; tlen = Math.ceil(tlen/8); for (L=2; L<4 && ol >>> 8*L; L++) {} if (L < 15 - ivl) { L = 15-ivl; } iv = w.clamp(iv,8*(15-L)); //prf should use a 256 bit key to make precomputation attacks infeasible mac = sjcl.arrayBuffer.ccm._computeTag(prf, plaintext_buffer, iv, adata, tlen, ol, L); //encrypt the plaintext and the mac //returns the mac since the plaintext will be left encrypted inside the buffer mac = sjcl.arrayBuffer.ccm._ctrMode(prf, plaintext_buffer, iv, mac, tlen, L); //the plaintext_buffer has been modified so it is now the ciphertext_buffer return {'ciphertext_buffer':plaintext_buffer, 'tag':mac}; }, /** Really fast ccm decryption, uses arraybufer and mutates the given buffer * @static * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. * @param {ArrayBuffer} ciphertext_buffer The Ciphertext data. * @param {bitArray} iv The initialization value. * @param {bitArray} The authentication tag for the ciphertext * @param {ArrayBuffer} [adata=[]] The authenticated data. * @param {Number} [tlen=128] the desired tag length, in bits. * @return {ArrayBuffer} The decrypted data, in the same array buffer as the given buffer, but given back anyways */ decrypt: function(prf, ciphertext_buffer, iv, tag, adata, tlen, ol){ var mac, mac2, i, L, w = sjcl.bitArray, ivl = w.bitLength(iv) / 8; //set up defaults adata = adata || []; tlen = tlen || sjcl.arrayBuffer.ccm.defaults.tlen; ol = ol || ciphertext_buffer.byteLength; tlen = Math.ceil(tlen/8) ; for (L=2; L<4 && ol >>> 8*L; L++) {} if (L < 15 - ivl) { L = 15-ivl; } iv = w.clamp(iv,8*(15-L)); //prf should use a 256 bit key to make precomputation attacks infeasible //decrypt the buffer mac = sjcl.arrayBuffer.ccm._ctrMode(prf, ciphertext_buffer, iv, tag, tlen, L); mac2 = sjcl.arrayBuffer.ccm._computeTag(prf, ciphertext_buffer, iv, adata, tlen, ol, L); //check the tag if (!sjcl.bitArray.equal(mac, mac2)){ throw new sjcl.exception.corrupt("ccm: tag doesn't match"); } return ciphertext_buffer; }, /* Compute the (unencrypted) authentication tag, according to the CCM specification * @param {Object} prf The pseudorandom function. * @param {ArrayBuffer} data_buffer The plaintext data in an arraybuffer. * @param {bitArray} iv The initialization value. * @param {bitArray} adata The authenticated data. * @param {Number} tlen the desired tag length, in bits. * @return {bitArray} The tag, but not yet encrypted. * @private */ _computeTag: function(prf, data_buffer, iv, adata, tlen, ol, L){ var i, plaintext, mac, data, data_blocks_size, data_blocks, w = sjcl.bitArray, tmp, macData; mac = sjcl.mode.ccm._macAdditionalData(prf, adata, iv, tlen, ol, L); if (data_buffer.byteLength !== 0) { data = new DataView(data_buffer); //set padding bytes to 0 for (i=ol; i< data_buffer.byteLength; i++){ data.setUint8(i,0); } //now to mac the plaintext blocks for (i=0; i < data.byteLength; i+=16){ mac[0] ^= data.getUint32(i); mac[1] ^= data.getUint32(i+4); mac[2] ^= data.getUint32(i+8); mac[3] ^= data.getUint32(i+12); mac = prf.encrypt(mac); } } return sjcl.bitArray.clamp(mac,tlen*8); }, /** CCM CTR mode. * Encrypt or decrypt data and tag with the prf in CCM-style CTR mode. * Mutates given array buffer * @param {Object} prf The PRF. * @param {ArrayBuffer} data_buffer The data to be encrypted or decrypted. * @param {bitArray} iv The initialization vector. * @param {bitArray} tag The authentication tag. * @param {Number} tlen The length of th etag, in bits. * @return {Object} An object with data and tag, the en/decryption of data and tag values. * @private */ _ctrMode: function(prf, data_buffer, iv, mac, tlen, L){ var data, ctr, word0, word1, word2, word3, keyblock, i, w = sjcl.bitArray, xor = w._xor4, n = data_buffer.byteLength/50, p = n; ctr = new DataView(new ArrayBuffer(16)); //create the first block for the counter //prf should use a 256 bit key to make precomputation attacks infeasible // start the ctr ctr = w.concat([w.partial(8,L-1)],iv).concat([0,0,0]).slice(0,4); // en/decrypt the tag mac = w.bitSlice(xor(mac,prf.encrypt(ctr)), 0, tlen*8); ctr[3]++; if (ctr[3]===0) ctr[2]++; //increment higher bytes if the lowest 4 bytes are 0 if (data_buffer.byteLength !== 0) { data = new DataView(data_buffer); //now lets encrypt the message for (i=0; i n) { sjcl.mode.ccm._callProgressListener(i/data_buffer.byteLength); n += p; } keyblock = prf.encrypt(ctr); word0 = data.getUint32(i); word1 = data.getUint32(i+4); word2 = data.getUint32(i+8); word3 = data.getUint32(i+12); data.setUint32(i,word0 ^ keyblock[0]); data.setUint32(i+4, word1 ^ keyblock[1]); data.setUint32(i+8, word2 ^ keyblock[2]); data.setUint32(i+12, word3 ^ keyblock[3]); ctr[3]++; if (ctr[3]===0) ctr[2]++; //increment higher bytes if the lowest 4 bytes are 0 } } //return the mac, the ciphered data is available through the same data_buffer that was given return mac; } };