├── .npmignore ├── screenshot.png ├── .babelrc ├── test ├── library.spec.js ├── mock │ └── ls.js ├── basic.spec.js ├── ls-data-compression.spec.js ├── localStorage.spec.js ├── standard.spec.js ├── utils.spec.js ├── functional.spec.js └── ls-data-enc-dec.spec.js ├── .travis.yml ├── example ├── base64-compressed.js ├── only-base64.js ├── aes-compressed.js ├── des-compressed.js ├── rc4-compressed.js ├── only-compressed.js ├── aes-uncompressed.js ├── des-uncompressed.js ├── rc4-uncompressed.js ├── rabbit-compressed.js ├── rabbit-uncompressed.js ├── standard.js ├── index.html └── vendor │ └── screenlog.min.js ├── .gitignore ├── src ├── constants.js ├── enc-utf8.js ├── WordArray.js ├── utils.js ├── Base64.js └── index.js ├── LICENSE ├── webpack.config.js ├── package.json ├── .eslintrc ├── README.md └── dist └── secure-ls.min.js /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | test 3 | node_modules 4 | webpack.config.js -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellohaptik/secure-ls/master/screenshot.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["babel-plugin-add-module-exports"] 4 | } -------------------------------------------------------------------------------- /test/library.spec.js: -------------------------------------------------------------------------------- 1 | import './basic.spec'; 2 | import './standard.spec'; 3 | import './localStorage.spec'; 4 | import './utils.spec'; 5 | import './ls-data-compression.spec'; 6 | import './ls-data-enc-dec.spec'; 7 | import './functional.spec'; -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4.0.0' 4 | before_install: 5 | - npm install -g webpack 6 | install: 7 | - npm install 8 | - npm run build-dev 9 | - npm run build 10 | script: 11 | - npm run build-test 12 | - npm run coverage 13 | after_success: 'npm run coveralls' 14 | cache: 15 | directories: 16 | - node_modules 17 | -------------------------------------------------------------------------------- /example/base64-compressed.js: -------------------------------------------------------------------------------- 1 | var key = 'base64__compressed'; 2 | var data = {data: [{age: 1}, {age: '2'}]}; 3 | var b_c = new SecureLS(); 4 | ae = b_c.AES.encrypt(JSON.stringify(data), 's3cr3t@123'); 5 | bde = b_c.AES.decrypt(ae.toString(), 's3cr3t@123'); 6 | de = bde.toString(b_c.enc._Utf8); 7 | 8 | b_c.set(key, data); 9 | console.log('Base64 Compressed'); 10 | console.log(localStorage.getItem(key)); 11 | console.log(b_c.get(key)); 12 | console.log('____________________________________') -------------------------------------------------------------------------------- /example/only-base64.js: -------------------------------------------------------------------------------- 1 | var key = 'only__base64'; 2 | var data = {data: [{age: 1}, {age: '2'}]}; 3 | var o_b = new SecureLS({isCompression: false}); 4 | ae = o_b.AES.encrypt(JSON.stringify(data), 's3cr3t@123'); 5 | bde = o_b.AES.decrypt(ae.toString(), 's3cr3t@123'); 6 | de = bde.toString(o_b.enc._Utf8); 7 | 8 | o_b.set(key, data); 9 | console.log('Only Base64, no compression'); 10 | console.log(localStorage.getItem(key)); 11 | console.log(o_b.get(key)); 12 | console.log('____________________________________') -------------------------------------------------------------------------------- /example/aes-compressed.js: -------------------------------------------------------------------------------- 1 | var key = 'aes__compressed'; 2 | var data = {data: [{age: 1}, {age: '2'}]}; 3 | var aes_c = new SecureLS({encodingType: 'aes', encryptionSecret: ''}); 4 | ae = aes_c.AES.encrypt(JSON.stringify(data), '') 5 | bde = aes_c.AES.decrypt(ae.toString(), '') 6 | de = bde.toString(aes_c.enc._Utf8) 7 | 8 | aes_c.set(key, data); 9 | console.log('AES Compressed'); 10 | console.log(localStorage.getItem(key)); 11 | console.log(aes_c.get(key)); 12 | console.log('____________________________________') -------------------------------------------------------------------------------- /example/des-compressed.js: -------------------------------------------------------------------------------- 1 | var key = 'des__compressed'; 2 | var data = {data: [{age: 1}, {age: '2'}]}; 3 | var des_c = new SecureLS({encodingType: 'des'}); 4 | ae = des_c.DES.encrypt(JSON.stringify(data), 's3cr3t@123'); 5 | bde = des_c.DES.decrypt(ae.toString(), 's3cr3t@123'); 6 | de = bde.toString(des_c.enc._Utf8); 7 | 8 | des_c.set(key, data); 9 | console.log('DES Compressed'); 10 | console.log(localStorage.getItem(key)); 11 | console.log(des_c.get(key)); 12 | console.log('____________________________________') -------------------------------------------------------------------------------- /example/rc4-compressed.js: -------------------------------------------------------------------------------- 1 | var key = 'rc4__compressed'; 2 | var data = {data: [{age: 1}, {age: '2'}]}; 3 | var rc4_c = new SecureLS({encodingType: 'rc4'}); 4 | ae = rc4_c.RC4.encrypt(JSON.stringify(data), 's3cr3t@123'); 5 | bde = rc4_c.RC4.decrypt(ae.toString(), 's3cr3t@123'); 6 | de = bde.toString(rc4_c.enc._Utf8); 7 | 8 | rc4_c.set(key, data); 9 | console.log('RC4 Compressed'); 10 | console.log(localStorage.getItem(key)); 11 | console.log(rc4_c.get(key)); 12 | console.log('____________________________________') -------------------------------------------------------------------------------- /example/only-compressed.js: -------------------------------------------------------------------------------- 1 | var key = 'only__compressed'; 2 | var data = {data: [{age: 1}, {age: '2'}]}; 3 | var o_c = new SecureLS({encodingType: ''}); 4 | ae = o_c.AES.encrypt(JSON.stringify(data), 's3cr3t@123'); 5 | bde = o_c.AES.decrypt(ae.toString(), 's3cr3t@123'); 6 | de = bde.toString(o_c.enc._Utf8); 7 | 8 | o_c.set(key, data); 9 | console.log('Only Compression, no encoding/encryption'); 10 | console.log(localStorage.getItem(key)); 11 | console.log(o_c.get(key)); 12 | console.log('____________________________________') -------------------------------------------------------------------------------- /example/aes-uncompressed.js: -------------------------------------------------------------------------------- 1 | var key = 'aes__uncompressed'; 2 | var data = {data: [{age: 1}, {age: '2'}]}; 3 | var aes_u = new SecureLS({encodingType: 'aes', isCompression: false}); 4 | ae = aes_u.AES.encrypt(JSON.stringify(data), 's3cr3t@123'); 5 | bde = aes_u.AES.decrypt(ae.toString(), 's3cr3t@123'); 6 | de = bde.toString(aes_u.enc._Utf8); 7 | 8 | aes_u.set(key, data); 9 | console.log('AES NOT Compressed'); 10 | console.log(localStorage.getItem(key)); 11 | console.log(aes_u.get(key)); 12 | console.log('____________________________________') -------------------------------------------------------------------------------- /example/des-uncompressed.js: -------------------------------------------------------------------------------- 1 | var key = 'des__uncompressed'; 2 | var data = {data: [{age: 1}, {age: '2'}]}; 3 | var des_u = new SecureLS({encodingType: 'des', isCompression: false}); 4 | ae = des_u.DES.encrypt(JSON.stringify(data), 's3cr3t@123'); 5 | bde = des_u.DES.decrypt(ae.toString(), 's3cr3t@123'); 6 | de = bde.toString(des_u.enc._Utf8); 7 | 8 | des_u.set(key, data); 9 | console.log('DES not Compressed'); 10 | console.log(localStorage.getItem(key)); 11 | console.log(des_u.get(key)); 12 | console.log('____________________________________') -------------------------------------------------------------------------------- /example/rc4-uncompressed.js: -------------------------------------------------------------------------------- 1 | var key = 'rc4__uncompressed'; 2 | var data = {data: [{age: 1}, {age: '2'}]}; 3 | var rc4_u = new SecureLS({encodingType: 'rc4', isCompression: false}); 4 | ae = rc4_u.RC4.encrypt(JSON.stringify(data), 's3cr3t@123'); 5 | bde = rc4_u.RC4.decrypt(ae.toString(), 's3cr3t@123'); 6 | de = bde.toString(rc4_u.enc._Utf8); 7 | 8 | rc4_u.set(key, data); 9 | console.log('RC4 not Compressed'); 10 | console.log(localStorage.getItem(key)); 11 | console.log(rc4_u.get(key)); 12 | console.log('____________________________________') -------------------------------------------------------------------------------- /example/rabbit-compressed.js: -------------------------------------------------------------------------------- 1 | var key = 'rabbit__compressed'; 2 | var data = {data: [{age: 1}, {age: '2'}]}; 3 | var rabbit_c = new SecureLS({encodingType: 'rabbit'}); 4 | ae = rabbit_c.RABBIT.encrypt(JSON.stringify(data), 's3cr3t@123'); 5 | bde = rabbit_c.RABBIT.decrypt(ae.toString(), 's3cr3t@123'); 6 | de = bde.toString(rabbit_c.enc._Utf8); 7 | 8 | rabbit_c.set(key, data); 9 | console.log('RABBIT Compressed'); 10 | console.log(localStorage.getItem(key)); 11 | console.log(rabbit_c.get(key)); 12 | console.log('____________________________________') -------------------------------------------------------------------------------- /example/rabbit-uncompressed.js: -------------------------------------------------------------------------------- 1 | var key = 'rabbit__uncompressed'; 2 | var data = {data: [{age: 1}, {age: '2'}]}; 3 | var rabbit_u = new SecureLS({encodingType: 'rabbit', isCompression: false}); 4 | ae = rabbit_u.RABBIT.encrypt(JSON.stringify(data), 's3cr3t@123'); 5 | bde = rabbit_u.RABBIT.decrypt(ae.toString(), 's3cr3t@123'); 6 | de = bde.toString(rabbit_u.enc._Utf8); 7 | 8 | rabbit_u.set(key, data); 9 | console.log('RABBIT not Compressed'); 10 | console.log(localStorage.getItem(key)); 11 | console.log(rabbit_u.get(key)); 12 | console.log('____________________________________') -------------------------------------------------------------------------------- /example/standard.js: -------------------------------------------------------------------------------- 1 | var key = 'standard'; 2 | var data = {data: [{age: 1}, {age: '2'}]}; 3 | var a = new SecureLS({encodingType: '', isCompression: false}); 4 | ae = a.AES.encrypt(JSON.stringify(data), 's3cr3t@123'); 5 | bde = a.AES.decrypt(ae.toString(), 's3cr3t@123'); 6 | de = bde.toString(a.enc._Utf8); 7 | 8 | a.set(key, data); 9 | console.log('____________________________________'); 10 | console.log('Standard Case: no compression, no encryption / encoding'); 11 | console.log(localStorage.getItem(key)); 12 | console.log(a.get(key)); 13 | console.log('____________________________________'); -------------------------------------------------------------------------------- /test/mock/ls.js: -------------------------------------------------------------------------------- 1 | // Storage Mock 2 | let mockLS = { 3 | storage: {} 4 | }; 5 | 6 | mockLS.storageMock = function () { 7 | return { 8 | setItem: (key, value) => { 9 | this.storage[key] = value || ''; 10 | }, 11 | getItem: (key) => { 12 | return this.storage[key] || null; 13 | }, 14 | removeItem: (key) => { 15 | delete this.storage[key]; 16 | }, 17 | get length() { 18 | return Object.keys(this.storage).length; 19 | }, 20 | key: (i) => { 21 | let keys = Object.keys(this.storage); 22 | 23 | return keys[i] || null; 24 | } 25 | }; 26 | }; 27 | module.exports = mockLS; 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | .coveralls.yml 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | let WarningEnum = { 2 | KEY_NOT_PROVIDED: 'keyNotProvided', 3 | META_KEY_REMOVE: 'metaKeyRemove', 4 | DEFAULT_TEXT: 'defaultText' 5 | }; 6 | 7 | let WarningTypes = {}; 8 | 9 | WarningTypes[WarningEnum.KEY_NOT_PROVIDED] = 'Secure LS: Key not provided. Aborting operation!'; 10 | WarningTypes[WarningEnum.META_KEY_REMOVE] = `Secure LS: Meta key can not be removed 11 | unless all keys created by Secure LS are removed!`; 12 | WarningTypes[WarningEnum.DEFAULT_TEXT] = `Unexpected output`; 13 | 14 | let constants = { 15 | WarningEnum: WarningEnum, 16 | WarningTypes: WarningTypes, 17 | EncrytionTypes: { 18 | BASE64: 'base64', 19 | AES: 'aes', 20 | DES: 'des', 21 | RABBIT: 'rabbit', 22 | RC4: 'rc4' 23 | } 24 | }; 25 | 26 | module.exports = constants; 27 | -------------------------------------------------------------------------------- /src/enc-utf8.js: -------------------------------------------------------------------------------- 1 | /* 2 | ES6 compatible port of CryptoJS - encoding 3 | 4 | Source: https://github.com/brix/crypto-js 5 | LICENSE: MIT 6 | */ 7 | let enc = {}; 8 | 9 | enc.Latin1 = { 10 | stringify: (wordArray) => { 11 | // Shortcuts 12 | let words = wordArray.words; 13 | let sigBytes = wordArray.sigBytes; 14 | let latin1Chars = [], i, bite; 15 | 16 | // Convert 17 | for (i = 0; i < sigBytes; i++) { 18 | bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; 19 | latin1Chars.push(String.fromCharCode(bite)); 20 | } 21 | 22 | return latin1Chars.join(''); 23 | } 24 | }; 25 | 26 | enc._Utf8 = { 27 | stringify: (wordArray) => { 28 | try { 29 | return decodeURIComponent(escape(enc.Latin1.stringify(wordArray))); 30 | } catch (e) { 31 | throw new Error('Malformed UTF-8 data'); 32 | } 33 | } 34 | }; 35 | 36 | module.exports = enc; 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Varun Malhotra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/WordArray.js: -------------------------------------------------------------------------------- 1 | /* 2 | ES6 compatible port of CryptoJS - WordArray for PBKDF2 password key generation 3 | 4 | Source: https://github.com/brix/crypto-js 5 | LICENSE: MIT 6 | */ 7 | 8 | let CryptoJSWordArray = {}; 9 | 10 | CryptoJSWordArray.random = function (nBytes) { 11 | let words = []; 12 | let r = (function (mw) { 13 | let mz = 0x3ade68b1; 14 | let mask = 0xffffffff; 15 | 16 | return function () { 17 | mz = (0x9069 * (mz & 0xFFFF) + (mz >> 0x10)) & mask; 18 | mw = (0x4650 * (mw & 0xFFFF) + (mw >> 0x10)) & mask; 19 | let result = ((mz << 0x10) + mw) & mask; 20 | 21 | result /= 0x100000000; 22 | result += 0.5; 23 | return result * (Math.random() > 0.5 ? 1 : -1); 24 | }; 25 | }); 26 | 27 | for (let i = 0, rcache; i < nBytes; i += 4) { 28 | let _r = r((rcache || Math.random()) * 0x100000000); 29 | 30 | rcache = _r() * 0x3ade67b7; 31 | words.push((_r() * 0x100000000) | 0); 32 | } 33 | 34 | return new this.Set(words, nBytes); 35 | }; 36 | 37 | CryptoJSWordArray.Set = function (words, sigBytes) { 38 | words = this.words = words || []; 39 | 40 | if (sigBytes !== undefined) { 41 | this.sigBytes = sigBytes; 42 | } else { 43 | this.sigBytes = words.length * 8; 44 | } 45 | }; 46 | 47 | module.exports = CryptoJSWordArray; 48 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin; 3 | var path = require('path'); 4 | var env = require('yargs').argv.mode; 5 | 6 | var libraryName = 'secure-ls'; 7 | 8 | var plugins = [], outputFile; 9 | 10 | if (env === 'build') { 11 | plugins.push(new UglifyJsPlugin({ minimize: true })); 12 | outputFile = libraryName + '.min.js'; 13 | } else { 14 | outputFile = libraryName + '.js'; 15 | } 16 | 17 | var config = { 18 | entry: __dirname + '/src/index.js', 19 | devtool: 'source-map', 20 | output: { 21 | path: __dirname + '/dist', 22 | filename: outputFile, 23 | library: 'SecureLS', 24 | libraryTarget: 'umd', 25 | umdNamedDefine: true 26 | }, 27 | module: { 28 | loaders: [ 29 | { 30 | test: /(\.jsx|\.js)$/, 31 | loader: 'babel', 32 | exclude: /(node_modules|bower_components)/ 33 | }, 34 | { 35 | test: /(\.jsx|\.js)$/, 36 | loader: "eslint-loader", 37 | exclude: /node_modules/ 38 | } 39 | ] 40 | }, 41 | eslint: { 42 | failOnWarning: false, 43 | failOnError: false 44 | }, 45 | resolve: { 46 | root: path.resolve('./src'), 47 | extensions: ['', '.js'] 48 | }, 49 | plugins: plugins 50 | }; 51 | 52 | module.exports = config; 53 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |{"data":[{"age":1},{"age":"2"}]}
15 | Output: {"data":[{"age":1},{"age":"2"}]}
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "secure-ls",
3 | "version": "1.2.1",
4 | "description": "Secure localStorage data with high level of encryption and data compression",
5 | "main": "dist/secure-ls.js",
6 | "scripts": {
7 | "build": "webpack --mode=build",
8 | "build-dev": "webpack --mode=dev",
9 | "build-test": "mocha -r mock-local-storage --compilers js:babel-core/register --colors ./test/library.spec.js",
10 | "dev": "webpack --progress --colors --watch --mode=dev",
11 | "test": "mocha -r mock-local-storage --compilers js:babel-core/register --colors -w ./test/library.spec.js",
12 | "coverage": "node node_modules/.bin/istanbul cover _mocha -- -r mock-local-storage --compilers js:babel-core/register -R spec ./test/library.spec.js",
13 | "coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls"
14 | },
15 | "devDependencies": {
16 | "babel": "6.3.13",
17 | "babel-core": "6.1.18",
18 | "babel-eslint": "5.0.0",
19 | "babel-loader": "6.1.0",
20 | "babel-plugin-add-module-exports": "0.1.2",
21 | "babel-preset-es2015": "6.3.13",
22 | "chai": "3.4.1",
23 | "coveralls": "^2.11.16",
24 | "eslint": "1.7.2",
25 | "eslint-loader": "1.1.0",
26 | "istanbul": "1.0.0-alpha.2",
27 | "mocha": "2.3.4",
28 | "mock-local-storage": "^1.0.2",
29 | "sinon": "^1.17.4",
30 | "sinon-chai": "^2.8.0",
31 | "webpack": "1.12.9",
32 | "yargs": "3.32.0"
33 | },
34 | "repository": {
35 | "type": "git",
36 | "url": "https://github.com/softvar/secure-ls.git"
37 | },
38 | "keywords": [
39 | "secure-ls",
40 | "localStorage",
41 | "encryption",
42 | "compression",
43 | "webpack",
44 | "es6",
45 | "umd",
46 | "commonjs"
47 | ],
48 | "author": "Varun Malhotra",
49 | "license": "MIT",
50 | "bugs": {
51 | "url": "https://github.com/softvar/secure-ls/issues"
52 | },
53 | "homepage": "https://github.com/softvar/secure-ls",
54 | "dependencies": {
55 | "crypto-js": "^3.1.6",
56 | "lz-string": "^1.4.4"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/test/basic.spec.js:
--------------------------------------------------------------------------------
1 | import chai from 'chai';
2 | import sinonChai from 'sinon-chai';
3 | import sinon from 'sinon';
4 | import SecureLS from '../dist/secure-ls.js';
5 |
6 | chai.expect();
7 | chai.use(sinonChai);
8 |
9 | const expect = chai.expect;
10 | let lib;
11 |
12 | describe('Basic suites ->', () => {
13 | before(() => {
14 |
15 | });
16 |
17 | describe('instance creation', () => {
18 | lib = new SecureLS();
19 |
20 | it('should check correct instance creation', () => {
21 | expect(lib).to.be.instanceof(SecureLS);
22 | });
23 | it('should return the name', () => {
24 | expect(lib._name).to.be.equal('secure-ls');
25 | });
26 |
27 | });
28 |
29 | describe('constructor', () => {
30 | lib = new SecureLS();
31 |
32 | it('should be called on instance creation', () => {
33 | expect(lib._name).to.exist;
34 | expect(lib.utils).to.exist;
35 | expect(lib.constants).to.exist;
36 | expect(lib.Base64).to.exist;
37 | expect(lib.LZString).to.exist;
38 | expect(lib.AES).to.exist;
39 | expect(lib.DES).to.exist;
40 | expect(lib.RABBIT).to.exist;
41 | expect(lib.RC4).to.exist;
42 | expect(lib.enc).to.exist;
43 | expect(lib.ls).to.exist;
44 | expect(lib.config).to.exist;
45 | expect(lib.config).to.be.an('object');
46 | expect(lib.config).to.include.keys('encodingType', 'isCompression');
47 | });
48 | it('should call init method', () => {
49 | let spy = sinon.spy(lib, 'init');
50 |
51 | // mock as if new instance is created but actually not
52 | // Can't expect otherwise Object reference would be lost
53 | expect(spy).to.not.be.called;
54 | lib.init();
55 | expect(spy).to.be.called;
56 | });
57 | it('should define enums, booleans and others', () => {
58 | expect(lib.WarningEnum).to.exist;
59 | expect(lib.WarningTypes).to.exist;
60 | expect(lib.EncrytionTypes).to.exist;
61 | expect(lib.utils.allKeys).to.exist;
62 | expect(lib.utils.allKeys).to.be.an('array');
63 | });
64 | });
65 |
66 | });
67 |
--------------------------------------------------------------------------------
/example/vendor/screenlog.min.js:
--------------------------------------------------------------------------------
1 | /*! screenlog - v0.2.2 - 2016-07-11
2 | * https://github.com/chinchang/screenlog.js
3 | * Copyright (c) 2016 Kushagra Gour; Licensed */
4 |
5 | !function(){function a(a,b){var c=document.createElement(a);return c.style.cssText=b,c}function b(){var b=a("div","z-index:2147483647;font-family:Helvetica,Arial,sans-serif;font-size:12px;font-weight:bold;padding:10px;text-align:left;opacity:0.8;position:fixed;min-width:400px;max-height:70vh;overflow:auto;background:"+_options.bgColor+";"+_options.css);return b}function c(b){return function(){var c=a("div","line-height:18px;min-height:18px;background:"+(o.children.length%2?"rgba(255,255,255,0.1)":"")+";color:"+b),d=[].slice.call(arguments).reduce(function(a,b){return a+" "+("object"==typeof b?JSON.stringify(b):b)},"");c.textContent=d,o.appendChild(c),_options.autoScroll&&(o.scrollTop=o.scrollHeight-o.clientHeight)}}function d(){o.innerHTML=""}function e(){return c(_options.logColor).apply(null,arguments)}function f(){return c(_options.infoColor).apply(null,arguments)}function g(){return c(_options.warnColor).apply(null,arguments)}function h(){return c(_options.errorColor).apply(null,arguments)}function i(a){for(var b in a)a.hasOwnProperty(b)&&_options.hasOwnProperty(b)&&(_options[b]=a[b])}function j(a){p||(p=!0,a&&i(a),o=b(),document.body.appendChild(o),_options.freeConsole||(q.log=console.log,q.clear=console.clear,q.info=console.info,q.warn=console.warn,q.error=console.error,console.log=n(e,"log"),console.clear=n(d,"clear"),console.info=n(f,"info"),console.warn=n(g,"warn"),console.error=n(h,"error")))}function k(){p=!1,console.log=q.log,console.clear=q.clear,console.info=q.info,console.warn=q.warn,console.error=q.error,o.remove()}function l(){if(!p)throw"You need to call `screenLog.init()` first."}function m(a){return function(){return l(),a.apply(this,arguments)}}function n(a,b){return function(){a.apply(this,arguments),"function"==typeof q[b]&&q[b].apply(console,arguments)}}var o,p=!1,q={};_options={bgColor:"black",logColor:"lightgreen",infoColor:"blue",warnColor:"orange",errorColor:"red",freeConsole:!1,css:"",autoScroll:!0},window.screenLog={init:j,log:n(m(e),"log"),clear:n(m(d),"clear"),info:n(m(d),"info"),warn:n(m(g),"warn"),error:n(m(h),"error"),destroy:m(k)}}();
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import constants from './constants';
2 | import CryptoJSWordArray from './WordArray';
3 | import PBKDF2 from 'crypto-js/pbkdf2';
4 |
5 | let utils = {
6 | metaKey: '_secure__ls__metadata',
7 | encryptionSecret: '',
8 | secretPhrase: 's3cr3t$#@135^&*246',
9 | allKeys: [],
10 | is: function (key) {
11 | if (key) {
12 | return true;
13 | }
14 | return false;
15 | },
16 | warn: function (reason) {
17 | reason = reason ? reason : constants.WarningEnum.DEFAULT_TEXT;
18 | console.warn(constants.WarningTypes[reason]);
19 | },
20 | generateSecretKey: function () {
21 | let salt = CryptoJSWordArray.random(128 / 8);
22 | let key128Bits = PBKDF2(this.secretPhrase, salt, {keySize: 128 / 32 });
23 |
24 | return key128Bits && key128Bits.toString();
25 | },
26 | getObjectFromKey: function (data, key) {
27 | if (!data || !data.length) {
28 | return {};
29 | }
30 |
31 | let i, obj = {};
32 |
33 | for (i = 0; i < data.length; i++) {
34 | if (data[i].k === key) {
35 | obj = data[i];
36 | break;
37 | }
38 | }
39 |
40 | return obj;
41 | },
42 | extractKeyNames: function (data) {
43 | if (!data || !data.keys || !data.keys.length) {
44 | return [];
45 | }
46 |
47 | return data.keys.map(keyData => {
48 | return keyData.k;
49 | });
50 | },
51 | getAllKeys: function () {
52 | return this.allKeys;
53 | },
54 | isKeyPresent: function (key) {
55 | let isKeyAlreadyPresent = false;
56 |
57 | for (let i = 0; i < this.allKeys.length; i++) {
58 | if (String(this.allKeys[i].k) === String(key)) {
59 | isKeyAlreadyPresent = true; // found
60 | break;
61 | }
62 | }
63 |
64 | return isKeyAlreadyPresent;
65 | },
66 | addToKeysList: function (key) {
67 | this.allKeys.push({
68 | k: key,
69 | s: this.encryptionSecret
70 | });
71 | },
72 | removeFromKeysList: function (key) {
73 | let i, index = -1;
74 |
75 | for (i = 0; i < this.allKeys.length; i++) {
76 | if (this.allKeys[i].k === key) {
77 | index = i;
78 | break;
79 | }
80 | }
81 | if (index !== -1) {
82 | this.allKeys.splice(index, 1);
83 | }
84 | return index;
85 | }
86 | };
87 |
88 | module.exports = utils;
89 |
--------------------------------------------------------------------------------
/src/Base64.js:
--------------------------------------------------------------------------------
1 | let Base64 = {
2 | _keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
3 | encode: function (e) {
4 | let t = '';
5 | let n, r, i, s, o, u, a;
6 | let f = 0;
7 |
8 | e = Base64._utf8Encode(e);
9 | while (f < e.length) {
10 | n = e.charCodeAt(f++);
11 | r = e.charCodeAt(f++);
12 | i = e.charCodeAt(f++);
13 | s = n >> 2;
14 | o = (n & 3) << 4 | r >> 4;
15 | u = (r & 15) << 2 | i >> 6;
16 | a = i & 63;
17 | if (isNaN(r)) {
18 | u = a = 64;
19 | } else if (isNaN(i)) {
20 | a = 64;
21 | }
22 | t = t + this._keyStr.charAt(s) + this._keyStr.charAt(o) + this._keyStr.charAt(u) + this._keyStr.charAt(a);
23 | }
24 | return t;
25 | },
26 | decode: function (e) {
27 | let t = '';
28 | let n, r, i;
29 | let s, o, u, a;
30 | let f = 0;
31 |
32 | e = e.replace(/[^A-Za-z0-9\+\/\=]/g, '');
33 | while (f < e.length) {
34 | s = this._keyStr.indexOf(e.charAt(f++));
35 | o = this._keyStr.indexOf(e.charAt(f++));
36 | u = this._keyStr.indexOf(e.charAt(f++));
37 | a = this._keyStr.indexOf(e.charAt(f++));
38 | n = s << 2 | o >> 4;
39 | r = (o & 15) << 4 | u >> 2;
40 | i = (u & 3) << 6 | a;
41 | t = t + String.fromCharCode(n);
42 | if (u !== 64) {
43 | t = t + String.fromCharCode(r);
44 | }
45 | if (a !== 64) {
46 | t = t + String.fromCharCode(i);
47 | }
48 | }
49 | t = Base64._utf8Decode(t);
50 | return t;
51 | },
52 | _utf8Encode: function (e) {
53 | e = e.replace(/\r\n/g, '\n');
54 | let t = '';
55 |
56 | for (let n = 0; n < e.length; n++) {
57 | let r = e.charCodeAt(n);
58 |
59 | if (r < 128) {
60 | t += String.fromCharCode(r);
61 | } else if (r > 127 && r < 2048) {
62 | t += String.fromCharCode(r >> 6 | 192);
63 | t += String.fromCharCode(r & 63 | 128);
64 | } else {
65 | t += String.fromCharCode(r >> 12 | 224);
66 | t += String.fromCharCode(r >> 6 & 63 | 128);
67 | t += String.fromCharCode(r & 63 | 128);
68 | }
69 | }
70 | return t;
71 | },
72 | _utf8Decode: function (e) {
73 | let t = '';
74 | let n = 0;
75 | let r, c2, c3;
76 |
77 | r = c2 = 0;
78 | while (n < e.length) {
79 | r = e.charCodeAt(n);
80 | if (r < 128) {
81 | t += String.fromCharCode(r);
82 | n++;
83 | } else if (r > 191 && r < 224) {
84 | c2 = e.charCodeAt(n + 1);
85 | t += String.fromCharCode((r & 31) << 6 | c2 & 63);
86 | n += 2;
87 | } else {
88 | c2 = e.charCodeAt(n + 1);
89 | c3 = e.charCodeAt(n + 2);
90 | t += String.fromCharCode((r & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
91 | n += 3;
92 | }
93 | }
94 | return t;
95 | }
96 | };
97 |
98 | module.exports = Base64;
99 |
--------------------------------------------------------------------------------
/test/ls-data-compression.spec.js:
--------------------------------------------------------------------------------
1 | import chai from 'chai';
2 | import SecureLS from '../dist/secure-ls.js';
3 | import mockLS from './mock/ls';
4 |
5 | const expect = chai.expect;
6 |
7 | chai.expect();
8 |
9 | describe('Data Compression Tests ->', () => {
10 | let mockStorage = mockLS.storageMock();
11 | let lib;
12 |
13 | beforeEach(() => {
14 | mockLS.storage = {};
15 | lib = new SecureLS();
16 | lib.ls = mockStorage;
17 | });
18 |
19 | afterEach(() => {
20 | lib.removeAll();
21 | });
22 |
23 | describe('no data compression but Base64 encoded', () => {
24 | it('should not compress data before storing to localStorage', () => {
25 | let valueStored;
26 | let data = [1, 2, 3];
27 | let key = 'key-1';
28 |
29 | lib = new SecureLS({isCompression: false});
30 | lib.ls = mockStorage;
31 | lib.set(key, data);
32 |
33 | // corresponding to [1, 2, 3] => WzEsMiwzXQ== i.e. Base64 encoded
34 | valueStored = lib.LZString.compressToUTF16(lib.Base64.encode(JSON.stringify(data)));
35 |
36 | expect(mockLS.storage[key]).to.exist;
37 | expect(mockLS.storage[key]).to.be.a('string');
38 | // important
39 | expect(mockLS.storage[key]).to.not.equal(valueStored);
40 |
41 | lib.removeAll();
42 | });
43 | });
44 |
45 | describe('no data compression and no encoding', () => {
46 | it('should not compress data before storing to localStorage', () => {
47 | let valueStored;
48 | let data = [1, 2, 3];
49 | let key = 'key-1';
50 |
51 | lib = new SecureLS({encodingType: '', isCompression: false});
52 | lib.ls = mockStorage;
53 | lib.set(key, data);
54 |
55 | // corresponding to [1, 2, 3] => "[1, 2, 3]"
56 | valueStored = JSON.stringify(data);
57 |
58 | expect(mockLS.storage[key]).to.exist;
59 | expect(mockLS.storage[key]).to.be.a('string');
60 | // important
61 | expect(mockLS.storage[key]).to.equal(valueStored);
62 |
63 | lib.removeAll();
64 | });
65 | });
66 |
67 | describe('data compression', () => {
68 | it('should compress data before storing to localStorage', () => {
69 | let valueStored;
70 | let data = [1, 2, 3];
71 | let key = 'key-1';
72 |
73 | lib.set(key, data);
74 |
75 | // corresponding to [1, 2, 3] => 㪂ೠ눉惮 脔ொꀀ
76 | valueStored = lib.LZString.compressToUTF16(lib.Base64.encode(JSON.stringify(data)));
77 |
78 | expect(mockLS.storage[key]).to.exist;
79 | expect(mockLS.storage[key]).to.be.a('string');
80 | // important
81 | expect(mockLS.storage[key]).to.equal(valueStored);
82 | });
83 |
84 | it('should compress data before storing to localStorage', () => {
85 | let valueStored;
86 | let data = {
87 | username: 'softvar',
88 | contact: 1234567890,
89 | hobbies: ['x', 'y', 'z']
90 | };
91 | let key = 'key-1';
92 |
93 | lib.set(key, data);
94 |
95 | // corresponding to [1, 2, 3] => ⦄ࣀ옄쁪‑腬ؠᜁ栙䂒ͥ쀻äʹ좀鑠ፀ൜Ұـ愰ʴ䘁堀斠ᵄ뽜鰃�ଠ՚䰀ι〈怜䀧ፚ저�舀郰Y馮ހ㎱्蠀
96 | valueStored = lib.LZString.compressToUTF16(lib.Base64.encode(JSON.stringify(data)));
97 |
98 | expect(mockLS.storage[key]).to.exist;
99 | expect(mockLS.storage[key]).to.be.a('string');
100 | // important
101 | expect(mockLS.storage[key]).to.equal(valueStored);
102 | });
103 | });
104 |
105 | });
106 |
--------------------------------------------------------------------------------
/test/localStorage.spec.js:
--------------------------------------------------------------------------------
1 | import chai from 'chai';
2 | import SecureLS from '../dist/secure-ls.js';
3 | import mockLS from './mock/ls';
4 |
5 | const expect = chai.expect;
6 | chai.expect();
7 |
8 | describe('LocalSorage API Tests ->', () => {
9 | let lib;
10 |
11 | beforeEach(function() {
12 | let mockStorage = mockLS.storageMock();
13 |
14 | lib = new SecureLS();
15 | lib.ls = mockStorage;
16 | });
17 |
18 | afterEach(function() {
19 | lib.removeAll();
20 | });
21 |
22 | describe('setItem method', () => {
23 | it('should set the value on key', () => {
24 | let data = [1, 2, 3];
25 | let key = 'key-1';
26 |
27 | lib.set(key, data);
28 |
29 | expect(mockLS.storage[key]).to.exist;
30 | expect(mockLS.storage[key]).to.be.a('string');
31 | });
32 | });
33 |
34 | describe('getItem method', () => {
35 | it('should return the value stored', () => {
36 | let value;
37 | let data = [1, 2, 3];
38 | let key = 'key-1';
39 |
40 | lib.set(key, data);
41 |
42 | expect(mockLS.storage[key]).to.exist;
43 | expect(mockLS.storage[key]).to.be.a('string');
44 |
45 | value = lib.get(key);
46 |
47 | expect(value).to.be.an('array');
48 | expect(value.length).to.equal(3);
49 | expect(value.toString()).to.equal(data.toString());
50 | });
51 | });
52 |
53 | describe('removeItem method', () => {
54 | it('should remove the key-value, if stored', () => {
55 | let value1, value2;
56 | let data = [1, 2, 3];
57 | let key1 = 'key-1', key2 = 'key-2';
58 |
59 | lib.set(key1, data);
60 | lib.set(key2, data);
61 |
62 | lib.remove(key1);
63 | value1 = lib.get(key1);
64 | expect(mockLS.storage[key1]).to.not.exist;
65 | expect(value1).to.not.be.an('array');
66 |
67 | value2 = lib.get(key2);
68 | expect(mockLS.storage[key2]).to.exist;
69 | expect(value2).to.be.an('array');
70 |
71 | lib.remove(key2);
72 | value1 = lib.get(key1);
73 | expect(mockLS.storage[key1]).to.not.exist;
74 | expect(value1).to.not.be.an('array');
75 |
76 | value2 = lib.get(key2);
77 | expect(mockLS.storage[key2]).to.not.exist;
78 | expect(value2).to.not.be.an('array');
79 | });
80 | });
81 |
82 | describe('setItem, getItem and removeItem in one go', () => {
83 | it('should set, get and remove', () => {
84 | let value1, value2;
85 | let data = [1, 2, 3];
86 | let key1 = 'key-1', key2 = 'key-2';
87 |
88 | lib.set(key1, data);
89 | expect(mockLS.storage[key1]).to.exist;
90 | expect(mockLS.storage[key1]).to.be.a('string');
91 |
92 | lib.set(key2, data);
93 | expect(mockLS.storage[key2]).to.exist;
94 | expect(mockLS.storage[key2]).to.be.a('string');
95 |
96 | value1 = lib.get(key1);
97 | expect(value1).to.be.an('array');
98 | expect(value1.length).to.equal(3);
99 | expect(value1.toString()).to.equal(data.toString());
100 |
101 | value2 = lib.get(key2);
102 | expect(value2).to.be.an('array');
103 | expect(value2.length).to.equal(3);
104 | expect(value2.toString()).to.equal(data.toString());
105 |
106 | lib.remove(key1);
107 | value1 = lib.get(key1);
108 | expect(mockLS.storage[key1]).to.not.exist;
109 | expect(value1).to.not.be.an('array');
110 |
111 | value2 = lib.get(key2);
112 | expect(value2).to.be.an('array');
113 | expect(value2.length).to.equal(3);
114 | expect(value2.toString()).to.equal(data.toString());
115 |
116 | lib.remove(key2);
117 | value1 = lib.get(key1);
118 | expect(mockLS.storage[key1]).to.not.exist;
119 | expect(value1).to.not.be.an('array');
120 |
121 | value2 = lib.get(key2);
122 | expect(mockLS.storage[key2]).to.not.exist;
123 | expect(value2).to.not.be.an('array');
124 | });
125 | });
126 | });
127 |
--------------------------------------------------------------------------------
/test/standard.spec.js:
--------------------------------------------------------------------------------
1 | import chai from 'chai';
2 | import sinonChai from 'sinon-chai';
3 | import sinon from 'sinon';
4 | import SecureLS from '../dist/secure-ls.js';
5 |
6 | chai.expect();
7 | chai.use(sinonChai);
8 |
9 | const expect = chai.expect;
10 | let lib;
11 |
12 | describe('Standard SecureLS API Tests ->', () => {
13 | beforeEach(() => {
14 | sinon.spy(console, 'warn');
15 | lib = new SecureLS();
16 | });
17 |
18 | afterEach(() => {
19 | console.warn.restore();
20 | lib.removeAll();
21 | });
22 |
23 | describe('secure-ls: set method', () => {
24 | it('should warn if no key is provided', () => {
25 | expect(console.warn).to.not.be.called;
26 | lib.set();
27 | expect(console.warn).to.be.called;
28 | });
29 |
30 | it('should add key to list of stored keys', () => {
31 | let spy = sinon.spy(lib, 'processData');
32 | let lsSpy = sinon.spy(lib, 'setDataToLocalStorage');
33 |
34 | lib.set('test123');
35 |
36 | expect(lib.utils.allKeys).to.exist;
37 | expect(lib.utils.allKeys).to.be.an('array');
38 | expect(lib.utils.allKeys.length).to.equal(1);
39 |
40 | expect(spy).to.be.called;
41 | expect(lsSpy).to.be.called;
42 | });
43 | });
44 |
45 | describe('secure-ls: get method', () => {
46 | it('should warn if no key is provided', () => {
47 | expect(console.warn).to.not.be.called;
48 | lib.get();
49 | expect(console.warn).to.be.called;
50 | });
51 |
52 | it('should add key to list of stored keys', () => {
53 | let lsSpy = sinon.spy(lib, 'getDataFromLocalStorage');
54 |
55 | lib.get('test123');
56 | expect(lsSpy).to.be.called;
57 | });
58 | });
59 |
60 | describe('secure-ls: getAllKeys method', () => {
61 | it('should return [] if nothing set', () => {
62 | let keys = lib.getAllKeys();
63 | expect(keys).to.be.an('array');
64 | expect(keys.length).to.equal(0);
65 | });
66 |
67 | it('should return keys when there are', () => {
68 | let keys = lib.getAllKeys();
69 | expect(keys.length).to.equal(0);
70 |
71 | lib.set('key-1');
72 |
73 | keys = lib.getAllKeys();
74 | expect(keys).to.be.an('array');
75 | expect(keys.length).to.equal(1);
76 |
77 | lib.set('key-2');
78 |
79 | keys = lib.getAllKeys();
80 | expect(keys).to.be.an('array');
81 | expect(keys.length).to.equal(2);
82 | });
83 | });
84 |
85 | describe('secure-ls: remove method', function () {
86 | it('should warn if no key is provided', () => {
87 | expect(console.warn).to.not.be.called;
88 | lib.remove();
89 | expect(console.warn).to.be.called;
90 | });
91 |
92 | it('should warn if key is metakey and keys are there', () => {
93 | lib.set('key-1');
94 | lib.remove('_secure__ls__metadata');
95 | expect(console.warn).to.be.called;
96 | // clear
97 | lib.removeAll();
98 | });
99 |
100 | it('should not warn if key is metadata and no ther keys present', () => {
101 | lib.remove('_secure__ls__metadata');
102 | expect(console.warn).to.not.be.called;
103 | });
104 |
105 | it('should decreament counter', () => {
106 | lib.set('key-1', {});
107 | lib.set('key-2', []);
108 | expect(lib.utils.allKeys.length).to.equal(2);
109 |
110 | lib.remove();
111 | expect(console.warn).to.be.called;
112 |
113 | lib.remove('key-2');
114 | expect(lib.utils.allKeys.length).to.equal(1);
115 |
116 | // verify no effect on applying same operation i.e. removing already deleted key
117 | lib.remove('key-2');
118 | expect(lib.utils.allKeys.length).to.equal(1);
119 |
120 | lib.remove('key-1');
121 | expect(lib.utils.allKeys.length).to.equal(0);
122 | });
123 |
124 | it('should update the list of stored keys', () => {
125 | let spy = sinon.spy(lib, 'setMetaData');
126 | lib.set('key-1');
127 | lib.remove('key-1');
128 | expect(spy).to.be.called;
129 | });
130 | });
131 |
132 | describe('secure-ls: removeAll method', function () {
133 | it('verify allKeys length on removal', () => {
134 | let spy = sinon.spy(lib, 'getAllKeys');
135 | lib.set('key-1', {data: 'data'});
136 | lib.set('key-2', [1, 2, 3])
137 |
138 | expect(lib.utils.allKeys.length).to.equal(2);
139 |
140 | lib.removeAll();
141 | expect(spy).to.be.called;
142 | expect(lib.utils.allKeys.length).to.equal(0);
143 | });
144 | });
145 |
146 | describe('secure-ls: clear method', function () {
147 | it('verify allKeys length on removal', () => {
148 | lib.set('key-1', {data: 'data'});
149 | lib.set('key-2', [1, 2, 3])
150 |
151 | expect(lib.utils.allKeys.length).to.equal(2);
152 |
153 | lib.clear();
154 | expect(lib.utils.allKeys.length).to.equal(0);
155 | });
156 | });
157 |
158 | });
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "ecmaFeatures": {
3 | "globalReturn": true,
4 | "jsx": true,
5 | "modules": true
6 | },
7 |
8 | "env": {
9 | "browser": true,
10 | "es6": true,
11 | "node": true
12 | },
13 |
14 | "globals": {
15 | "document": false,
16 | "escape": false,
17 | "navigator": false,
18 | "unescape": false,
19 | "window": false,
20 | "describe": true,
21 | "before": true,
22 | "it": true,
23 | "expect": true,
24 | "sinon": true
25 | },
26 |
27 | "parser": "babel-eslint",
28 |
29 | "plugins": [
30 |
31 | ],
32 |
33 | "rules": {
34 | "block-scoped-var": 2,
35 | "brace-style": [2, "1tbs", { "allowSingleLine": true }],
36 | "camelcase": [2, { "properties": "always" }],
37 | "comma-dangle": [2, "never"],
38 | "comma-spacing": [2, { "before": false, "after": true }],
39 | "comma-style": [2, "last"],
40 | "complexity": 0,
41 | "consistent-return": 2,
42 | "consistent-this": 0,
43 | "curly": [2, "multi-line"],
44 | "default-case": 0,
45 | "dot-location": [2, "property"],
46 | "dot-notation": 0,
47 | "eol-last": 2,
48 | "eqeqeq": [2, "allow-null"],
49 | "func-names": 0,
50 | "func-style": 0,
51 | "generator-star-spacing": [2, "both"],
52 | "guard-for-in": 0,
53 | "handle-callback-err": [2, "^(err|error|anySpecificError)$" ],
54 | "indent": [2, 2, { "SwitchCase": 1 }],
55 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
56 | "linebreak-style": 0,
57 | "max-depth": 0,
58 | "max-len": [2, 120, 4],
59 | "max-nested-callbacks": 0,
60 | "max-params": 0,
61 | "max-statements": 0,
62 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
63 | "newline-after-var": [2, "always"],
64 | "new-parens": 2,
65 | "no-alert": 0,
66 | "no-array-constructor": 2,
67 | "no-bitwise": 0,
68 | "no-caller": 2,
69 | "no-catch-shadow": 0,
70 | "no-cond-assign": 2,
71 | "no-console": 0,
72 | "no-constant-condition": 0,
73 | "no-continue": 0,
74 | "no-control-regex": 2,
75 | "no-debugger": 2,
76 | "no-delete-var": 2,
77 | "no-div-regex": 0,
78 | "no-dupe-args": 2,
79 | "no-dupe-keys": 2,
80 | "no-duplicate-case": 2,
81 | "no-else-return": 2,
82 | "no-empty": 0,
83 | "no-empty-character-class": 2,
84 | "no-empty-label": 2,
85 | "no-eq-null": 0,
86 | "no-eval": 2,
87 | "no-ex-assign": 2,
88 | "no-extend-native": 2,
89 | "no-extra-bind": 2,
90 | "no-extra-boolean-cast": 2,
91 | "no-extra-parens": 0,
92 | "no-extra-semi": 0,
93 | "no-extra-strict": 0,
94 | "no-fallthrough": 2,
95 | "no-floating-decimal": 2,
96 | "no-func-assign": 2,
97 | "no-implied-eval": 2,
98 | "no-inline-comments": 0,
99 | "no-inner-declarations": [2, "functions"],
100 | "no-invalid-regexp": 2,
101 | "no-irregular-whitespace": 2,
102 | "no-iterator": 2,
103 | "no-label-var": 2,
104 | "no-labels": 2,
105 | "no-lone-blocks": 0,
106 | "no-lonely-if": 0,
107 | "no-loop-func": 0,
108 | "no-mixed-requires": 0,
109 | "no-mixed-spaces-and-tabs": [2, false],
110 | "no-multi-spaces": 2,
111 | "no-multi-str": 2,
112 | "no-multiple-empty-lines": [2, { "max": 1 }],
113 | "no-native-reassign": 2,
114 | "no-negated-in-lhs": 2,
115 | "no-nested-ternary": 0,
116 | "no-new": 2,
117 | "no-new-func": 2,
118 | "no-new-object": 2,
119 | "no-new-require": 2,
120 | "no-new-wrappers": 2,
121 | "no-obj-calls": 2,
122 | "no-octal": 2,
123 | "no-octal-escape": 2,
124 | "no-path-concat": 0,
125 | "no-plusplus": 0,
126 | "no-process-env": 0,
127 | "no-process-exit": 0,
128 | "no-proto": 2,
129 | "no-redeclare": 2,
130 | "no-regex-spaces": 2,
131 | "no-reserved-keys": 0,
132 | "no-restricted-modules": 0,
133 | "no-return-assign": 2,
134 | "no-script-url": 0,
135 | "no-self-compare": 2,
136 | "no-sequences": 2,
137 | "no-shadow": 0,
138 | "no-shadow-restricted-names": 2,
139 | "no-spaced-func": 2,
140 | "no-sparse-arrays": 2,
141 | "no-sync": 0,
142 | "no-ternary": 0,
143 | "no-throw-literal": 2,
144 | "no-trailing-spaces": 2,
145 | "no-undef": 2,
146 | "no-undef-init": 2,
147 | "no-undefined": 0,
148 | "no-underscore-dangle": 0,
149 | "no-unneeded-ternary": 2,
150 | "no-unreachable": 2,
151 | "no-unused-expressions": 0,
152 | "no-unused-vars": [2, { "vars": "all", "args": "none" }],
153 | "no-use-before-define": 2,
154 | "no-var": 0,
155 | "no-void": 0,
156 | "no-warning-comments": 0,
157 | "no-with": 2,
158 | "one-var": 0,
159 | "operator-assignment": 0,
160 | "operator-linebreak": [2, "after"],
161 | "padded-blocks": 0,
162 | "quote-props": 0,
163 | "quotes": [2, "single", "avoid-escape"],
164 | "radix": 2,
165 | "semi": [2, "always"],
166 | "semi-spacing": 0,
167 | "sort-vars": 0,
168 | "space-after-keywords": [2, "always"],
169 | "space-before-blocks": [2, "always"],
170 | "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
171 | "space-in-brackets": 0,
172 | "space-in-parens": [2, "never"],
173 | "space-infix-ops": 2,
174 | "space-return-throw-case": 2,
175 | "space-unary-ops": [2, { "words": true, "nonwords": false }],
176 | "spaced-comment": [2, "always"],
177 | "strict": 0,
178 | "use-isnan": 2,
179 | "valid-jsdoc": 0,
180 | "valid-typeof": 2,
181 | "vars-on-top": 2,
182 | "wrap-iife": [2, "any"],
183 | "wrap-regex": 0,
184 | "yoda": [2, "never"]
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/test/utils.spec.js:
--------------------------------------------------------------------------------
1 | import chai from 'chai';
2 | import sinonChai from 'sinon-chai';
3 | import sinon from 'sinon';
4 | import SecureLS from '../dist/secure-ls.js';
5 |
6 | chai.expect();
7 | chai.use(sinonChai);
8 |
9 | const expect = chai.expect;
10 | let lib;
11 |
12 | describe('Utils tests', () => {
13 | beforeEach(() => {
14 | sinon.spy(console, 'warn');
15 | lib = new SecureLS();
16 | });
17 |
18 | afterEach(() => {
19 | console.warn.restore();
20 | lib.removeAll();
21 | });
22 |
23 | describe('variables initialization', () => {
24 | it('should verify meta-key', () => {
25 | expect(lib.utils.metaKey).to.be.a('string');
26 | expect(lib.utils.metaKey).to.equal('_secure__ls__metadata');
27 | });
28 | it('should verify encryptionSecret', () => {
29 | expect(lib.utils.encryptionSecret).to.be.a('string');
30 | expect(lib.utils.encryptionSecret).to.equal('');
31 | });
32 | it('should verify secretPhrase', () => {
33 | expect(lib.utils.secretPhrase).to.be.a('string');
34 | expect(lib.utils.secretPhrase).to.equal('s3cr3t$#@135^&*246');
35 | });
36 | it('should verify allKeys', () => {
37 | expect(lib.utils.allKeys).to.be.an('array');
38 | expect(lib.utils.allKeys.length).to.equal(0);
39 | });
40 | });
41 |
42 | describe('method: is ->', () => {
43 | it('return true if key is present', () => {
44 | let response;
45 |
46 | response = lib.utils.is('yes')
47 | expect(response).to.equal(true);
48 | });
49 | it('return false if key is present', () => {
50 | let response;
51 |
52 | response = lib.utils.is()
53 | expect(response).to.equal(false);
54 | });
55 | });
56 |
57 | describe('method: warn ->', () => {
58 | it('warn with default warning msg if no reason provided', () => {
59 | lib.utils.warn();
60 | expect(console.warn).to.be.called;
61 | expect(console.warn.calledWith('Unexpected output')).to.be.ok;
62 | });
63 | it('warn with undefined warning msg if wrong reason provided', () => {
64 | lib.utils.warn('wrong');
65 | expect(console.warn).to.be.called;
66 | expect(console.warn.calledWith('Unexpected output')).to.not.be.ok;
67 | });
68 | it('warn with warning msg as per reason provided', () => {
69 | lib.utils.warn('keyNotProvided');
70 | expect(console.warn).to.be.called;
71 | expect(console.warn.calledWith('Secure LS: Key not provided. Aborting operation!')).to.be.ok;
72 | });
73 |
74 | describe('method: generateSecretKey ->', () => {
75 | it('validate PBKDF2 key generated', () => {
76 | let encryptionKey = lib.utils.generateSecretKey()
77 |
78 | expect(encryptionKey).to.be.a('string');
79 | expect(encryptionKey.length).to.be.above(30);
80 | });
81 | });
82 |
83 | describe('method: getObjectFromKey ->', () => {
84 | it('if no data provided, return', () => {
85 | let response;
86 |
87 | response = lib.utils.getObjectFromKey();
88 | expect(response).to.be.an('object');
89 | expect(response).to.be.empty;
90 | });
91 |
92 | it('if data provided is empty array, return', () => {
93 | let response;
94 |
95 | response = lib.utils.getObjectFromKey([]);
96 | expect(response).to.be.an('object');
97 | expect(response).to.be.empty;
98 | });
99 |
100 | it('should return obj matching the key provided', () => {
101 | let response, key = 'name', data = [{
102 | k: 'name',
103 | test: 'case1'
104 | }, {
105 | k: 'age',
106 | test: 'case2'
107 | }];
108 |
109 | response = lib.utils.getObjectFromKey(data, key);
110 |
111 | expect(response).to.be.an('object');
112 | expect(response).to.not.be.empty;
113 | expect(response).to.include.keys('k');
114 | expect(response).to.include.keys('test');
115 | expect(response).to.equal(data[0]);
116 | });
117 | });
118 |
119 | describe('method: extractKeyNames ->', () => {
120 | it('should return just the `k` values', () => {
121 | let response, key = 'name', data = {
122 | keys: [{
123 | k: 'name',
124 | test: 'case1'
125 | }, {
126 | k: 'age',
127 | test: 'case2'
128 | }]
129 | };
130 |
131 | response = lib.utils.extractKeyNames(data);
132 |
133 | expect(response).to.be.an('array');
134 | expect(response).to.include('name');
135 | expect(response).to.not.include('test');
136 | });
137 | });
138 |
139 | describe('method: isKeyPresent ->', () => {
140 | it('should return the boolean based on key presence', () => {
141 | let response, key = 'name';
142 |
143 | lib.utils.allKeys = [{
144 | k: 'name',
145 | test: 'case1'
146 | }, {
147 | k: 'age',
148 | test: 'case2'
149 | }];
150 |
151 | response = lib.utils.isKeyPresent(key);
152 |
153 | expect(response).to.be.a('boolean');
154 | expect(response).to.equal(true);
155 |
156 | response = lib.utils.isKeyPresent('wrong-key');
157 |
158 | expect(response).to.be.a('boolean');
159 | expect(response).to.equal(false);
160 | });
161 | });
162 |
163 | describe('method: removeFromKeysList ->', () => {
164 | it('should remove object from array if key matches', () => {
165 | let response;
166 |
167 | lib.utils.allKeys = [{
168 | k: 'name',
169 | test: 'case1'
170 | }, {
171 | k: 'age',
172 | test: 'case2'
173 | }];
174 |
175 | expect(lib.utils.allKeys.length).to.equal(2);
176 |
177 | // length should dec by 1
178 | response = lib.utils.removeFromKeysList('name');
179 | expect(lib.utils.allKeys.length).to.equal(1);
180 |
181 | // length should not change
182 | response = lib.utils.removeFromKeysList('wrong-key');
183 | expect(lib.utils.allKeys.length).to.equal(1);
184 |
185 | // length should be 0
186 | response = lib.utils.removeFromKeysList('age');
187 | expect(lib.utils.allKeys.length).to.equal(0);
188 | });
189 | });
190 | });
191 | });
--------------------------------------------------------------------------------
/test/functional.spec.js:
--------------------------------------------------------------------------------
1 | import chai from 'chai';
2 | import sinonChai from 'sinon-chai';
3 | import sinon from 'sinon';
4 | import SecureLS from '../dist/secure-ls.js';
5 |
6 | chai.expect();
7 | chai.use(sinonChai);
8 |
9 | const expect = chai.expect;
10 | let lib;
11 |
12 | describe('Functional tests', () => {
13 | beforeEach(() => {
14 | lib = new SecureLS();
15 | });
16 |
17 | afterEach(() => {
18 | lib.removeAll();
19 | });
20 |
21 | describe('Config test: is Base64 encoding', () => {
22 | it('should verify encryption type with data encryption', () => {
23 | lib = new SecureLS();
24 | expect(lib._isBase64EncryptionType()).to.equal(true);
25 | expect(lib._isDataCompressionEnabled()).to.equal(true);
26 | });
27 | it('should verify encryption type with no data compression', () => {
28 | lib = new SecureLS({isCompression: false});
29 | expect(lib._isBase64EncryptionType()).to.equal(true);
30 | expect(lib._isDataCompressionEnabled()).to.equal(false);
31 | });
32 | });
33 |
34 | describe('Config test: is AES encryption', () => {
35 | it('should verify encryption type with data encryption', () => {
36 | lib = new SecureLS({encodingType: 'aes'});
37 | expect(lib._isAESEncryptionType()).to.equal(true);
38 | expect(lib._isDataCompressionEnabled()).to.equal(true);
39 | });
40 | it('should verify encryption type with no data compression', () => {
41 | lib = new SecureLS({encodingType: 'aes', isCompression: false});
42 | expect(lib._isAESEncryptionType()).to.equal(true);
43 | expect(lib._isDataCompressionEnabled()).to.equal(false);
44 | });
45 | });
46 |
47 | describe('Config test: is DES encryption', () => {
48 | it('should verify encryption type with data encryption', () => {
49 | lib = new SecureLS({encodingType: 'des'});
50 | expect(lib._isDESEncryptionType()).to.equal(true);
51 | expect(lib._isDataCompressionEnabled()).to.equal(true);
52 | });
53 | it('should verify encryption type with no data compression', () => {
54 | lib = new SecureLS({encodingType: 'des', isCompression: false});
55 | expect(lib._isDESEncryptionType()).to.equal(true);
56 | expect(lib._isDataCompressionEnabled()).to.equal(false);
57 | });
58 | });
59 |
60 | describe('Config test: is RABBIT encryption', () => {
61 | it('should verify encryption type with data encryption', () => {
62 | lib = new SecureLS({encodingType: 'rabbit'});
63 | expect(lib._isRabbitEncryptionType()).to.equal(true);
64 | expect(lib._isDataCompressionEnabled()).to.equal(true);
65 | });
66 | it('should verify encryption type with no data compression', () => {
67 | lib = new SecureLS({encodingType: 'rabbit', isCompression: false});
68 | expect(lib._isRabbitEncryptionType()).to.equal(true);
69 | expect(lib._isDataCompressionEnabled()).to.equal(false);
70 | });
71 | });
72 |
73 | describe('Config test: is RC4 encryption', () => {
74 | it('should verify encryption type with data encryption', () => {
75 | lib = new SecureLS({encodingType: 'rc4'});
76 | expect(lib._isRC4EncryptionType()).to.equal(true);
77 | expect(lib._isDataCompressionEnabled()).to.equal(true);
78 | });
79 | it('should verify encryption type with no data compression', () => {
80 | lib = new SecureLS({encodingType: 'rc4', isCompression: false});
81 | expect(lib._isRC4EncryptionType()).to.equal(true);
82 | expect(lib._isDataCompressionEnabled()).to.equal(false);
83 | });
84 | });
85 |
86 | describe('processData: method', () => {
87 | it('should return if no data provided', () => {
88 | let spyOnLZStringCompress = sinon.spy(lib.LZString, 'compressToUTF16');
89 |
90 | lib.processData();
91 | expect(spyOnLZStringCompress).to.not.been.called;
92 |
93 | spyOnLZStringCompress.restore();
94 | });
95 |
96 | it('should call AES encrypt if encoding is AES', () => {
97 | lib = new SecureLS({encodingType: 'aes'});
98 |
99 | let spyOnLZStringCompress = sinon.spy(lib.LZString, 'compressToUTF16');
100 | let spyOnAESEncrypt = sinon.spy(lib.AES, 'encrypt');
101 | let spyOnRABBITEncrypt = sinon.spy(lib.RABBIT, 'encrypt');
102 | let data = {
103 | username: 'softvar',
104 | module: 'secure-ls',
105 | age: 1
106 | };
107 |
108 | lib.processData(data);
109 | expect(spyOnLZStringCompress).to.been.called;
110 | expect(spyOnAESEncrypt).to.been.called;
111 | expect(spyOnRABBITEncrypt).to.not.been.called;
112 |
113 | spyOnLZStringCompress.restore();
114 | spyOnAESEncrypt.restore();
115 | spyOnRABBITEncrypt.restore();
116 | });
117 |
118 | it('should call DES encrypt if encoding is DES', () => {
119 | lib = new SecureLS({encodingType: 'DES'});
120 |
121 | let spyOnLZStringCompress = sinon.spy(lib.LZString, 'compressToUTF16');
122 | let spyOnDESEncrypt = sinon.spy(lib.DES, 'encrypt');
123 | let spyOnRABBITEncrypt = sinon.spy(lib.RABBIT, 'encrypt');
124 | let data = {
125 | username: 'softvar',
126 | module: 'secure-ls',
127 | age: 1
128 | };
129 |
130 | lib.processData(data);
131 | expect(spyOnLZStringCompress).to.been.called;
132 | expect(spyOnDESEncrypt).to.been.called;
133 | expect(spyOnRABBITEncrypt).to.not.been.called;
134 |
135 | spyOnLZStringCompress.restore();
136 | spyOnDESEncrypt.restore();
137 | spyOnRABBITEncrypt.restore();
138 | });
139 |
140 | it('should call RABBIT encrypt if encoding is RABBIT', () => {
141 | lib = new SecureLS({encodingType: 'RABBIT'});
142 |
143 | let spyOnLZStringCompress = sinon.spy(lib.LZString, 'compressToUTF16');
144 | let spyOnRABBITEncrypt = sinon.spy(lib.RABBIT, 'encrypt');
145 | let spyOnAESEncrypt = sinon.spy(lib.AES, 'encrypt');
146 | let data = {
147 | username: 'softvar',
148 | module: 'secure-ls',
149 | age: 1
150 | };
151 |
152 | lib.processData(data);
153 | expect(spyOnLZStringCompress).to.been.called;
154 | expect(spyOnRABBITEncrypt).to.been.called;
155 | expect(spyOnAESEncrypt).to.not.been.called;
156 |
157 | spyOnLZStringCompress.restore();
158 | spyOnRABBITEncrypt.restore();
159 | spyOnAESEncrypt.restore();
160 | });
161 |
162 | it('should call RC4 encrypt if encoding is RC4', () => {
163 | lib = new SecureLS({encodingType: 'RC4'});
164 |
165 | let spyOnLZStringCompress = sinon.spy(lib.LZString, 'compressToUTF16');
166 | let spyOnRC4Encrypt = sinon.spy(lib.RC4, 'encrypt');
167 | let spyOnRABBITEncrypt = sinon.spy(lib.RABBIT, 'encrypt');
168 | let data = {
169 | username: 'softvar',
170 | module: 'secure-ls',
171 | age: 1
172 | };
173 |
174 | lib.processData(data);
175 | expect(spyOnLZStringCompress).to.been.called;
176 | expect(spyOnRC4Encrypt).to.been.called;
177 | expect(spyOnRABBITEncrypt).to.not.been.called;
178 |
179 | spyOnLZStringCompress.restore();
180 | spyOnRC4Encrypt.restore();
181 | spyOnRABBITEncrypt.restore();
182 | });
183 |
184 | it('should not call LZString compress if compression OFF', () => {
185 | lib = new SecureLS({encodingType: 'aes', isCompression: false});
186 |
187 | let spyOnLZStringCompress = sinon.spy(lib.LZString, 'compressToUTF16');
188 | let data = {
189 | username: 'softvar',
190 | module: 'secure-ls',
191 | age: 1
192 | };
193 |
194 | lib.processData(data);
195 | expect(spyOnLZStringCompress).to.not.been.called;
196 |
197 | spyOnLZStringCompress.restore();
198 | });
199 |
200 | it('should call LZString compress if compression in ON', () => {
201 | lib = new SecureLS({encodingType: 'aes', isCompression: true});
202 |
203 | let spyOnLZStringCompress = sinon.spy(lib.LZString, 'compressToUTF16');
204 | let data = {
205 | username: 'softvar',
206 | module: 'secure-ls',
207 | age: 1
208 | };
209 |
210 | lib.processData(data);
211 | expect(spyOnLZStringCompress).to.been.called;
212 |
213 | spyOnLZStringCompress.restore();
214 | });
215 | });
216 |
217 | });
218 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import utils from './utils';
2 | import constants from './constants';
3 | import enc from './enc-utf8';
4 |
5 | import Base64 from './Base64';
6 | import LZString from 'lz-string/libs/lz-string';
7 | import AES from 'crypto-js/aes';
8 | import DES from 'crypto-js/tripledes';
9 | import RABBIT from 'crypto-js/rabbit';
10 | import RC4 from 'crypto-js/rc4';
11 |
12 | export default class SecureLS {
13 | constructor(config) {
14 | config = config || {};
15 | this._name = 'secure-ls';
16 | this.utils = utils;
17 | this.constants = constants;
18 | this.Base64 = Base64;
19 | this.LZString = LZString;
20 | this.AES = AES;
21 | this.DES = DES;
22 | this.RABBIT = RABBIT;
23 | this.RC4 = RC4;
24 | this.enc = enc;
25 |
26 | this.config = {
27 | isCompression: true,
28 | encodingType: constants.EncrytionTypes.BASE64
29 | };
30 | this.config.isCompression = typeof config.isCompression !== 'undefined' ?
31 | config.isCompression :
32 | true;
33 | this.config.encodingType = (typeof config.encodingType !== 'undefined' || config.encodingType === '') ?
34 | config.encodingType.toLowerCase() :
35 | constants.EncrytionTypes.BASE64;
36 | this.config.encryptionSecret = config.encryptionSecret;
37 |
38 | this.ls = localStorage;
39 | this.init();
40 | };
41 |
42 | init() {
43 | let metaData = this.getMetaData() || {};
44 |
45 | this.WarningEnum = this.constants.WarningEnum;
46 | this.WarningTypes = this.constants.WarningTypes;
47 | this.EncrytionTypes = this.constants.EncrytionTypes;
48 |
49 | this._isBase64 = this._isBase64EncryptionType();
50 | this._isAES = this._isAESEncryptionType();
51 | this._isDES = this._isDESEncryptionType();
52 | this._isRabbit = this._isRabbitEncryptionType();
53 | this._isRC4 = this._isRC4EncryptionType();
54 | this._isCompression = this._isDataCompressionEnabled();
55 |
56 | // fill the already present keys to the list of keys being used by secure-ls
57 | this.utils.allKeys = metaData.keys || this.resetAllKeys();
58 | };
59 |
60 | _isBase64EncryptionType() {
61 | return Base64 &&
62 | (typeof this.config.encodingType === 'undefined' ||
63 | this.config.encodingType === this.constants.EncrytionTypes.BASE64);
64 | };
65 |
66 | _isAESEncryptionType() {
67 | return AES &&
68 | (this.config.encodingType === this.constants.EncrytionTypes.AES);
69 | };
70 |
71 | _isDESEncryptionType() {
72 | return DES &&
73 | (this.config.encodingType === this.constants.EncrytionTypes.DES);
74 | };
75 |
76 | _isRabbitEncryptionType() {
77 | return RABBIT &&
78 | (this.config.encodingType === this.constants.EncrytionTypes.RABBIT);
79 | };
80 |
81 | _isRC4EncryptionType() {
82 | return RC4 &&
83 | (this.config.encodingType === this.constants.EncrytionTypes.RC4);
84 | };
85 |
86 | _isDataCompressionEnabled() {
87 | return this.config.isCompression;
88 | }
89 |
90 | getEncyptionSecret(key) {
91 | let metaData = this.getMetaData() || {};
92 | let obj = this.utils.getObjectFromKey(metaData.keys, key);
93 |
94 | if (!obj) {
95 | return;
96 | }
97 |
98 | if (this._isAES ||
99 | this._isDES ||
100 | this._isRabbit ||
101 | this._isRC4
102 | ) {
103 | if (typeof this.config.encryptionSecret === 'undefined') {
104 | this.utils.encryptionSecret = obj.s;
105 |
106 | if (!this.utils.encryptionSecret) {
107 | this.utils.encryptionSecret = this.utils.generateSecretKey();
108 | this.setMetaData();
109 | }
110 | } else {
111 | this.utils.encryptionSecret = this.config.encryptionSecret || obj.s || '';
112 | }
113 | }
114 | }
115 |
116 | get(key, isAllKeysData) {
117 | let decodedData = '',
118 | jsonData = '',
119 | deCompressedData,
120 | bytes,
121 | data;
122 |
123 | if (!this.utils.is(key)) {
124 | this.utils.warn(this.WarningEnum.KEY_NOT_PROVIDED);
125 | return jsonData;
126 | }
127 |
128 | data = this.getDataFromLocalStorage(key);
129 |
130 | if (!data) {
131 | return jsonData;
132 | }
133 |
134 | deCompressedData = data; // saves else
135 | if (this._isCompression || isAllKeysData) { // meta data always compressed
136 | deCompressedData = LZString.decompressFromUTF16(data);
137 | }
138 |
139 | decodedData = deCompressedData; // saves else
140 | if (this._isBase64 || isAllKeysData) { // meta data always Base64
141 | decodedData = Base64.decode(deCompressedData);
142 | } else {
143 | this.getEncyptionSecret(key);
144 | if (this._isAES) {
145 | bytes = AES.decrypt(deCompressedData.toString(), this.utils.encryptionSecret);
146 | } else if (this._isDES) {
147 | bytes = DES.decrypt(deCompressedData.toString(), this.utils.encryptionSecret);
148 | } else if (this._isRabbit) {
149 | bytes = RABBIT.decrypt(deCompressedData.toString(), this.utils.encryptionSecret);
150 | } else if (this._isRC4) {
151 | bytes = RC4.decrypt(deCompressedData.toString(), this.utils.encryptionSecret);
152 | }
153 |
154 | if (bytes) {
155 | decodedData = bytes.toString(enc._Utf8);
156 | }
157 | }
158 |
159 | try {
160 | jsonData = JSON.parse(decodedData);
161 | } catch (e) {
162 | throw new Error('Could not parse JSON');
163 | }
164 |
165 | return jsonData;
166 | };
167 |
168 | getDataFromLocalStorage(key) {
169 | return this.ls.getItem(key, true);
170 | };
171 |
172 | getAllKeys() {
173 | let data = this.getMetaData();
174 |
175 | return this.utils.extractKeyNames(data) || [];
176 | };
177 |
178 | set(key, data) {
179 | let dataToStore = '';
180 |
181 | if (!this.utils.is(key)) {
182 | this.utils.warn(this.WarningEnum.KEY_NOT_PROVIDED);
183 | return;
184 | }
185 |
186 | this.getEncyptionSecret(key);
187 |
188 | // add key(s) to Array if not already added, only for keys other than meta key
189 | if (!(String(key) === String(this.utils.metaKey))) {
190 | if (!this.utils.isKeyPresent(key)) {
191 | this.utils.addToKeysList(key);
192 | this.setMetaData();
193 | }
194 | }
195 |
196 | dataToStore = this.processData(data);
197 | // Store the data to localStorage
198 | this.setDataToLocalStorage(key, dataToStore);
199 | };
200 |
201 | setDataToLocalStorage(key, data) {
202 | this.ls.setItem(key, data);
203 | };
204 |
205 | remove(key) {
206 | if (!this.utils.is(key)) {
207 | this.utils.warn(this.WarningEnum.KEY_NOT_PROVIDED);
208 | return;
209 | }
210 |
211 | if (key === this.utils.metaKey && this.getAllKeys().length) {
212 | this.utils.warn(this.WarningEnum.META_KEY_REMOVE);
213 | return;
214 | }
215 |
216 | if (this.utils.isKeyPresent(key)) {
217 | this.utils.removeFromKeysList(key);
218 | this.setMetaData();
219 | }
220 | this.ls.removeItem(key);
221 | };
222 |
223 | removeAll() {
224 | let keys, i;
225 |
226 | keys = this.getAllKeys();
227 | for (i = 0; i < keys.length; i++) {
228 | this.ls.removeItem(keys[i]);
229 | }
230 | this.ls.removeItem(this.utils.metaKey);
231 |
232 | this.resetAllKeys();
233 | };
234 |
235 | clear() {
236 | this.ls.clear();
237 | this.resetAllKeys();
238 | };
239 |
240 | resetAllKeys() {
241 | this.utils.allKeys = [];
242 | return [];
243 | }
244 |
245 | processData(data, isAllKeysData) {
246 | if (!data) {
247 | return '';
248 | }
249 |
250 | let jsonData, encodedData, compressedData;
251 |
252 | try {
253 | jsonData = JSON.stringify(data);
254 | } catch (e) {
255 | throw new Error('Could not stringify data.');
256 | }
257 |
258 | // Encode Based on encoding type
259 | // If not set, default to Base64 for securing data
260 | encodedData = jsonData;
261 | if (this._isBase64 || isAllKeysData) {
262 | encodedData = Base64.encode(jsonData);
263 | } else {
264 | if (this._isAES) {
265 | encodedData = AES.encrypt(jsonData, this.utils.encryptionSecret);
266 | } else if (this._isDES) {
267 | encodedData = DES.encrypt(jsonData, this.utils.encryptionSecret);
268 | } else if (this._isRabbit) {
269 | encodedData = RABBIT.encrypt(jsonData, this.utils.encryptionSecret);
270 | } else if (this._isRC4) {
271 | encodedData = RC4.encrypt(jsonData, this.utils.encryptionSecret);
272 | }
273 |
274 | encodedData = encodedData && encodedData.toString();
275 | }
276 |
277 | // Compress data if set to true
278 | compressedData = encodedData;
279 | if (this._isCompression || isAllKeysData) {
280 | compressedData = LZString.compressToUTF16(encodedData);
281 | }
282 |
283 | return compressedData;
284 | };
285 |
286 | setMetaData() {
287 | let dataToStore = this.processData({
288 | keys: this.utils.allKeys
289 | }, true);
290 |
291 | // Store the data to localStorage
292 | this.setDataToLocalStorage(this.utils.metaKey, dataToStore);
293 | };
294 |
295 | getMetaData() {
296 | return this.get(this.utils.metaKey, true);
297 | };
298 |
299 | };
300 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # secure-ls
2 |
3 | Secure localStorage data with high level of encryption and data compression.
4 |
5 | [](https://www.npmjs.com/package/secure-ls) [](https://www.npmjs.com/package/secure-ls) [](http://travis-ci.org/softvar/secure-ls) [](https://coveralls.io/github/softvar/secure-ls?branch=master)
6 |
7 | **[LIVE DEMO](http://softvar.github.io/secure-ls#live-demo)**
8 |
9 | ## Features
10 |
11 | * Secure data with various types of encryption including `AES`, `DES`, `Rabbit` and `RC4`. (defaults to `Base64` encoding).
12 | * Compress data before storing it to `localStorage` to save extra bytes (defaults to `true`).
13 | * Advanced API wrapper over `localStorage` API, providing other basic utilities.
14 | * Save data in multiple keys inside `localStorage` and `secure-ls` will always remember it's creation.
15 |
16 | ## Installation
17 |
18 | ```
19 | $ npm install secure-ls
20 | ```
21 |
22 | ## Libraries used
23 |
24 | * **Encryption / Decryption** using [The Cipher Algorithms](https://code.google.com/archive/p/crypto-js)
25 |
26 | It requires secret-key for encrypting and decrypting data securely. If custom secret-key is provided as mentioned below in APIs, then the library will pick that otherwise it will generate yet another very `secure` unique password key using [PBKDF2](https://code.google.com/archive/p/crypto-js/#PBKDF2), which will be further used for future API requests.
27 |
28 | `PBKDF2` is a password-based key derivation function. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.
29 |
30 | A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.
31 |
32 | Eg: `55e8f5585789191d350329b9ebcf2b11` and `db51d35aad96610683d5a40a70b20c39`.
33 |
34 | For the genration of such strings, `secretPhrase` is being used and can be found in code easily but that won't make it unsecure, `PBKDF2`'s layer on top of that will handle security.
35 |
36 | * **Compresion / Decompression** using [lz-string](https://github.com/pieroxy/lz-string)
37 |
38 | ## Usage
39 |
40 | * Example 1: With `default` settings i.e. `Base64` Encoding and Data Compression
41 |
42 | ```
43 | > var ls = new SecureLS();
44 | > ls.set('key1', {data: 'test'}); // set key1
45 | > ls.get('key1'); // print data
46 | {data: 'test'}
47 | ```
48 |
49 | * Example 2: With `AES` Encryption and Data Compression
50 |
51 | ```
52 | > var ls = new SecureLS({encodingType: 'aes'});
53 | > ls.set('key1', {data: 'test'}); // set key1
54 | > ls.get('key1'); // print data
55 | {data: 'test'}
56 |
57 | > ls.set('key2', [1, 2, 3]); // set another key
58 | > ls.getAllKeys(); // get all keys
59 | ["key1", "key2"]
60 | > ls.removeAll(); // remove all keys
61 |
62 | ```
63 |
64 | * Example 3: With `RC4` Encryption but no Data Compression
65 |
66 | ```
67 | > var ls = new SecureLS({encodingType: 'rc4', isCompression: false});
68 | > ls.set('key1', {data: 'test'}); // set key1
69 | > ls.get('key1'); // print data
70 | {data: 'test'}
71 |
72 | > ls.set('key2', [1, 2, 3]); // set another key
73 | > ls.getAllKeys(); // get all keys
74 | ["key1", "key2"]
75 | > ls.removeAll(); // remove all keys
76 |
77 | ```
78 |
79 | * Example 3: With `DES` Encryption, no Data Compression and custom secret key
80 |
81 | ```
82 | > var ls = new SecureLS({encodingType: 'des', isCompression: false, encryptionSecret: 'my-secret-key'});
83 | > ls.set('key1', {data: 'test'}); // set key1
84 | > ls.get('key1'); // print data
85 | {data: 'test'}
86 |
87 | > ls.set('key2', [1, 2, 3]); // set another key
88 | > ls.getAllKeys(); // get all keys
89 | ["key1", "key2"]
90 | > ls.removeAll(); // remove all keys
91 |
92 | ```
93 |
94 | ## API Documentation
95 |
96 | #### Create an instance / reference before using.
97 |
98 | ```
99 | var ls = new SecureLS();
100 | ```
101 |
102 | `Contructor` accepts a configurable `Object` with all three keys being optional.
103 |
104 |
105 | | Config Keys | default | accepts |
106 | | --------------------- | -------------- | ----------------------------------------- |
107 | | **encodingType** | Base64 | `base64`/`aes`/`des`/`rabbit`/`rc4`/`''` |
108 | | **isCompression** | `true` | `true`/`false` |
109 | | **encryptionSecret** | PBKDF2 value | String |
110 |
111 | **Note:** `encryptionSecret` will only be used for the Encryption and Decryption of data with `AES`, `DES`, `RC4`, `RABBIT`, and the library will discard it if no encoding / Base64 encoding method is choosen.
112 |
113 |
114 | **Examples:**
115 |
116 | * No config or empty Object i.e. Default **`Base64 Encoding`** and **`Data compression`**
117 |
118 | ```
119 | var ls = new SecureLS();
120 | // or
121 | var ls = new SecureLS({});
122 | ```
123 |
124 | * No encoding No data compression i.e. **`Normal`** way of storing data
125 |
126 | ```
127 | var ls = new SecureLS({encodingType: '', isCompression: false});
128 | ```
129 |
130 | * **`Base64`** encoding but **`no`** data compression
131 |
132 | ```
133 | var ls = new SecureLS({isCompression: false});
134 | ```
135 |
136 | * **`AES`** encryption and **`data compression`**
137 |
138 | ```
139 | var ls = new SecureLS({encodingType: 'aes'});
140 | ```
141 |
142 | * **`RC4`** encryption and **`no`** data compression
143 |
144 | ```
145 | var ls = new SecureLS({encodingType: 'rc4', isCompression: false});
146 | ```
147 |
148 | * **`RABBIT`** encryption, **`no`** data compression and `custom` encryptionSecret
149 |
150 | ```
151 | var ls = new SecureLS({encodingType: 'rc4', isCompression: false, encryptionSecret: 's3cr3tPa$$w0rd@123'});
152 | ```
153 |
154 |
155 | #### Methods
156 |
157 | * **`set`**
158 |
159 | Saves `data` in specifed `key` in localStorage. If the key is not provided, the library will warn. Following types of JavaScript objects are supported:
160 |
161 | * Array
162 | * ArrayBuffer
163 | * Blob
164 | * Float32Array
165 | * Float64Array
166 | * Int8Array
167 | * Int16Array
168 | * Int32Array
169 | * Number
170 | * Object
171 | * Uint8Array
172 | * Uint8ClampedArray
173 | * Uint16Array
174 | * Uint32Array
175 | * String
176 |
177 | | Parameter | Description |
178 | | ------------- | --------------------------- |
179 | | key | key to store data in |
180 | | data | data to be stored |
181 |
182 | ```
183 | ls.set('key-name', {test: 'secure-ls'})
184 | ```
185 |
186 | * **`get`**
187 |
188 | Gets `data` back from specified `key` from the localStorage library. If the key is not provided, the library will warn.
189 |
190 | | Parameter | Description |
191 | | ------------- | ----------------------------------- |
192 | | key | key in which data is stored |
193 |
194 | ```
195 | ls.get('key-name')
196 | ```
197 |
198 | * **`remove`**
199 |
200 | Removes the value of a key from the localStorage. If the `meta key`, which stores the list of keys, is tried to be removed even if there are other keys which were created by `secure-ls` library, the library will warn for the action.
201 |
202 | | Parameter | Description |
203 | | ------------- | ----------------------------------------- |
204 | | key | remove key in which data is stored |
205 |
206 | ```
207 | ls.remove('key-name')
208 | ```
209 |
210 | * **`removeAll`**
211 |
212 | Removes all the keys that were created by the `secure-ls` library, even the `meta key`.
213 |
214 | ```
215 | ls.removeAll()
216 | ```
217 |
218 | * **`clear`**
219 |
220 | Removes all the keys ever created for that particular domain. Remember localStorage works differently for `http` and `https` protocol;
221 |
222 | ```
223 | ls.clear()
224 | ```
225 |
226 | * **`getAllKeys`**
227 |
228 | Gets the list of keys that were created using the `secure-ls` library. Helpful when data needs to be retrieved for all the keys or when keys name are not known(dynamically created keys).
229 |
230 | `getAllKeys()`
231 |
232 | ```
233 | ls.getAllKeys()
234 | ```
235 |
236 | ## Screenshot
237 |
238 |
239 |
240 | ## Scripts
241 |
242 | * `npm run build` - produces production version of the library under the `dist` folder
243 | * `npm run dev` - produces development version of the library and runs a watcher
244 | * `npm run test` - well ... it runs the tests :)
245 |
246 | ## Contributing
247 |
248 | 1. Fork the repo on GitHub.
249 | 2. Clone the repo on machine.
250 | 3. Execute `npm install` and `npm run dev`.
251 | 3. Create a new branch `>=1}f--,0==f&&(f=Math.pow(2,p),p++),delete a[h]}else for(n=o[h],i=0;i
>=1;f--,0==f&&(f=Math.pow(2,p),p++),o[u]=l++,h=String(c)}if(""!==h){if(Object.prototype.hasOwnProperty.call(a,h)){if(h.charCodeAt(0)<256){for(i=0;i
>=1}else{for(n=1,i=0;i
>=1}f--,0==f&&(f=Math.pow(2,p),p++),delete a[h]}else for(n=o[h],i=0;i
>=1;f--,0==f&&(f=Math.pow(2,p),p++)}for(n=2,i=0;i
>=1;for(;;){if(y<<=1,v==e-1){d.push(r(y));break}v++}return d.join("")},decompress:function(t){return null==t?"":""==t?null:s._decompress(t.length,32768,function(e){return t.charCodeAt(e)})},_decompress:function(t,r,i){var n,s,o,a,c,u,h,f,l=[],p=4,d=4,y=3,v="",_=[],g={val:i(0),position:r,index:1};for(s=0;s<3;s+=1)l[s]=s;for(a=0,u=Math.pow(2,2),h=1;h!=u;)c=g.val&g.position,g.position>>=1,0==g.position&&(g.position=r,g.val=i(g.index++)),a|=(c>0?1:0)*h,h<<=1;switch(n=a){case 0:for(a=0,u=Math.pow(2,8),h=1;h!=u;)c=g.val&g.position,g.position>>=1,0==g.position&&(g.position=r,g.val=i(g.index++)),a|=(c>0?1:0)*h,h<<=1;f=e(a);break;case 1:for(a=0,u=Math.pow(2,16),h=1;h!=u;)c=g.val&g.position,g.position>>=1,0==g.position&&(g.position=r,g.val=i(g.index++)),a|=(c>0?1:0)*h,h<<=1;f=e(a);break;case 2:return""}for(l[3]=f,o=f,_.push(f);;){if(g.index>t)return"";for(a=0,u=Math.pow(2,y),h=1;h!=u;)c=g.val&g.position,g.position>>=1,0==g.position&&(g.position=r,g.val=i(g.index++)),a|=(c>0?1:0)*h,h<<=1;switch(f=a){case 0:for(a=0,u=Math.pow(2,8),h=1;h!=u;)c=g.val&g.position,g.position>>=1,0==g.position&&(g.position=r,g.val=i(g.index++)),a|=(c>0?1:0)*h,h<<=1;l[d++]=e(a),f=d-1,p--;break;case 1:for(a=0,u=Math.pow(2,16),h=1;h!=u;)c=g.val&g.position,g.position>>=1,0==g.position&&(g.position=r,g.val=i(g.index++)),a|=(c>0?1:0)*h,h<<=1;l[d++]=e(a),f=d-1,p--;break;case 2:return _.join("")}if(0==p&&(p=Math.pow(2,y),y++),l[f])v=l[f];else{if(f!==d)return null;v=o+o.charAt(0)}_.push(v),l[d++]=o+v.charAt(0),p--,o=v,0==p&&(p=Math.pow(2,y),y++)}}};return s}();i=function(){return n}.call(e,r,e,t),!(void 0!==i&&(t.exports=i))},function(t,e,r){!function(i,n,s){t.exports=e=n(r(5),r(12),r(13),r(14),r(15))}(this,function(t){return function(){var e=t,r=e.lib,i=r.BlockCipher,n=e.algo,s=[],o=[],a=[],c=[],u=[],h=[],f=[],l=[],p=[],d=[];!function(){for(var t=[],e=0;e<256;e++)e<128?t[e]=e<<1:t[e]=e<<1^283;for(var r=0,i=0,e=0;e<256;e++){var n=i^i<<1^i<<2^i<<3^i<<4;n=n>>>8^255&n^99,s[r]=n,o[n]=r;var y=t[r],v=t[y],_=t[v],g=257*t[n]^16843008*n;a[r]=g<<24|g>>>8,c[r]=g<<16|g>>>16,u[r]=g<<8|g>>>24,h[r]=g;var g=16843009*_^65537*v^257*y^16843008*r;f[n]=g<<24|g>>>8,l[n]=g<<16|g>>>16,p[n]=g<<8|g>>>24,d[n]=g,r?(r=y^t[t[t[_^y]]],i^=t[t[i]]):r=i=1}}();var y=[0,1,2,4,8,16,32,64,128,27,54],v=n.AES=i.extend({_doReset:function(){if(!this._nRounds||this._keyPriorReset!==this._key){for(var t=this._keyPriorReset=this._key,e=t.words,r=t.sigBytes/4,i=this._nRounds=r+6,n=4*(i+1),o=this._keySchedule=[],a=0;a>>32-s)+e}function n(t,e,r,i,n,s,o){var a=t+(e^r^i)+n+o;return(a<>>32-s)+e}function s(t,e,r,i,n,s,o){var a=t+(r^(e|~i))+n+o;return(a<>>32-s)+e}var o=t,a=o.lib,c=a.WordArray,u=a.Hasher,h=o.algo,f=[];!function(){for(var t=0;t<64;t++)f[t]=4294967296*e.abs(e.sin(t+1))|0}();var l=h.MD5=u.extend({_doReset:function(){this._hash=new c.init([1732584193,4023233417,2562383102,271733878])},_doProcessBlock:function(t,e){for(var o=0;o<16;o++){var a=e+o,c=t[a];t[a]=16711935&(c<<8|c>>>24)|4278255360&(c<<24|c>>>8)}var u=this._hash.words,h=t[e+0],l=t[e+1],p=t[e+2],d=t[e+3],y=t[e+4],v=t[e+5],_=t[e+6],g=t[e+7],S=t[e+8],m=t[e+9],k=t[e+10],B=t[e+11],E=t[e+12],C=t[e+13],x=t[e+14],A=t[e+15],w=u[0],b=u[1],D=u[2],T=u[3];w=r(w,b,D,T,h,7,f[0]),T=r(T,w,b,D,l,12,f[1]),D=r(D,T,w,b,p,17,f[2]),b=r(b,D,T,w,d,22,f[3]),w=r(w,b,D,T,y,7,f[4]),T=r(T,w,b,D,v,12,f[5]),D=r(D,T,w,b,_,17,f[6]),b=r(b,D,T,w,g,22,f[7]),w=r(w,b,D,T,S,7,f[8]),T=r(T,w,b,D,m,12,f[9]),D=r(D,T,w,b,k,17,f[10]),b=r(b,D,T,w,B,22,f[11]),w=r(w,b,D,T,E,7,f[12]),T=r(T,w,b,D,C,12,f[13]),D=r(D,T,w,b,x,17,f[14]),b=r(b,D,T,w,A,22,f[15]),w=i(w,b,D,T,l,5,f[16]),T=i(T,w,b,D,_,9,f[17]),D=i(D,T,w,b,B,14,f[18]),b=i(b,D,T,w,h,20,f[19]),w=i(w,b,D,T,v,5,f[20]),T=i(T,w,b,D,k,9,f[21]),D=i(D,T,w,b,A,14,f[22]),b=i(b,D,T,w,y,20,f[23]),w=i(w,b,D,T,m,5,f[24]),T=i(T,w,b,D,x,9,f[25]),D=i(D,T,w,b,d,14,f[26]),b=i(b,D,T,w,S,20,f[27]),w=i(w,b,D,T,C,5,f[28]),T=i(T,w,b,D,p,9,f[29]),D=i(D,T,w,b,g,14,f[30]),b=i(b,D,T,w,E,20,f[31]),w=n(w,b,D,T,v,4,f[32]),T=n(T,w,b,D,S,11,f[33]),D=n(D,T,w,b,B,16,f[34]),b=n(b,D,T,w,x,23,f[35]),w=n(w,b,D,T,l,4,f[36]),T=n(T,w,b,D,y,11,f[37]),D=n(D,T,w,b,g,16,f[38]),b=n(b,D,T,w,k,23,f[39]),w=n(w,b,D,T,C,4,f[40]),T=n(T,w,b,D,h,11,f[41]),D=n(D,T,w,b,d,16,f[42]),b=n(b,D,T,w,_,23,f[43]),w=n(w,b,D,T,m,4,f[44]),T=n(T,w,b,D,E,11,f[45]),D=n(D,T,w,b,A,16,f[46]),b=n(b,D,T,w,p,23,f[47]),w=s(w,b,D,T,h,6,f[48]),T=s(T,w,b,D,g,10,f[49]),D=s(D,T,w,b,x,15,f[50]),b=s(b,D,T,w,v,21,f[51]),w=s(w,b,D,T,E,6,f[52]),T=s(T,w,b,D,d,10,f[53]),D=s(D,T,w,b,k,15,f[54]),b=s(b,D,T,w,l,21,f[55]),w=s(w,b,D,T,S,6,f[56]),T=s(T,w,b,D,A,10,f[57]),D=s(D,T,w,b,_,15,f[58]),b=s(b,D,T,w,C,21,f[59]),w=s(w,b,D,T,y,6,f[60]),T=s(T,w,b,D,B,10,f[61]),D=s(D,T,w,b,p,15,f[62]),b=s(b,D,T,w,m,21,f[63]),u[0]=u[0]+w|0,u[1]=u[1]+b|0,u[2]=u[2]+D|0,u[3]=u[3]+T|0},_doFinalize:function(){var t=this._data,r=t.words,i=8*this._nDataBytes,n=8*t.sigBytes;r[n>>>5]|=128<<24-n%32;var s=e.floor(i/4294967296),o=i;r[(n+64>>>9<<4)+15]=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),r[(n+64>>>9<<4)+14]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8),t.sigBytes=4*(r.length+1),this._process();for(var a=this._hash,c=a.words,u=0;u<4;u++){var h=c[u];c[u]=16711935&(h<<8|h>>>24)|4278255360&(h<<24|h>>>8)}return a},clone:function(){var t=u.clone.call(this);return t._hash=this._hash.clone(),t}});o.MD5=u._createHelper(l),o.HmacMD5=u._createHmacHelper(l)}(Math),t.MD5})},function(t,e,r){!function(i,n,s){t.exports=e=n(r(5),r(6),r(7))}(this,function(t){return function(){var e=t,r=e.lib,i=r.Base,n=r.WordArray,s=e.algo,o=s.MD5,a=s.EvpKDF=i.extend({cfg:i.extend({keySize:4,hasher:o,iterations:1}),init:function(t){this.cfg=this.cfg.extend(t)},compute:function(t,e){for(var r=this.cfg,i=r.hasher.create(),s=n.create(),o=s.words,a=r.keySize,c=r.iterations;o.length>>2];t.sigBytes-=e}},_=(i.BlockCipher=f.extend({cfg:f.cfg.extend({mode:d,padding:v}),reset:function(){f.reset.call(this);var t=this.cfg,e=t.iv,r=t.mode;if(this._xformMode==this._ENC_XFORM_MODE)var i=r.createEncryptor;else{var i=r.createDecryptor;this._minBufferSize=1}this._mode=i.call(r,this,e&&e.words)},_doProcessBlock:function(t,e){this._mode.processBlock(t,e)},_doFinalize:function(){var t=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){t.pad(this._data,this.blockSize);var e=this._process(!0)}else{var e=this._process(!0);t.unpad(e)}return e},blockSize:4}),i.CipherParams=n.extend({init:function(t){this.mixIn(t)},toString:function(t){return(t||this.formatter).stringify(this)}})),g=r.format={},S=g.OpenSSL={stringify:function(t){var e=t.ciphertext,r=t.salt;if(r)var i=s.create([1398893684,1701076831]).concat(r).concat(e);else var i=e;return i.toString(c)},parse:function(t){var e=c.parse(t),r=e.words;if(1398893684==r[0]&&1701076831==r[1]){var i=s.create(r.slice(2,4));r.splice(0,4),e.sigBytes-=16}return _.create({ciphertext:e,salt:i})}},m=i.SerializableCipher=n.extend({cfg:n.extend({format:S}),encrypt:function(t,e,r,i){i=this.cfg.extend(i);var n=t.createEncryptor(r,i),s=n.finalize(e),o=n.cfg;return _.create({ciphertext:s,key:r,iv:o.iv,algorithm:t,mode:o.mode,padding:o.padding,blockSize:t.blockSize,formatter:i.format})},decrypt:function(t,e,r,i){i=this.cfg.extend(i),e=this._parse(e,i.format);var n=t.createDecryptor(r,i).finalize(e.ciphertext);return n},_parse:function(t,e){return"string"==typeof t?e.parse(t,this):t}}),k=r.kdf={},B=k.OpenSSL={execute:function(t,e,r,i){i||(i=s.random(8));var n=h.create({keySize:e+r}).compute(t,i),o=s.create(n.words.slice(e),4*r);return n.sigBytes=4*e,_.create({key:n,iv:o,salt:i})}},E=i.PasswordBasedCipher=m.extend({cfg:m.cfg.extend({kdf:B}),encrypt:function(t,e,r,i){i=this.cfg.extend(i);var n=i.kdf.execute(r,t.keySize,t.ivSize);i.iv=n.iv;var s=m.encrypt.call(this,t,e,n.key,i);return s.mixIn(n),s},decrypt:function(t,e,r,i){i=this.cfg.extend(i),e=this._parse(e,i.format);var n=i.kdf.execute(r,t.keySize,t.ivSize,e.salt);
2 | i.iv=n.iv;var s=m.decrypt.call(this,t,e,n.key,i);return s}})}()})},function(t,e,r){!function(i,n,s){t.exports=e=n(r(5),r(12),r(13),r(14),r(15))}(this,function(t){return function(){function e(t,e){var r=(this._lBlock>>>t^this._rBlock)&e;this._rBlock^=r,this._lBlock^=r<