Spaces:
Configuration error
Configuration error
/** | |
* Cipher base API. | |
* | |
* @author Dave Longley | |
* | |
* Copyright (c) 2010-2014 Digital Bazaar, Inc. | |
*/ | |
var forge = require('./forge'); | |
require('./util'); | |
module.exports = forge.cipher = forge.cipher || {}; | |
// registered algorithms | |
forge.cipher.algorithms = forge.cipher.algorithms || {}; | |
/** | |
* Creates a cipher object that can be used to encrypt data using the given | |
* algorithm and key. The algorithm may be provided as a string value for a | |
* previously registered algorithm or it may be given as a cipher algorithm | |
* API object. | |
* | |
* @param algorithm the algorithm to use, either a string or an algorithm API | |
* object. | |
* @param key the key to use, as a binary-encoded string of bytes or a | |
* byte buffer. | |
* | |
* @return the cipher. | |
*/ | |
forge.cipher.createCipher = function(algorithm, key) { | |
var api = algorithm; | |
if(typeof api === 'string') { | |
api = forge.cipher.getAlgorithm(api); | |
if(api) { | |
api = api(); | |
} | |
} | |
if(!api) { | |
throw new Error('Unsupported algorithm: ' + algorithm); | |
} | |
// assume block cipher | |
return new forge.cipher.BlockCipher({ | |
algorithm: api, | |
key: key, | |
decrypt: false | |
}); | |
}; | |
/** | |
* Creates a decipher object that can be used to decrypt data using the given | |
* algorithm and key. The algorithm may be provided as a string value for a | |
* previously registered algorithm or it may be given as a cipher algorithm | |
* API object. | |
* | |
* @param algorithm the algorithm to use, either a string or an algorithm API | |
* object. | |
* @param key the key to use, as a binary-encoded string of bytes or a | |
* byte buffer. | |
* | |
* @return the cipher. | |
*/ | |
forge.cipher.createDecipher = function(algorithm, key) { | |
var api = algorithm; | |
if(typeof api === 'string') { | |
api = forge.cipher.getAlgorithm(api); | |
if(api) { | |
api = api(); | |
} | |
} | |
if(!api) { | |
throw new Error('Unsupported algorithm: ' + algorithm); | |
} | |
// assume block cipher | |
return new forge.cipher.BlockCipher({ | |
algorithm: api, | |
key: key, | |
decrypt: true | |
}); | |
}; | |
/** | |
* Registers an algorithm by name. If the name was already registered, the | |
* algorithm API object will be overwritten. | |
* | |
* @param name the name of the algorithm. | |
* @param algorithm the algorithm API object. | |
*/ | |
forge.cipher.registerAlgorithm = function(name, algorithm) { | |
name = name.toUpperCase(); | |
forge.cipher.algorithms[name] = algorithm; | |
}; | |
/** | |
* Gets a registered algorithm by name. | |
* | |
* @param name the name of the algorithm. | |
* | |
* @return the algorithm, if found, null if not. | |
*/ | |
forge.cipher.getAlgorithm = function(name) { | |
name = name.toUpperCase(); | |
if(name in forge.cipher.algorithms) { | |
return forge.cipher.algorithms[name]; | |
} | |
return null; | |
}; | |
var BlockCipher = forge.cipher.BlockCipher = function(options) { | |
this.algorithm = options.algorithm; | |
this.mode = this.algorithm.mode; | |
this.blockSize = this.mode.blockSize; | |
this._finish = false; | |
this._input = null; | |
this.output = null; | |
this._op = options.decrypt ? this.mode.decrypt : this.mode.encrypt; | |
this._decrypt = options.decrypt; | |
this.algorithm.initialize(options); | |
}; | |
/** | |
* Starts or restarts the encryption or decryption process, whichever | |
* was previously configured. | |
* | |
* For non-GCM mode, the IV may be a binary-encoded string of bytes, an array | |
* of bytes, a byte buffer, or an array of 32-bit integers. If the IV is in | |
* bytes, then it must be Nb (16) bytes in length. If the IV is given in as | |
* 32-bit integers, then it must be 4 integers long. | |
* | |
* Note: an IV is not required or used in ECB mode. | |
* | |
* For GCM-mode, the IV must be given as a binary-encoded string of bytes or | |
* a byte buffer. The number of bytes should be 12 (96 bits) as recommended | |
* by NIST SP-800-38D but another length may be given. | |
* | |
* @param options the options to use: | |
* iv the initialization vector to use as a binary-encoded string of | |
* bytes, null to reuse the last ciphered block from a previous | |
* update() (this "residue" method is for legacy support only). | |
* additionalData additional authentication data as a binary-encoded | |
* string of bytes, for 'GCM' mode, (default: none). | |
* tagLength desired length of authentication tag, in bits, for | |
* 'GCM' mode (0-128, default: 128). | |
* tag the authentication tag to check if decrypting, as a | |
* binary-encoded string of bytes. | |
* output the output the buffer to write to, null to create one. | |
*/ | |
BlockCipher.prototype.start = function(options) { | |
options = options || {}; | |
var opts = {}; | |
for(var key in options) { | |
opts[key] = options[key]; | |
} | |
opts.decrypt = this._decrypt; | |
this._finish = false; | |
this._input = forge.util.createBuffer(); | |
this.output = options.output || forge.util.createBuffer(); | |
this.mode.start(opts); | |
}; | |
/** | |
* Updates the next block according to the cipher mode. | |
* | |
* @param input the buffer to read from. | |
*/ | |
BlockCipher.prototype.update = function(input) { | |
if(input) { | |
// input given, so empty it into the input buffer | |
this._input.putBuffer(input); | |
} | |
// do cipher operation until it needs more input and not finished | |
while(!this._op.call(this.mode, this._input, this.output, this._finish) && | |
!this._finish) {} | |
// free consumed memory from input buffer | |
this._input.compact(); | |
}; | |
/** | |
* Finishes encrypting or decrypting. | |
* | |
* @param pad a padding function to use in CBC mode, null for default, | |
* signature(blockSize, buffer, decrypt). | |
* | |
* @return true if successful, false on error. | |
*/ | |
BlockCipher.prototype.finish = function(pad) { | |
// backwards-compatibility w/deprecated padding API | |
// Note: will overwrite padding functions even after another start() call | |
if(pad && (this.mode.name === 'ECB' || this.mode.name === 'CBC')) { | |
this.mode.pad = function(input) { | |
return pad(this.blockSize, input, false); | |
}; | |
this.mode.unpad = function(output) { | |
return pad(this.blockSize, output, true); | |
}; | |
} | |
// build options for padding and afterFinish functions | |
var options = {}; | |
options.decrypt = this._decrypt; | |
// get # of bytes that won't fill a block | |
options.overflow = this._input.length() % this.blockSize; | |
if(!this._decrypt && this.mode.pad) { | |
if(!this.mode.pad(this._input, options)) { | |
return false; | |
} | |
} | |
// do final update | |
this._finish = true; | |
this.update(); | |
if(this._decrypt && this.mode.unpad) { | |
if(!this.mode.unpad(this.output, options)) { | |
return false; | |
} | |
} | |
if(this.mode.afterFinish) { | |
if(!this.mode.afterFinish(this.output, options)) { | |
return false; | |
} | |
} | |
return true; | |
}; | |