diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index c849628..161abc5 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -35,7 +35,7 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install Dependencies - run: npm i -g npminstall && npminstall + run: npm i -g npminstall@5 && npminstall - name: Continuous Integration run: npm run ci diff --git a/lib/keygrip.js b/lib/keygrip.js index 62461a2..eaf3d98 100644 --- a/lib/keygrip.js +++ b/lib/keygrip.js @@ -4,6 +4,9 @@ const debug = require('debug')('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 = { '/': '_', @@ -25,7 +28,8 @@ class Keygrip { // encrypt a message encrypt(data, key) { key = key || this.keys[0]; - const cipher = crypto.createCipher(this.cipher, key); + const password = keyToPassword(key); + const cipher = crypto.createCipheriv(this.cipher, password.key, password.iv); return crypt(cipher, data); } @@ -43,7 +47,8 @@ class Keygrip { } try { - const cipher = crypto.createDecipher(this.cipher, key); + 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); @@ -80,4 +85,33 @@ function crypt(cipher, data) { 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; diff --git a/test/lib/keygrip.test.js b/test/lib/keygrip.test.js index 39ad073..18e85b1 100644 --- a/test/lib/keygrip.test.js +++ b/test/lib/keygrip.test.js @@ -2,6 +2,7 @@ const Keygrip = require('../../lib/keygrip'); const assert = require('assert'); +const crypto = require('crypto'); describe('test/lib/keygrip.test.js', () => { it('should throw without keys', () => { @@ -31,6 +32,18 @@ describe('test/lib/keygrip.test.js', () => { assert(newKeygrip.decrypt(encrypted) === false); }); + it('should decrypt key encrypted by createCipher without error', () => { + const keygrip = new Keygrip([ 'foo' ]); + const encrypted = keygrip.encrypt('hello'); + + const cipher = crypto.createCipher(keygrip.cipher, 'foo'); + const text = cipher.update('hello', 'utf8'); + const oldEncrypted = Buffer.concat([ text, cipher.final() ]); + + assert(encrypted.toString('hex') === oldEncrypted.toString('hex')); + assert(keygrip.decrypt(oldEncrypted).value.toString('utf-8') === 'hello'); + }); + it('should signed and verify success', () => { const keygrip = new Keygrip([ 'foo', 'bar' ]); const newKeygrip = new Keygrip([ 'another', 'foo' ]);