186 lines
5.8 KiB
JavaScript
186 lines
5.8 KiB
JavaScript
/** @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) };
|
|
}
|
|
};
|