/** @fileOverview CCM mode implementation. * * Special thanks to Roy Nicholson for pointing out a bug in our * implementation. * * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh */ /** @namespace CTR mode with CBC MAC. */ sjcl.mode.ccm = { /** The name of the mode. * @constant */ name: "ccm", /** Encrypt in CCM 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=64] the desired tag length, in bits. * @return {bitArray} The encrypted data, an array of bytes. */ encrypt: function(prf, plaintext, iv, adata, tlen) { var L, i, out = plaintext.slice(0), tag, w=sjcl.bitArray, ivl = w.bitLength(iv) / 8, ol = w.bitLength(out) / 8; tlen = tlen || 64; adata = adata || []; if (ivl < 7) { throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes"); } // compute the length of the length for (L=2; L<4 && ol >>> 8*L; L++) {} if (L < 15 - ivl) { L = 15-ivl; } iv = w.clamp(iv,8*(15-L)); // compute the tag tag = sjcl.mode.ccm._computeTag(prf, plaintext, iv, adata, tlen, L); // encrypt out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L); return w.concat(out.data, out.tag); }, /** Decrypt in CCM 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} [64] tlen the desired tag length, in bits. * @return {bitArray} The decrypted data. */ decrypt: function(prf, ciphertext, iv, adata, tlen) { tlen = tlen || 64; adata = adata || []; var L, i, w=sjcl.bitArray, ivl = w.bitLength(iv) / 8, ol = w.bitLength(ciphertext), out = w.clamp(ciphertext, ol - tlen), tag = w.bitSlice(ciphertext, ol - tlen), tag2; ol = (ol - tlen) / 8; if (ivl < 7) { throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes"); } // compute the length of the length for (L=2; L<4 && ol >>> 8*L; L++) {} if (L < 15 - ivl) { L = 15-ivl; } iv = w.clamp(iv,8*(15-L)); // decrypt out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L); // check the tag tag2 = sjcl.mode.ccm._computeTag(prf, out.data, iv, adata, tlen, L); if (!w.equal(out.tag, tag2)) { throw new sjcl.exception.corrupt("ccm: tag doesn't match"); } return out.data; }, /* Compute the (unencrypted) authentication tag, according to the CCM specification * @param {Object} prf The pseudorandom function. * @param {bitArray} plaintext The plaintext data. * @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, plaintext, iv, adata, tlen, L) { // compute B[0] var q, mac, field = 0, offset = 24, tmp, i, macData = [], w=sjcl.bitArray, xor = w._xor4; tlen /= 8; // check tag length and message length if (tlen % 2 || tlen < 4 || tlen > 16) { throw new sjcl.exception.invalid("ccm: invalid tag length"); } if (adata.length > 0xFFFFFFFF || plaintext.length > 0xFFFFFFFF) { // I don't want to deal with extracting high words from doubles. throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data"); } // mac the flags mac = [w.partial(8, (adata.length ? 1<<6 : 0) | (tlen-2) << 2 | L-1)]; // mac the iv and length mac = w.concat(mac, iv); mac[3] |= w.bitLength(plaintext)/8; mac = prf.encrypt(mac); if (adata.length) { // mac the associated data. start with its length... tmp = w.bitLength(adata)/8; if (tmp <= 0xFEFF) { macData = [w.partial(16, tmp)]; } else if (tmp <= 0xFFFFFFFF) { macData = w.concat([w.partial(16,0xFFFE)], [tmp]); } // else ... // mac the data itself macData = w.concat(macData, adata); for (i=0; i<macData.length; i += 4) { mac = prf.encrypt(xor(mac, macData.slice(i,i+4).concat([0,0,0]))); } } // mac the plaintext for (i=0; i<plaintext.length; i+=4) { mac = prf.encrypt(xor(mac, plaintext.slice(i,i+4).concat([0,0,0]))); } return w.clamp(mac, tlen * 8); }, /** CCM CTR mode. * Encrypt or decrypt data and tag with the prf in CCM-style CTR mode. * May mutate its arguments. * @param {Object} prf The PRF. * @param {bitArray} data 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. * @param {Number} L The CCM L value. * @return {Object} An object with data and tag, the en/decryption of data and tag values. * @private */ _ctrMode: function(prf, data, iv, tag, tlen, L) { var enc, i, w=sjcl.bitArray, xor = w._xor4, ctr, b, l = data.length, bl=w.bitLength(data); // start the ctr ctr = w.concat([w.partial(8,L-1)],iv).concat([0,0,0]).slice(0,4); // en/decrypt the tag tag = w.bitSlice(xor(tag,prf.encrypt(ctr)), 0, tlen); // en/decrypt the data if (!l) { return {tag:tag, 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]; } return { tag:tag, data:w.clamp(data,bl) }; } };