Skip to content

Instantly share code, notes, and snippets.

@Agoreddah
Last active April 19, 2024 21:28
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save Agoreddah/511864e2c00da064586523b3087c30e2 to your computer and use it in GitHub Desktop.
Save Agoreddah/511864e2c00da064586523b3087c30e2 to your computer and use it in GitHub Desktop.
Laravel encrypt & decrypt in Node.js
// load variables
require('dotenv').config();
// load dependencies
const crypto = require('crypto');
'use strict';
const APP_KEY = process.env.APP_KEY;
const SETTINGS = {
key : Buffer.from(APP_KEY.substring(7), 'base64'),
sha : 'sha256',
mode : 'AES-256-CBC'
}
/**
* Caclulate MAC.
* Paylod needs to be decoded to JSON with getJsonPayload(payload)
* @param {Object} payload with iv & value
* @param {String} key
*/
function calculateMac(payload, key){
let hashedData = hash(payload['iv'], payload['value'])
return hashHmac(hashedData, key);
}
/**
* Decrypts payload with master key
* @param {String} Payload - base64 encoded json with iv, value, mac information
*/
function decrypt(payload){
let _payload = getJsonPayload(payload);
let _iv = Buffer.from(_payload['iv'], 'base64');
let decipher = crypto.createDecipheriv(SETTINGS.mode, SETTINGS.key, _iv);
let decrypted = decipher.update(_payload['value'], 'base64', 'utf8');
decrypted += decipher.final('utf8');
return hashDeserialize(decrypted);
}
/**
* Create payload encrypted with master key.
* Payload contains: iv, value, mac
* @param {String} data to be encrypted
* @return {String} Base64 encdoded payload
*/
function encrypt(data){
let serializedValue = hashSerialize(data);
try{
let _iv = crypto.randomBytes(16);
let base64_iv = _iv.toString('base64');
let cipher = crypto.createCipheriv(SETTINGS.mode, SETTINGS.key, _iv);
let encrypted = cipher.update(serializedValue, 'utf8', 'base64');
encrypted += cipher.final('base64');
let _mac = hash(base64_iv, encrypted);
let payloadObject = {
'iv' : base64_iv,
'value' : encrypted,
'mac' : _mac
}
let _payload = JSON.stringify(payloadObject);
base64_payload = Buffer.from(_payload).toString('base64');
return base64_payload;
}
catch(e){
throw new Error('Cannot encrypt data provided !');
}
}
/**
* Get JSON object from payload.
* Payload needs to be base64 encoded and must contains iv, value, mac attributes.
* MAC is validated
* @param {String} payload
* @return {Object} Data with iv, value, mac
*/
function getJsonPayload(payload){
if(payload === undefined || payload === ''){
throw new Error('Payload MUST NOT be empty !');
}
if(typeof payload !== 'string'){
throw new Error('Payload MUST be string !');
}
try{
var _payload = JSON.parse(Buffer.from(payload, 'base64'));
}
catch(e){
throw new Error('Payload cannot be parsed !');
}
if(!isValidPayload(_payload)){
throw new Error('Payload is not valid !');
}
if(!isValidMac(_payload)){
throw new Error('Mac is not valid !');
}
return _payload;
}
/**
* Hash function.
* Combines initialization vector (iv) with data to be hashed (value).
* Uses master key to hash results
* @param {String} iv Initialization vector
* @param {String} value Data
*/
function hash(iv, value){
if(iv === undefined || iv === ''){
throw new Error('Iv is not defined !');
}
if(value === undefined || value === ''){
throw new Error('Value is not defined !');
}
let data = String(iv) + String(value);
return hashHmac(data, SETTINGS.key);
}
/**
* Crypto function to hash data with given key
* @param {String} data
* @param {String} key
*/
function hashHmac(data, key){
let hmac = crypto.createHmac(SETTINGS.sha, key);
hmac.update(data);
return hmac.digest('hex');
}
/**
* MAC validation function.
* Payload must be decoded to JSON
* @param {Object} payload
*/
function isValidMac(payload){
let bytes = crypto.randomBytes(16),
calculatedMac = calculateMac(payload, bytes);
let originalMac = hashHmac(payload['mac'], bytes);
return originalMac === calculatedMac;
}
/**
* Payload validation function.
* Payload must be decoded to JSON
* @param {Object} payload
*/
function isValidPayload(payload){
return (payload.hasOwnProperty('iv') && payload.hasOwnProperty('value') && payload.hasOwnProperty('mac'));
}
function hashDeserialize(data){
let str = String(data);
return str.substring( str.lastIndexOf(':') + 1, str.lastIndexOf(';') ).replace(/"/g,'');
}
function hashSerialize(data){
if(typeof data !== 'string'){
throw new Error('Data to be serialized must be type of string !');
}
let str = String(data);
return 's:'+str.length+':"'+str+';"';
}
module.exports = {
encrypt,
decrypt,
getJsonPayload,
hash
}
@gpotter2
Copy link

gpotter2 commented Mar 6, 2021

Hell that helped me. Thank you a lot, random internet stranger. Note

return 's:'+str.length+':"'+str+';"';

should be (; and " inverted)

return 's:'+str.length+':"'+str+'";';

In my case I had to make it work with laravel 4 so I actually had to change some of the crypto functions to use rijndael (https://gist.github.com/gpotter2/2eb1eae1ad18ab51186a71c0c7900ade)

@voidnerd
Copy link

voidnerd commented Mar 2, 2022

If you are decrypting a stringified JSON
change:

function hashDeserialize(data){
	let str = String(data);
	return str.substring( str.lastIndexOf(':') + 1, str.lastIndexOf(';') ).replace(/"/g,'');
}

to:

function hashDeserialize(data: any) {
    let str = String(data)
    return str.substring(str.indexOf(':', 2) + 2, str.lastIndexOf(';') - 1)
}

lastIndexOf(":") will not work for JSON strings as they have a lot more ":" than we are anticipating.

@charlie-nguyen-goldenowl-zz

awesome, thank you so much!

@ALTELMA
Copy link

ALTELMA commented Jul 21, 2022

Perfect!!! If anyone use custom encrypt/decrypt that not using APP_KEY

Example

$key = '12345678912345678974589632145698' // 16 for 128, 32 for 256
$decryptKey = new Encrypter($key, 'AES-256-CBC');
$decrypted = $decryptKey->decrypt($token);

You can change this a bit to this for SETTINGS

const SETTINGS = {
	key : Buffer.from(APP_KEY),
	sha : 'sha256',
	mode : 'AES-256-CBC'
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment