@midwayjs/cookies
Version:
midway cookies
146 lines • 5.45 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Keygrip = void 0;
const assert = require("assert");
const util_1 = require("util");
const crypto_1 = require("crypto");
const constantTimeCompare = require("scmp");
const debug = (0, util_1.debuglog)('midway:cookies');
const replacer = {
'/': '_',
'+': '-',
'=': '',
};
// patch from https://github.com/crypto-utils/keygrip
// encrypt code from https://github.com/btxtiger/encrypt-cookie/blob/master/src/cryptography.ts
class Keygrip {
constructor(keys) {
assert(Array.isArray(keys) && keys.length, 'keys must be provided and should be an array');
this.keys = keys;
this.hash = 'sha256';
}
// encrypt a message
encrypt(plainText, key) {
key = key || this.keys[0];
try {
plainText = String(plainText);
const algorithm = getAlgorithm();
// Generate random salt -> 64 bytes
const salt = (0, crypto_1.randomBytes)(64);
// Generate random initialization vector -> 16 bytes
const iv = (0, crypto_1.randomBytes)(16);
// Generate random count of iterations between 10.000 - 99.999 -> 5 bytes
const iterations = Math.floor(Math.random() * (99999 - 10000 + 1)) + 10000;
// Derive encryption key
const encryptionKey = deriveKeyFromPassword(key, salt, Math.floor(iterations * 0.47 + 1337));
// Create cipher
const cipher = (0, crypto_1.createCipheriv)(algorithm, encryptionKey, iv);
// Update the cipher with data to be encrypted and close cipher
const encryptedData = Buffer.concat([
cipher.update(plainText, 'utf8'),
cipher.final(),
]);
// Get authTag from cipher for decryption // 16 bytes
const authTag = cipher.getAuthTag();
// Join all data into single string, include requirements for decryption
const output = Buffer.concat([
salt,
iv,
authTag,
Buffer.from(iterations.toString()),
encryptedData,
]).toString('hex');
return getEncryptedPrefix() + output;
}
catch (err) {
debug('crypt error', err.stack);
return undefined;
}
}
// decrypt a single message
// returns false on bad decrypts
decrypt(cipherText, key) {
if (!key) {
// decrypt every key
const keys = this.keys;
for (let i = 0; i < keys.length; i++) {
const value = this.decrypt(cipherText, keys[i]);
if (value !== false)
return { value, index: i };
}
return false;
}
try {
const algorithm = getAlgorithm();
const cipherTextParts = cipherText.split(getEncryptedPrefix());
// If it's not encrypted by this, reject with undefined
if (cipherTextParts.length !== 2) {
// console.warn('Could not determine the beginning of the cipherText. Maybe not encrypted by this method.');
return void 0;
}
else {
cipherText = cipherTextParts[1];
}
const inputData = Buffer.from(cipherText, 'hex');
// Split cipherText into partials
const salt = inputData.slice(0, 64);
const iv = inputData.slice(64, 80);
const authTag = inputData.slice(80, 96);
const iterations = parseInt(inputData.slice(96, 101).toString('utf-8'), 10);
const encryptedData = inputData.slice(101);
// Derive key
const decryptionKey = deriveKeyFromPassword(key, salt, Math.floor(iterations * 0.47 + 1337));
// Create decipher
const decipher = (0, crypto_1.createDecipheriv)(algorithm, decryptionKey, iv);
decipher.setAuthTag(authTag);
// Decrypt data
return (decipher.update(encryptedData, 'binary', 'utf-8') +
decipher.final('utf-8'));
}
catch (err) {
debug('crypt error', err.stack);
return false;
}
}
sign(data, key) {
// default to the first key
key = key || this.keys[0];
return (0, crypto_1.createHmac)(this.hash, key)
.update(data)
.digest('base64')
.replace(/\/|\+|=/g, x => replacer[x]);
}
verify(data, digest) {
const keys = this.keys;
for (let i = 0; i < keys.length; i++) {
if (constantTimeCompare(Buffer.from(digest), Buffer.from(this.sign(data, keys[i])))) {
debug('data %s match key %s', data, keys[i]);
return i;
}
}
return -1;
}
}
exports.Keygrip = Keygrip;
/**
* Get encryption/decryption algorithm
*/
function getAlgorithm() {
return 'aes-256-gcm';
}
/**
* Get encrypted string prefix
*/
function getEncryptedPrefix() {
return 'enc::';
}
/**
* Derive 256 bit encryption key from password, using salt and iterations -> 32 bytes
* @param password
* @param salt
* @param iterations
*/
function deriveKeyFromPassword(password, salt, iterations) {
return (0, crypto_1.pbkdf2Sync)(password, salt, iterations, 32, 'sha512');
}
//# sourceMappingURL=keygrip.js.map