Source: ccm.js

  1. /** @fileOverview CCM mode implementation.
  2. *
  3. * Special thanks to Roy Nicholson for pointing out a bug in our
  4. * implementation.
  5. *
  6. * @author Emily Stark
  7. * @author Mike Hamburg
  8. * @author Dan Boneh
  9. */
  10. /**
  11. * CTR mode with CBC MAC.
  12. * @namespace
  13. */
  14. sjcl.mode.ccm = {
  15. /** The name of the mode.
  16. * @constant
  17. */
  18. name: "ccm",
  19. _progressListeners: [],
  20. listenProgress: function (cb) {
  21. sjcl.mode.ccm._progressListeners.push(cb);
  22. },
  23. unListenProgress: function (cb) {
  24. var index = sjcl.mode.ccm._progressListeners.indexOf(cb);
  25. if (index > -1) {
  26. sjcl.mode.ccm._progressListeners.splice(index, 1);
  27. }
  28. },
  29. _callProgressListener: function (val) {
  30. var p = sjcl.mode.ccm._progressListeners.slice(), i;
  31. for (i = 0; i < p.length; i += 1) {
  32. p[i](val);
  33. }
  34. },
  35. /** Encrypt in CCM mode.
  36. * @static
  37. * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes.
  38. * @param {bitArray} plaintext The plaintext data.
  39. * @param {bitArray} iv The initialization value.
  40. * @param {bitArray} [adata=[]] The authenticated data.
  41. * @param {Number} [tlen=64] the desired tag length, in bits.
  42. * @return {bitArray} The encrypted data, an array of bytes.
  43. */
  44. encrypt: function(prf, plaintext, iv, adata, tlen) {
  45. var L, out = plaintext.slice(0), tag, w=sjcl.bitArray, ivl = w.bitLength(iv) / 8, ol = w.bitLength(out) / 8;
  46. tlen = tlen || 64;
  47. adata = adata || [];
  48. if (ivl < 7) {
  49. throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");
  50. }
  51. // compute the length of the length
  52. for (L=2; L<4 && ol >>> 8*L; L++) {}
  53. if (L < 15 - ivl) { L = 15-ivl; }
  54. iv = w.clamp(iv,8*(15-L));
  55. // compute the tag
  56. tag = sjcl.mode.ccm._computeTag(prf, plaintext, iv, adata, tlen, L);
  57. // encrypt
  58. out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L);
  59. return w.concat(out.data, out.tag);
  60. },
  61. /** Decrypt in CCM mode.
  62. * @static
  63. * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes.
  64. * @param {bitArray} ciphertext The ciphertext data.
  65. * @param {bitArray} iv The initialization value.
  66. * @param {bitArray} [adata=[]] adata The authenticated data.
  67. * @param {Number} [tlen=64] tlen the desired tag length, in bits.
  68. * @return {bitArray} The decrypted data.
  69. */
  70. decrypt: function(prf, ciphertext, iv, adata, tlen) {
  71. tlen = tlen || 64;
  72. adata = adata || [];
  73. var L,
  74. w=sjcl.bitArray,
  75. ivl = w.bitLength(iv) / 8,
  76. ol = w.bitLength(ciphertext),
  77. out = w.clamp(ciphertext, ol - tlen),
  78. tag = w.bitSlice(ciphertext, ol - tlen), tag2;
  79. ol = (ol - tlen) / 8;
  80. if (ivl < 7) {
  81. throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");
  82. }
  83. // compute the length of the length
  84. for (L=2; L<4 && ol >>> 8*L; L++) {}
  85. if (L < 15 - ivl) { L = 15-ivl; }
  86. iv = w.clamp(iv,8*(15-L));
  87. // decrypt
  88. out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L);
  89. // check the tag
  90. tag2 = sjcl.mode.ccm._computeTag(prf, out.data, iv, adata, tlen, L);
  91. if (!w.equal(out.tag, tag2)) {
  92. throw new sjcl.exception.corrupt("ccm: tag doesn't match");
  93. }
  94. return out.data;
  95. },
  96. _macAdditionalData: function (prf, adata, iv, tlen, ol, L) {
  97. var mac, tmp, i, macData = [], w=sjcl.bitArray, xor = w._xor4;
  98. // mac the flags
  99. mac = [w.partial(8, (adata.length ? 1<<6 : 0) | (tlen-2) << 2 | L-1)];
  100. // mac the iv and length
  101. mac = w.concat(mac, iv);
  102. mac[3] |= ol;
  103. mac = prf.encrypt(mac);
  104. if (adata.length) {
  105. // mac the associated data. start with its length...
  106. tmp = w.bitLength(adata)/8;
  107. if (tmp <= 0xFEFF) {
  108. macData = [w.partial(16, tmp)];
  109. } else if (tmp <= 0xFFFFFFFF) {
  110. macData = w.concat([w.partial(16,0xFFFE)], [tmp]);
  111. } // else ...
  112. // mac the data itself
  113. macData = w.concat(macData, adata);
  114. for (i=0; i<macData.length; i += 4) {
  115. mac = prf.encrypt(xor(mac, macData.slice(i,i+4).concat([0,0,0])));
  116. }
  117. }
  118. return mac;
  119. },
  120. /* Compute the (unencrypted) authentication tag, according to the CCM specification
  121. * @param {Object} prf The pseudorandom function.
  122. * @param {bitArray} plaintext The plaintext data.
  123. * @param {bitArray} iv The initialization value.
  124. * @param {bitArray} adata The authenticated data.
  125. * @param {Number} tlen the desired tag length, in bits.
  126. * @return {bitArray} The tag, but not yet encrypted.
  127. * @private
  128. */
  129. _computeTag: function(prf, plaintext, iv, adata, tlen, L) {
  130. // compute B[0]
  131. var mac, i, w=sjcl.bitArray, xor = w._xor4;
  132. tlen /= 8;
  133. // check tag length and message length
  134. if (tlen % 2 || tlen < 4 || tlen > 16) {
  135. throw new sjcl.exception.invalid("ccm: invalid tag length");
  136. }
  137. if (adata.length > 0xFFFFFFFF || plaintext.length > 0xFFFFFFFF) {
  138. // I don't want to deal with extracting high words from doubles.
  139. throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data");
  140. }
  141. mac = sjcl.mode.ccm._macAdditionalData(prf, adata, iv, tlen, w.bitLength(plaintext)/8, L);
  142. // mac the plaintext
  143. for (i=0; i<plaintext.length; i+=4) {
  144. mac = prf.encrypt(xor(mac, plaintext.slice(i,i+4).concat([0,0,0])));
  145. }
  146. return w.clamp(mac, tlen * 8);
  147. },
  148. /** CCM CTR mode.
  149. * Encrypt or decrypt data and tag with the prf in CCM-style CTR mode.
  150. * May mutate its arguments.
  151. * @param {Object} prf The PRF.
  152. * @param {bitArray} data The data to be encrypted or decrypted.
  153. * @param {bitArray} iv The initialization vector.
  154. * @param {bitArray} tag The authentication tag.
  155. * @param {Number} tlen The length of th etag, in bits.
  156. * @param {Number} L The CCM L value.
  157. * @return {Object} An object with data and tag, the en/decryption of data and tag values.
  158. * @private
  159. */
  160. _ctrMode: function(prf, data, iv, tag, tlen, L) {
  161. var enc, i, w=sjcl.bitArray, xor = w._xor4, ctr, l = data.length, bl=w.bitLength(data), n = l/50, p = n;
  162. // start the ctr
  163. ctr = w.concat([w.partial(8,L-1)],iv).concat([0,0,0]).slice(0,4);
  164. // en/decrypt the tag
  165. tag = w.bitSlice(xor(tag,prf.encrypt(ctr)), 0, tlen);
  166. // en/decrypt the data
  167. if (!l) { return {tag:tag, data:[]}; }
  168. for (i=0; i<l; i+=4) {
  169. if (i > n) {
  170. sjcl.mode.ccm._callProgressListener(i/l);
  171. n += p;
  172. }
  173. ctr[3]++;
  174. enc = prf.encrypt(ctr);
  175. data[i] ^= enc[0];
  176. data[i+1] ^= enc[1];
  177. data[i+2] ^= enc[2];
  178. data[i+3] ^= enc[3];
  179. }
  180. return { tag:tag, data:w.clamp(data,bl) };
  181. }
  182. };