egg-cookies
Version:
cookies module for egg
118 lines (100 loc) • 2.88 kB
JavaScript
'use strict';
const debug = require('util').debuglog('egg-cookies:keygrip');
const crypto = require('crypto');
const assert = require('assert');
const constantTimeCompare = require('scmp');
const KEY_LEN = 32;
const IV_SIZE = 16;
const passwordCache = new Map();
const replacer = {
'/': '_',
'+': '-',
'=': '',
};
// patch from https://github.com/crypto-utils/keygrip
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';
this.cipher = 'aes-256-cbc';
}
// encrypt a message
encrypt(data, key) {
key = key || this.keys[0];
const password = keyToPassword(key);
const cipher = crypto.createCipheriv(this.cipher, password.key, password.iv);
return crypt(cipher, data);
}
// decrypt a single message
// returns false on bad decrypts
decrypt(data, key) {
if (!key) {
// decrypt every key
const keys = this.keys;
for (let i = 0; i < keys.length; i++) {
const value = this.decrypt(data, keys[i]);
if (value !== false) return { value, index: i };
}
return false;
}
try {
const password = keyToPassword(key);
const cipher = crypto.createDecipheriv(this.cipher, password.key, password.iv);
return crypt(cipher, data);
} catch (err) {
debug('crypt error', err.stack);
return false;
}
}
sign(data, key) {
// default to the first key
key = key || this.keys[0];
return crypto
.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;
}
}
function crypt(cipher, data) {
const text = cipher.update(data, 'utf8');
const pad = cipher.final();
return Buffer.concat([ text, pad ]);
}
function keyToPassword(key) {
if (passwordCache.has(key)) {
return passwordCache.get(key);
}
// Simulate EVP_BytesToKey.
// see https://github.com/nodejs/help/issues/1673#issuecomment-503222925
const bytes = Buffer.alloc(KEY_LEN + IV_SIZE);
let lastHash = null,
nBytes = 0;
while (nBytes < bytes.length) {
const hash = crypto.createHash('md5');
if (lastHash) hash.update(lastHash);
hash.update(key);
lastHash = hash.digest();
lastHash.copy(bytes, nBytes);
nBytes += lastHash.length;
}
// Use these for decryption.
const password = {
key: bytes.slice(0, KEY_LEN),
iv: bytes.slice(KEY_LEN, bytes.length),
};
passwordCache.set(key, password);
return password;
}
module.exports = Keygrip;