/*
* 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);
}
}
}