You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

230 lines
6.5 KiB

4 years ago
  1. /**
  2. * Cipher base API.
  3. *
  4. * @author Dave Longley
  5. *
  6. * Copyright (c) 2010-2014 Digital Bazaar, Inc.
  7. */
  8. var forge = require('./forge');
  9. require('./util');
  10. module.exports = forge.cipher = forge.cipher || {};
  11. // registered algorithms
  12. forge.cipher.algorithms = forge.cipher.algorithms || {};
  13. /**
  14. * Creates a cipher object that can be used to encrypt data using the given
  15. * algorithm and key. The algorithm may be provided as a string value for a
  16. * previously registered algorithm or it may be given as a cipher algorithm
  17. * API object.
  18. *
  19. * @param algorithm the algorithm to use, either a string or an algorithm API
  20. * object.
  21. * @param key the key to use, as a binary-encoded string of bytes or a
  22. * byte buffer.
  23. *
  24. * @return the cipher.
  25. */
  26. forge.cipher.createCipher = function(algorithm, key) {
  27. var api = algorithm;
  28. if(typeof api === 'string') {
  29. api = forge.cipher.getAlgorithm(api);
  30. if(api) {
  31. api = api();
  32. }
  33. }
  34. if(!api) {
  35. throw new Error('Unsupported algorithm: ' + algorithm);
  36. }
  37. // assume block cipher
  38. return new forge.cipher.BlockCipher({
  39. algorithm: api,
  40. key: key,
  41. decrypt: false
  42. });
  43. };
  44. /**
  45. * Creates a decipher object that can be used to decrypt data using the given
  46. * algorithm and key. The algorithm may be provided as a string value for a
  47. * previously registered algorithm or it may be given as a cipher algorithm
  48. * API object.
  49. *
  50. * @param algorithm the algorithm to use, either a string or an algorithm API
  51. * object.
  52. * @param key the key to use, as a binary-encoded string of bytes or a
  53. * byte buffer.
  54. *
  55. * @return the cipher.
  56. */
  57. forge.cipher.createDecipher = function(algorithm, key) {
  58. var api = algorithm;
  59. if(typeof api === 'string') {
  60. api = forge.cipher.getAlgorithm(api);
  61. if(api) {
  62. api = api();
  63. }
  64. }
  65. if(!api) {
  66. throw new Error('Unsupported algorithm: ' + algorithm);
  67. }
  68. // assume block cipher
  69. return new forge.cipher.BlockCipher({
  70. algorithm: api,
  71. key: key,
  72. decrypt: true
  73. });
  74. };
  75. /**
  76. * Registers an algorithm by name. If the name was already registered, the
  77. * algorithm API object will be overwritten.
  78. *
  79. * @param name the name of the algorithm.
  80. * @param algorithm the algorithm API object.
  81. */
  82. forge.cipher.registerAlgorithm = function(name, algorithm) {
  83. name = name.toUpperCase();
  84. forge.cipher.algorithms[name] = algorithm;
  85. };
  86. /**
  87. * Gets a registered algorithm by name.
  88. *
  89. * @param name the name of the algorithm.
  90. *
  91. * @return the algorithm, if found, null if not.
  92. */
  93. forge.cipher.getAlgorithm = function(name) {
  94. name = name.toUpperCase();
  95. if(name in forge.cipher.algorithms) {
  96. return forge.cipher.algorithms[name];
  97. }
  98. return null;
  99. };
  100. var BlockCipher = forge.cipher.BlockCipher = function(options) {
  101. this.algorithm = options.algorithm;
  102. this.mode = this.algorithm.mode;
  103. this.blockSize = this.mode.blockSize;
  104. this._finish = false;
  105. this._input = null;
  106. this.output = null;
  107. this._op = options.decrypt ? this.mode.decrypt : this.mode.encrypt;
  108. this._decrypt = options.decrypt;
  109. this.algorithm.initialize(options);
  110. };
  111. /**
  112. * Starts or restarts the encryption or decryption process, whichever
  113. * was previously configured.
  114. *
  115. * For non-GCM mode, the IV may be a binary-encoded string of bytes, an array
  116. * of bytes, a byte buffer, or an array of 32-bit integers. If the IV is in
  117. * bytes, then it must be Nb (16) bytes in length. If the IV is given in as
  118. * 32-bit integers, then it must be 4 integers long.
  119. *
  120. * Note: an IV is not required or used in ECB mode.
  121. *
  122. * For GCM-mode, the IV must be given as a binary-encoded string of bytes or
  123. * a byte buffer. The number of bytes should be 12 (96 bits) as recommended
  124. * by NIST SP-800-38D but another length may be given.
  125. *
  126. * @param options the options to use:
  127. * iv the initialization vector to use as a binary-encoded string of
  128. * bytes, null to reuse the last ciphered block from a previous
  129. * update() (this "residue" method is for legacy support only).
  130. * additionalData additional authentication data as a binary-encoded
  131. * string of bytes, for 'GCM' mode, (default: none).
  132. * tagLength desired length of authentication tag, in bits, for
  133. * 'GCM' mode (0-128, default: 128).
  134. * tag the authentication tag to check if decrypting, as a
  135. * binary-encoded string of bytes.
  136. * output the output the buffer to write to, null to create one.
  137. */
  138. BlockCipher.prototype.start = function(options) {
  139. options = options || {};
  140. var opts = {};
  141. for(var key in options) {
  142. opts[key] = options[key];
  143. }
  144. opts.decrypt = this._decrypt;
  145. this._finish = false;
  146. this._input = forge.util.createBuffer();
  147. this.output = options.output || forge.util.createBuffer();
  148. this.mode.start(opts);
  149. };
  150. /**
  151. * Updates the next block according to the cipher mode.
  152. *
  153. * @param input the buffer to read from.
  154. */
  155. BlockCipher.prototype.update = function(input) {
  156. if(input) {
  157. // input given, so empty it into the input buffer
  158. this._input.putBuffer(input);
  159. }
  160. // do cipher operation until it needs more input and not finished
  161. while(!this._op.call(this.mode, this._input, this.output, this._finish) &&
  162. !this._finish) {}
  163. // free consumed memory from input buffer
  164. this._input.compact();
  165. };
  166. /**
  167. * Finishes encrypting or decrypting.
  168. *
  169. * @param pad a padding function to use in CBC mode, null for default,
  170. * signature(blockSize, buffer, decrypt).
  171. *
  172. * @return true if successful, false on error.
  173. */
  174. BlockCipher.prototype.finish = function(pad) {
  175. // backwards-compatibility w/deprecated padding API
  176. // Note: will overwrite padding functions even after another start() call
  177. if(pad && (this.mode.name === 'ECB' || this.mode.name === 'CBC')) {
  178. this.mode.pad = function(input) {
  179. return pad(this.blockSize, input, false);
  180. };
  181. this.mode.unpad = function(output) {
  182. return pad(this.blockSize, output, true);
  183. };
  184. }
  185. // build options for padding and afterFinish functions
  186. var options = {};
  187. options.decrypt = this._decrypt;
  188. // get # of bytes that won't fill a block
  189. options.overflow = this._input.length() % this.blockSize;
  190. if(!this._decrypt && this.mode.pad) {
  191. if(!this.mode.pad(this._input, options)) {
  192. return false;
  193. }
  194. }
  195. // do final update
  196. this._finish = true;
  197. this.update();
  198. if(this._decrypt && this.mode.unpad) {
  199. if(!this.mode.unpad(this.output, options)) {
  200. return false;
  201. }
  202. }
  203. if(this.mode.afterFinish) {
  204. if(!this.mode.afterFinish(this.output, options)) {
  205. return false;
  206. }
  207. }
  208. return true;
  209. };