An update to @saptarshi-basu answer,
Buffer.slice
is now deprecated and replaced with Buffer.subarray
The salt
can also be added to the encrypted buffer array to help in the decryption process later
So a javascript
implementation becomes
//--------------------------------------------------
// INCLUDES
//--------------------------------------------------
const crypto = require('crypto');
const { Buffer } = require("buffer");
//--------------------------------------------------
//-----------------------------------------
// module exports
//-----------------------------------------
module.exports = { encryptVal,
decryptVal
};
//-----------------------------------------
//--------------------------------------------------
// CONSTANTS
//
//--------------------------------------------------
const ENCRYPTION_CONSTANTS = {
ALGORITHM: 'aes-256-gcm', //--the algorithm used for encryption
KEY_BYTE_LENGTH: 32, //--the length of the key used for encryption
IV_BYTE_LENGTH: 16, //--the length of the initialization vector
SALT_BYTE_LENGTH: 16, //--the length of the salt used for encryption
AUTH_TAG_BYTE_LENGTH: 16, //--the length of the authentication tag
INPUT_ENCODING: 'utf-8', //--the encoding of the input data
OUTPUT_ENCODING: 'base64url', //--the encoding of the output data in base64url (url/cookies friendly)
//OUTPUT_ENCODING: 'base64', //--the encoding of the output data in base64
//OUTPUT_ENCODING: 'hex', //--the encoding of the output data in hex
}
//--------------------------------------------------
//--------------------------------------------------
/**
* This function is use to generate a random key
* for the encryption process.
*
* @returns {Buffer} the generated random key
*/
async function getRandomKey() {
return crypto.randomBytes(ENCRYPTION_CONSTANTS.KEY_BYTE_LENGTH);
}
/**
* This function is use to generate a key base
* on the given password and salt for the
* encryption process.
*
* @returns {Buffer} the generated random key
*/
async function getKeyFromPassword(gPassword, gSalt){
return crypto.scryptSync(gPassword, gSalt, ENCRYPTION_CONSTANTS.KEY_BYTE_LENGTH);
}
/**
* This function is use to generate a random salt
* for the encryption process.
*
* @returns {Buffer} the generated random salt
*/
async function getSalt(){
return crypto.randomBytes(ENCRYPTION_CONSTANTS.SALT_BYTE_LENGTH);
}
/**
* This function is use to generate a random salt
* for the encryption process.
*
* @returns {Buffer} the generated random salt
*/
async function getInitializationVector(){
return crypto.randomBytes(ENCRYPTION_CONSTANTS.IV_BYTE_LENGTH);
}
/**
* This function is use to encrypt a given value using
* the given password.
*
* @param {Buffer} gVal the value to be encrypted
* @param {Buffer} gPassword the password to be used for encryption
*
* @returns {Buffer} the encrypted value
*/
async function encryptVal(gVal, gPassword){
try{
const algorithm = ENCRYPTION_CONSTANTS.ALGORITHM;
const iv = await getInitializationVector();
const salt = await getSalt();
const key = await getKeyFromPassword(gPassword, salt);
const cipher = crypto.createCipheriv(algorithm, key, iv, {
authTagLength: ENCRYPTION_CONSTANTS.AUTH_TAG_BYTE_LENGTH
});
const encryptedResults = Buffer.concat([cipher.update(gVal, ENCRYPTION_CONSTANTS.INPUT_ENCODING), cipher.final()]);
return Buffer.concat([iv, salt, encryptedResults, cipher.getAuthTag()])
.toString(ENCRYPTION_CONSTANTS.OUTPUT_ENCODING);
}catch(err){
//--log error to the system
const errMsg = '--->>ERROR: `encryptVal()` error: '+err;
console.log(errMsg);
}
}
/**
* This function is use to decrypt a given encrypted
* value using the given password.
*
* @param {Buffer} gEncryptedVal the value to be decrypted
* @param {Buffer} gPassword the password to be used for decryption
*
* @returns {Buffer} the decrypted value
*/
async function decryptVal(gEncryptedVal, gPassword){
try{
const algorithm = ENCRYPTION_CONSTANTS.ALGORITHM;
const encryptedBuffer = Buffer.from(gEncryptedVal, ENCRYPTION_CONSTANTS.OUTPUT_ENCODING);
const iv = encryptedBuffer.subarray(0, ENCRYPTION_CONSTANTS.IV_BYTE_LENGTH);
const salt = encryptedBuffer.subarray(ENCRYPTION_CONSTANTS.IV_BYTE_LENGTH, ENCRYPTION_CONSTANTS.IV_BYTE_LENGTH + ENCRYPTION_CONSTANTS.SALT_BYTE_LENGTH);
const encryptedData = encryptedBuffer.subarray((ENCRYPTION_CONSTANTS.IV_BYTE_LENGTH + ENCRYPTION_CONSTANTS.SALT_BYTE_LENGTH), -ENCRYPTION_CONSTANTS.AUTH_TAG_BYTE_LENGTH);
const authTag = encryptedBuffer.subarray(-ENCRYPTION_CONSTANTS.AUTH_TAG_BYTE_LENGTH);
const key = await getKeyFromPassword(gPassword, salt);
const decipher = crypto.createDecipheriv(algorithm, key, iv, {
authTagLength: ENCRYPTION_CONSTANTS.AUTH_TAG_BYTE_LENGTH
});
decipher.setAuthTag(authTag);
return Buffer.concat([decipher.update(encryptedData), decipher.final()])
.toString(ENCRYPTION_CONSTANTS.INPUT_ENCODING);
}catch(err){
//--log error to the system
const errMsg = '--->>ERROR: `decryptVal()` error: '+err;
console.log(errMsg);
}
}
You can try the functions above as:
//--test encrypt and decrypt helper functions
async function testEncryptDecrypt(){
const txt = 'Hello World';
const password = "opoo";
const encryptedTxt = await encryptVal(txt, password);
const decryptedTxt = await decryptVal(encryptedTxt, password);
console.log('-->>OUTPUT: the encrypted text is: '+encryptedTxt);
console.log('-->>OUTPUT: the decrypted text is: '+decryptedTxt);
}
testEncryptDecrypt();