diff --git a/index.js b/index.js index b316b73..da8a46e 100644 --- a/index.js +++ b/index.js @@ -1,19 +1,26 @@ var CryptoJS = require('crypto-js'); +var ProgressiveCryptor = require('./src/ProgressiveCryptor').default; var reduxPersist = require('redux-persist'); var stringify = require('json-stringify-safe'); var createTransform = reduxPersist.createTransform; -function createEncryptor(secretKey) { +function makeEncryptor(secretKey, progressive) { return function (state, key) { if (typeof state !== 'string') { state = stringify(state); } + if (progressive) { + new ProgressiveCryptor(state, secretKey).encrypt((encryptedState) => { + return encryptedState; + }); + } + return CryptoJS.AES.encrypt(state, secretKey).toString(); } } -function createDecryptor(secretKey) { +function makeDecryptor(secretKey, progressive) { return function (state, key) { if (typeof state !== 'string') { if (process.env.NODE_ENV !== 'production') { @@ -24,10 +31,16 @@ function createDecryptor(secretKey) { } try { - var bytes = CryptoJS.AES.decrypt(state, secretKey); - var newState = JSON.parse(bytes.toString(CryptoJS.enc.Utf8)); + if (progressive) { + new ProgressiveCryptor(state, secretKey).decrypt((decryptedState) => { + return JSON.parse(decryptedState.toString(CryptoJS.enc.Utf8)); + }); + } else { + var bytes = CryptoJS.AES.decrypt(state, secretKey); + var newState = JSON.parse(bytes.toString(CryptoJS.enc.Utf8)); - return newState; + return newState; + } } catch (err) { if (process.env.NODE_ENV !== 'production') { console.error(err); @@ -38,9 +51,18 @@ function createDecryptor(secretKey) { } } -module.exports = function (config) { - var inbound = createEncryptor(config.secretKey); - var outbound = createDecryptor(config.secretKey); +export function createEncryptor(config) { + var inbound = makeEncryptor(config.secretKey); + var outbound = makeDecryptor(config.secretKey); return createTransform(inbound, outbound, config); -}; +} + +export function createProgressiveEncryptor(config) { + var inbound = makeEncryptor(config.secretKey, true); + var outbound = makeDecryptor(config.secretKey, true); + + return createTransform(inbound, outbound, config); +} + +export default createEncryptor; diff --git a/package.json b/package.json index d8ec244..d8f2aa0 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "dependencies": { "crypto-js": "^3.1.6", "json-stringify-safe": "^5.0.1", + "readable-stream": "^2.1.5", "redux-persist": "^3.1.1" }, "devDependencies": { diff --git a/src/ProgressiveCryptor.js b/src/ProgressiveCryptor.js new file mode 100644 index 0000000..8cea45b --- /dev/null +++ b/src/ProgressiveCryptor.js @@ -0,0 +1,49 @@ +var CryptoJS = require('crypto-js'); +var Stream = require('readable-stream'); + +export default class ProgressiveCryptor { + constructor(state, secretKey) { + var salt = CryptoJS.lib.WordArray.random(8); + var cipher = CryptoJS.kdf.OpenSSL.execute(secretKey, 8, 4, salt); + + this.state = state; + this.key = CryptoJS.enc.Utf8.parse(secretKey); + + this.cryptorParams = { + iv: cipher.iv, + }; + } + + start() { + new Promise((resolve, reject) => { + try { + var stream = new Stream; + var processedState = ''; + + stream + .on('data', (data) => { + processedState += this.processor.process(data.toString()); + }) + .on('end', () => { + processedState += this.processor.finalize(); + resolve(processedState); + }); + + stream.push(this.state); + stream.push(null); + } catch (err) { + reject(err); + } + }); + } + + encrypt() { + this.processor = CryptoJS.algo.AES.createEncryptor(this.key, this.cryptorParams); + this.start(); + } + + decrypt() { + this.processor = CryptoJS.algo.AES.createDecryptor(this.key, this.cryptorParams); + this.start(); + } +} diff --git a/test/spec.js b/test/spec.js index 9463fd0..9c65272 100644 --- a/test/spec.js +++ b/test/spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -const createEncryptor = require('../'); +import createEncryptor, { createProgressiveEncryptor } from '../'; describe('redux-persist-transform-encrypt', () => { it('can encrypt incoming state', () => { @@ -33,4 +33,36 @@ describe('redux-persist-transform-encrypt', () => { expect(newState).to.eql(state); }); + + it('can encrypt incoming state progressively', () => { + const encryptTransform = createProgressiveEncryptor({ + secretKey: 'redux-is-awesome' + }); + + const key = 'testState'; + const state = { + foo: 'bar' + }; + + const newState = encryptTransform.in(state, key); + + expect(newState).to.be.a('string'); + expect(newState).to.not.eql(state); + }); + + it.skip('can decrypt outgoing state progressively', () => { + const encryptTransform = createProgressiveEncryptor({ + secretKey: 'redux-is-awesome' + }); + + const key = 'testState'; + const state = { + foo: 'bar' + }; + + const encryptedState = encryptTransform.in(state, key); + const newState = encryptTransform.out(encryptedState, key); + + expect(newState).to.eql(state); + }); });