```=javascript // Node.js core modules var crypto = require('crypto'); /** * The encryption algorithm (cipher) type to be used. * @type {String} * @const * @private */ var CIPHER_ALGORITHM = 'aes-256-cbc'; var SALT = Buffer.from([0x39, 0x30, 0x00, 0x00, 0x31, 0xd4, 0x00, 0x00]) function EVP_BytesToKey (password, salt, keyBits, ivLen) { if (!Buffer.isBuffer(password)) password = Buffer.from(password, 'binary') if (salt) { if (!Buffer.isBuffer(salt)) salt = Buffer.from(salt, 'binary') if (salt.length !== 8) throw new RangeError('salt should be Buffer with 8 byte length') } var keyLen = keyBits / 8 var key = Buffer.alloc(keyLen) var iv = Buffer.alloc(ivLen || 0) var tmp = Buffer.alloc(0) while (keyLen > 0 || ivLen > 0) { var hash = new crypto.createHash('sha256') hash.update(tmp) hash.update(password) if (salt) hash.update(salt) tmp = hash.digest() var used = 0 if (keyLen > 0) { var keyStart = key.length - keyLen used = Math.min(keyLen, tmp.length) tmp.copy(key, keyStart, 0, used) keyLen -= used } if (used < tmp.length && ivLen > 0) { var ivStart = iv.length - ivLen var length = Math.min(ivLen, tmp.length - used) tmp.copy(iv, ivStart, used, used + length) ivLen -= length } } tmp.fill(0) return { key: key, iv: iv } } // // Primary API // /** * An API to allow for greatly simplified AES-256 encryption and decryption using a passphrase of * any length plus a random Initialization Vector. * @exports aes256 * @public */ var aes256 = { /** * Encrypt a clear-text message using AES-256 plus a random Initialization Vector. * @param {String} key A passphrase of any length to used to generate a symmetric session key. * @param {String|Buffer} input The clear-text message or buffer to be encrypted. * @returns {String|Buffer} A custom-encrypted version of the input. * @public * @method */ encrypt: function(key, input) { if (typeof key !== 'string' || !key) { throw new TypeError('Provided "key" must be a non-empty string'); } var isString = typeof input === 'string'; var isBuffer = Buffer.isBuffer(input); if (!(isString || isBuffer) || (isString && !input) || (isBuffer && !Buffer.byteLength(input))) { throw new TypeError('Provided "input" must be a non-empty string or buffer'); } let evpKey = EVP_BytesToKey(Buffer.from(key, 'utf-8'), SALT, 256, 16); var cipher = crypto.createCipheriv(CIPHER_ALGORITHM, evpKey.key, evpKey.iv); var buffer = input; if (isString) { let prependZero = '0'; buffer = Buffer.concat([Buffer.from(prependZero.repeat(24 - input.length) + input, 'utf-8'), Buffer.from([ 0x00 ])]); } var ciphertext = cipher.update(buffer); var encrypted = Buffer.concat([ciphertext, cipher.final()]); if (isString) { encrypted = encrypted.toString('base64'); } return encrypted; }, /** * Decrypt an encrypted message back to clear-text using AES-256 plus a random Initialization Vector. * @param {String} key A passphrase of any length to used to generate a symmetric session key. * @param {String|Buffer} encrypted The encrypted message to be decrypted. * @returns {String|Buffer} The original plain-text message or buffer. * @public * @method */ decrypt: function(key, encrypted) { if (typeof key !== 'string' || !key) { throw new TypeError('Provided "key" must be a non-empty string'); } var isString = typeof encrypted === 'string'; var isBuffer = Buffer.isBuffer(encrypted); if (!(isString || isBuffer) || (isString && !encrypted) || (isBuffer && !Buffer.byteLength(encrypted))) { throw new TypeError('Provided "encrypted" must be a non-empty string or buffer'); } let evpKey = EVP_BytesToKey(Buffer.from(key, 'utf-8'), SALT, 256, 16); var input = encrypted; if (isString) { input = Buffer.from(encrypted, 'base64'); if (input.length < 17) { throw new TypeError('Provided "encrypted" must decrypt to a non-empty string or buffer'); } } else { if (Buffer.byteLength(encrypted) < 17) { throw new TypeError('Provided "encrypted" must decrypt to a non-empty string or buffer'); } } // Initialization Vector var decipher = crypto.createDecipheriv(CIPHER_ALGORITHM, evpKey.key, evpKey.iv); var ciphertext = input; var output; if (isString) { output = decipher.update(ciphertext) + decipher.final(); } else { output = Buffer.concat([decipher.update(ciphertext), decipher.final()]); } return output.slice(output.length - 9, -1); //remove 0x00 termination character and prepended zeros } }; /** * Create a symmetric cipher with a given passphrase to then encrypt/decrypt data symmetrically. * @param {String} key A passphrase of any length to used to generate a symmetric session key. * @public * @constructor */ function AesCipher(key) { if (typeof key !== 'string' || !key) { throw new TypeError('Provided "key" must be a non-empty string'); } /** * A passphrase of any length to used to generate a symmetric session key. * @member {String} key * @readonly */ Object.defineProperty(this, 'key', { value: key }); } /** * Encrypt a clear-text message using AES-256 plus a random Initialization Vector. * @param {String} plaintext The clear-text message to be encrypted. * @returns {String} A custom-encrypted version of the input. * @public * @method */ AesCipher.prototype.encrypt = function(plaintext) { return aes256.encrypt(this.key, plaintext); }; /** * Decrypt an encrypted message back to clear-text using AES-256 plus a random Initialization Vector. * @param {String} encrypted The encrypted message to be decrypted. * @returns {String} The original plain-text message. * @public * @method */ AesCipher.prototype.decrypt = function(encrypted) { return aes256.decrypt(this.key, encrypted); }; // // API Extension // /** * Create a symmetric cipher with a given passphrase to then encrypt/decrypt data symmetrically. * @param {String} key A passphrase of any length to used to generate a symmetric session key. * @returns {AesCipher} * @public * @method */ aes256.createCipher = function(key) { return new AesCipher(key); }; // // Export the API // ```