/* * Copyright 2018 Scytl Secure Electronic Voting SA * * All rights reserved * * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package */ /* jshint node:true */ 'use strict'; var validator = require('./input-validator'); var bitwise = require('scytl-bitwise'); var codec = require('scytl-codec'); var forge = require('node-forge'); module.exports = SymmetricCipher; /** * @class SymmetricCipher * @classdesc The symmetric cipher API. To instantiate this object, use the * method {@link SymmetricCryptographyService.newCipher}. * @hideconstructor * @param {Policy} * policy The cryptographic policy to use. * @param {SecureRandomService} * secureRandomService The secure random service to use. */ function SymmetricCipher(policy, secureRandomService) { var keyLengthBytes_ = policy.symmetric.cipher.algorithm.keyLengthBytes; var algorithm_ = policy.symmetric.cipher.algorithm.name; var tagLengthBytes_ = policy.symmetric.cipher.algorithm.tagLengthBytes; var ivLengthBytes_ = policy.symmetric.cipher.ivLengthBytes; var randomGenerator_ = secureRandomService.newRandomGenerator(); var initialized_ = false; var cipher_; /** * Initializes the symmetric cipher with the provided secret key. * * @function init * @memberof SymmetricCipher * @param {Uint8Array} * key The key with which to initialize the symmetric cipher. * @returns {SymmetricCipher} A reference to this object, to facilitate * method chaining. * @throws {Error} * If the input data validation fails. */ this.init = function(key) { checkInitData(key); cipher_ = forge.cipher.createCipher(algorithm_, codec.binaryEncode(key)); initialized_ = true; return this; }; /** * Symmetrically encrypts some data. Before using this method, the cipher * must have been initialized with a secret key, via the method * {@link SymmetricCipher.init}. * * @function encrypt * @memberof SymmetricCipher * @param {Uint8Array} * data The data to encrypt. NOTE: Data of type * string will be UTF-8 encoded. * @returns {Uint8Array} The bitwise concatenation of the initialization * vector and the encrypted data. * @throws {Error} * If the input data validation fails, the cipher was not * initialized or the encryption process fails. */ this.encrypt = function(data) { if (!initialized_) { throw new Error( 'Could not encrypt; Symmetric cipher was not initialized with any secret key'); } if (typeof data === 'string') { data = codec.utf8Encode(data); } validator.checkIsInstanceOf( data, Uint8Array, 'Uint8Array', 'Data to symmetrically encrypt'); try { var iv = codec.binaryEncode(randomGenerator_.nextBytes(ivLengthBytes_)); // Create a byte buffer for data. var dataBuffer = new forge.util.ByteBuffer(codec.binaryEncode(data)); // Only for the GCM mode var gcmAuthTagByteLength = tagLengthBytes_; if (typeof gcmAuthTagBitLength !== 'undefined') { cipher_.start({iv: iv, tagLength: gcmAuthTagByteLength}); } else { cipher_.start({iv: iv}); } cipher_.update(dataBuffer); cipher_.finish(); var encryptedData = cipher_.output.getBytes().toString(); if (typeof gcmAuthTagByteLength !== 'undefined') { var gcmAuthTag = cipher_.mode.tag.getBytes(); encryptedData = encryptedData + gcmAuthTag.toString(); } var initVectorAndEncryptedData = iv + encryptedData; return codec.binaryDecode(initVectorAndEncryptedData); } catch (error) { throw new Error( 'Data could not be symmetrically encrypted: ' + error.message); } }; /** * Symmetrically decrypts some data, using the initialization vector * provided with the encrypted data. Before using this method, the cipher * must have been initialized with a secret key, via the method * {@link SymmetricCipher.init}. * * @function decrypt * @memberof SymmetricCipher * @param {Uint8Array} * initVectorAndEncryptedData The bitwise concatenation of the * initialization vector and the encrypted data. * @returns {Uint8Array} The decrypted data. NOTE: To retrieve data * of type string, apply method * codec.utf8Decode to result. * @throws {Error} * If the input data validation or the decryption process fails. */ this.decrypt = function(initVectorAndEncryptedData) { if (!initialized_) { throw new Error( 'Could not decrypt; Symmetric cipher was not initialized with any secret key'); } validator.checkIsInstanceOf( initVectorAndEncryptedData, Uint8Array, 'Uint8Array', 'Concatenation of initialization vector and encrypted data to symmetrically decrypt'); try { var initVector = bitwise.slice(initVectorAndEncryptedData, 0, ivLengthBytes_); var encryptedData = bitwise.slice( initVectorAndEncryptedData, ivLengthBytes_, initVectorAndEncryptedData.length); // Only for the GCM mode var gcmAuthTagByteLength = tagLengthBytes_; if (typeof gcmAuthTagByteLength !== 'undefined') { var offset = encryptedData.length - gcmAuthTagByteLength; var gcmAuthTag = bitwise.slice(encryptedData, offset, encryptedData.length); encryptedData = bitwise.slice(encryptedData, 0, offset); var gcmAuthTagBitLength = gcmAuthTagByteLength * 8; cipher_.start({ iv: codec.binaryEncode(initVector), tagLength: gcmAuthTagBitLength, tag: codec.binaryEncode(gcmAuthTag) }); } else { cipher_.start({iv: initVector}); } var encryptedDataBuffer = new forge.util.ByteBuffer(codec.binaryEncode(encryptedData)); cipher_.update(encryptedDataBuffer); cipher_.finish(); var decryptedData = codec.binaryDecode(cipher_.output.getBytes().toString()); return decryptedData; } catch (error) { throw new Error('Data could not be symmetrically decrypted; ' + error); } }; function checkInitData(key) { validator.checkIsInstanceOf( key, Uint8Array, 'Uint8Array', 'Secret key with which to initialize symmetric cipher'); if (key.length !== keyLengthBytes_) { throw new Error( 'Expected secret key byte length ' + keyLengthBytes_ + ' ; Found: ' + key.length); } } }