diff options
Diffstat (limited to 'core/ccmArrayBuffer.js')
-rw-r--r-- | core/ccmArrayBuffer.js | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/core/ccmArrayBuffer.js b/core/ccmArrayBuffer.js new file mode 100644 index 0000000..a7b44b0 --- /dev/null +++ b/core/ccmArrayBuffer.js @@ -0,0 +1,247 @@ +/** @fileOverview Really fast & small implementation of CCM using JS' array buffers + * + * @author Marco Munizaga + */ + +/** @namespace CTR mode with CBC MAC. */ + +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 The authenticated data. + * @param {Number} [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<data.byteLength;i+=16){ + if (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; + } + +}; |