├── .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 | Example Page 5 | 6 | 7 | 8 | 9 | 10 | Open console to check localStorage data. 11 |
12 | All of the console output from various examples will be log here. Scroll within it to view all logs. 13 |

14 | Input:
{"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 | [![npm version](https://badge.fury.io/js/secure-ls.svg)](https://www.npmjs.com/package/secure-ls) [![npm](https://img.shields.io/npm/dt/secure-ls.svg)](https://www.npmjs.com/package/secure-ls) [![Build Status](http://img.shields.io/travis/softvar/secure-ls/master.svg?style=flat)](http://travis-ci.org/softvar/secure-ls) [![Coverage Status](https://coveralls.io/repos/github/softvar/secure-ls/badge.svg?branch=master)](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 `` and do your work. 252 | 4. Run `npm run build` to build dist files and `npm run test` to ensure all test cases are passing. 253 | 5. Commit your changes to the branch. 254 | 6. Submit a Pull request. 255 | 256 | ## Development Stack 257 | 258 | * Webpack based `src` compilation & bundling and `dist` generation. 259 | * ES6 as a source of writing code. 260 | * Exports in a [umd](https://github.com/umdjs/umd) format so the library works everywhere. 261 | * ES6 test setup with [Mocha](http://mochajs.org/) and [Chai](http://chaijs.com/). 262 | * Linting with [ESLint](http://eslint.org/). 263 | 264 | ## Process 265 | 266 | ``` 267 | ES6 source files 268 | | 269 | | 270 | webpack 271 | | 272 | +--- babel, eslint 273 | | 274 | ready to use 275 | library 276 | in umd format 277 | ``` 278 | 279 | ## Credits 280 | 281 | Many thanks to: 282 | 283 | * [@brix](https://github.com/brix) for the awesome **[crypto-js](https://github.com/brix/crypto-js)** library for encrypting and decrypting data securely. 284 | 285 | * [@pieroxy](https://github.com/pieroxy) for the **[lz-string](https://github.com/pieroxy/lz-string)** js library for data compression / decompression. 286 | 287 | * [@chinchang](https://github.com/chinchang) for the below open-source libraries which are used only for the [Landing Page Development](http://varunmalhotra.xyz/secure-ls/). 288 | 289 | * **[screenlog.js](https://github.com/chinchang/screenlog.js/)** - Brings console.log on the page's screen. 290 | * **[superplaceholder.js](https://github.com/chinchang/superplaceholder.js)** - For super charging input placeholders. 291 | 292 | 293 | ## Copyright and license 294 | 295 | >The [MIT license](https://opensource.org/licenses/MIT) (MIT) 296 | > 297 | >Copyright (c) 2015-2016 Varun Malhotra 298 | > 299 | >Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 300 | > 301 | >The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 302 | > 303 | >THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 304 | -------------------------------------------------------------------------------- /test/ls-data-enc-dec.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('Encryption / Decryption 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('Base64 encoded and no data compression', () => { 24 | it('should Base64 encode 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.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.equal(valueStored); 40 | 41 | lib.removeAll(); 42 | }); 43 | }); 44 | 45 | describe('Base64 encoded and data compression', () => { 46 | it('should Base64 encode data before storing to localStorage', () => { 47 | let valueStored; 48 | let data = [1, 2, 3]; 49 | let key = 'key-1'; 50 | 51 | lib = new SecureLS(); 52 | lib.ls = mockStorage; 53 | lib.set(key, data); 54 | 55 | // corresponding to [1, 2, 3] => "㪂ೠ눉惮 脔ொꀀ" i.e. Base64 encoded 56 | valueStored = lib.LZString.compressToUTF16(lib.Base64.encode(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('AES encyption and no data compression', () => { 68 | it('should encrypt data with AES before storing to localStorage', () => { 69 | let valueStored, valueRetrieved; 70 | let data = [1, 2, 3]; 71 | let key = 'key-1'; 72 | 73 | lib = new SecureLS({encodingType: 'aes', isCompression: false}); 74 | lib.ls = mockStorage; 75 | lib.set(key, data); 76 | 77 | // corresponding to [1, 2, 3] => "U2FsdGVkX18+oRXqlPpUH+Q0sI26w+8msQo5UhNq6hw=" i.e. AES encrypted 78 | valueStored = lib.AES.encrypt(JSON.stringify(data), lib.utils.encryptionSecret).toString(); 79 | 80 | expect(mockLS.storage[key]).to.exist; 81 | expect(mockLS.storage[key]).to.be.a('string'); 82 | 83 | // Can't check exact values since CryptoJS encryption is time-dependent 84 | // expect(mockLS.storage[key]).to.equal(valueStored); 85 | 86 | valueRetrieved = JSON.parse(lib.AES.decrypt(valueStored, lib.utils.encryptionSecret).toString(lib.enc._Utf8)); 87 | expect(data.toString()).to.equal(valueRetrieved.toString()); 88 | 89 | lib.removeAll(); 90 | }); 91 | }); 92 | 93 | describe('AES encyption and data compression', () => { 94 | it('should encrypt data with AES before storing to localStorage', () => { 95 | let valueStored, valueRetrieved; 96 | let data = [1, 2, 3]; 97 | let key = 'key-1'; 98 | 99 | lib = new SecureLS({encodingType: 'aes', isCompression: true}); 100 | lib.ls = mockStorage; 101 | lib.set(key, data); 102 | 103 | // corresponding to [1, 2, 3] => "⪂恢ೠ☎ڰځ᠁쁺Ÿીꀜ鄈Àኀ퐁᠁肢ϙ㑀娃࠰Ⲁ찠̨ư༠ǟ踈Ÿ耀 " i.e. compressed AES encrypted 104 | valueStored = lib.LZString.compressToUTF16(lib.AES.encrypt(JSON.stringify(data), lib.utils.encryptionSecret).toString()); 105 | 106 | expect(mockLS.storage[key]).to.exist; 107 | expect(mockLS.storage[key]).to.be.a('string'); 108 | 109 | // Can't check exact values since CryptoJS encryption is time-dependent 110 | // expect(mockLS.storage[key]).to.equal(valueStored); 111 | 112 | valueRetrieved = lib.LZString.decompressFromUTF16(valueStored); 113 | valueRetrieved = JSON.parse(lib.AES.decrypt(valueRetrieved, lib.utils.encryptionSecret).toString(lib.enc._Utf8)); 114 | expect(data.toString()).to.equal(valueRetrieved.toString()); 115 | 116 | lib.removeAll(); 117 | }); 118 | }); 119 | 120 | describe('AES encyption, data compression and custom secret key', () => { 121 | it('should encrypt data with AES with custom key before storing to localStorage', () => { 122 | let valueStored, valueRetrieved; 123 | let data = [1, 2, 3]; 124 | let key = 'key-1'; 125 | 126 | lib = new SecureLS({ 127 | encodingType: 'aes', 128 | isCompression: true, 129 | encryptionSecret: 'mySecretKey123' 130 | }); 131 | lib.ls = mockStorage; 132 | lib.set(key, data); 133 | 134 | expect(lib.config.encryptionSecret).to.equal('mySecretKey123'); 135 | expect(lib.utils.encryptionSecret).to.equal('mySecretKey123'); 136 | 137 | // corresponding to [1, 2, 3] => "⪂恢ೠ☎ڰځ᠁쁺Ÿીꀜ鄈Àኀ퐁᠁肢ϙ㑀娃࠰Ⲁ찠̨ư༠ǟ踈Ÿ耀 " i.e. compressed AES encrypted 138 | valueStored = lib.LZString.compressToUTF16(lib.AES.encrypt(JSON.stringify(data), lib.utils.encryptionSecret).toString()); 139 | 140 | expect(mockLS.storage[key]).to.exist; 141 | expect(mockLS.storage[key]).to.be.a('string'); 142 | 143 | 144 | // Can't check exact values since CryptoJS encryption is time-dependent 145 | // expect(mockLS.storage[key]).to.equal(valueStored); 146 | 147 | valueRetrieved = lib.LZString.decompressFromUTF16(valueStored); 148 | valueRetrieved = JSON.parse(lib.AES.decrypt(valueRetrieved, lib.utils.encryptionSecret).toString(lib.enc._Utf8)); 149 | expect(data.toString()).to.equal(valueRetrieved.toString()); 150 | 151 | lib.removeAll(); 152 | }); 153 | }); 154 | 155 | describe('DES encyption and no data compression', () => { 156 | it('should encrypt data with DES before storing to localStorage', () => { 157 | let valueStored, valueRetrieved; 158 | let data = [1, 2, 3]; 159 | let key = 'key-1'; 160 | 161 | lib = new SecureLS({encodingType: 'DES', isCompression: false}); 162 | lib.ls = mockStorage; 163 | lib.set(key, data); 164 | 165 | // corresponding to [1, 2, 3] => "U2FsdGVkX19FJjcyo+8PjIGbhKjZKxEt" i.e. DES encrypted 166 | valueStored = lib.DES.encrypt(JSON.stringify(data), lib.utils.encryptionSecret).toString(); 167 | 168 | expect(mockLS.storage[key]).to.exist; 169 | expect(mockLS.storage[key]).to.be.a('string'); 170 | 171 | // Can't check exact values since CryptoJS encryption is time-dependent 172 | // expect(mockLS.storage[key]).to.equal(valueStored); 173 | 174 | valueRetrieved = JSON.parse(lib.DES.decrypt(valueStored, lib.utils.encryptionSecret).toString(lib.enc._Utf8)); 175 | expect(data.toString()).to.equal(valueRetrieved.toString()); 176 | 177 | lib.removeAll(); 178 | }); 179 | }); 180 | 181 | describe('DES encyption, no data compression and custom secret key', () => { 182 | it('should encrypt data with DES before storing to localStorage', () => { 183 | let valueStored, valueRetrieved; 184 | let data = [1, 2, 3]; 185 | let key = 'key-1'; 186 | 187 | lib = new SecureLS({ 188 | encodingType: 'DES', 189 | isCompression: false, 190 | encryptionSecret: 'mySecretKey123' 191 | }); 192 | lib.ls = mockStorage; 193 | lib.set(key, data); 194 | 195 | expect(lib.config.encryptionSecret).to.equal('mySecretKey123'); 196 | expect(lib.utils.encryptionSecret).to.equal('mySecretKey123'); 197 | 198 | // corresponding to [1, 2, 3] => "U2FsdGVkX19FJjcyo+8PjIGbhKjZKxEt" i.e. DES encrypted 199 | valueStored = lib.DES.encrypt(JSON.stringify(data), lib.utils.encryptionSecret).toString(); 200 | 201 | expect(mockLS.storage[key]).to.exist; 202 | expect(mockLS.storage[key]).to.be.a('string'); 203 | 204 | // Can't check exact values since CryptoJS encryption is time-dependent 205 | // expect(mockLS.storage[key]).to.equal(valueStored); 206 | 207 | valueRetrieved = JSON.parse(lib.DES.decrypt(valueStored, lib.utils.encryptionSecret).toString(lib.enc._Utf8)); 208 | expect(data.toString()).to.equal(valueRetrieved.toString()); 209 | 210 | lib.removeAll(); 211 | }); 212 | }); 213 | 214 | describe('DES encyption and data compression', () => { 215 | it('should encrypt data with DES before storing to localStorage', () => { 216 | let valueStored, valueRetrieved; 217 | let data = [1, 2, 3]; 218 | let key = 'key-1'; 219 | 220 | lib = new SecureLS({encodingType: 'DES', isCompression: true}); 221 | lib.ls = mockStorage; 222 | lib.set(key, data); 223 | 224 | // corresponding to [1, 2, 3] => "⪂恢ೠ☎ڰځ᠉쁡㠓䌄倈쁺ᆰୀ䬐ʐɀ挀喠儴ݲ " i.e. compressed DES encrypted 225 | valueStored = lib.LZString.compressToUTF16(lib.DES.encrypt(JSON.stringify(data), lib.utils.encryptionSecret).toString()); 226 | 227 | expect(mockLS.storage[key]).to.exist; 228 | expect(mockLS.storage[key]).to.be.a('string'); 229 | 230 | // Can't check exact values since CryptoJS encryption is time-dependent 231 | // expect(mockLS.storage[key]).to.equal(valueStored); 232 | 233 | valueRetrieved = lib.LZString.decompressFromUTF16(valueStored); 234 | valueRetrieved = JSON.parse(lib.DES.decrypt(valueRetrieved, lib.utils.encryptionSecret).toString(lib.enc._Utf8)); 235 | expect(data.toString()).to.equal(valueRetrieved.toString()); 236 | 237 | lib.removeAll(); 238 | }); 239 | }); 240 | 241 | describe('RABBIT encyption and no data compression', () => { 242 | it('should encrypt data with RABBIT before storing to localStorage', () => { 243 | let valueStored, valueRetrieved; 244 | let data = [1, 2, 3]; 245 | let key = 'key-1'; 246 | 247 | lib = new SecureLS({encodingType: 'RABBIT', isCompression: false}); 248 | lib.ls = mockStorage; 249 | lib.set(key, data); 250 | 251 | // corresponding to [1, 2, 3] => "U2FsdGVkX1+hs6euMenWXefB7TBJwPM=" i.e. RABBIT encrypted 252 | valueStored = lib.RABBIT.encrypt(JSON.stringify(data), lib.utils.encryptionSecret).toString(); 253 | 254 | expect(mockLS.storage[key]).to.exist; 255 | expect(mockLS.storage[key]).to.be.a('string'); 256 | 257 | // Can't check exact values since CryptoJS encryption is time-dependent 258 | // expect(mockLS.storage[key]).to.equal(valueStored); 259 | 260 | valueRetrieved = JSON.parse(lib.RABBIT.decrypt(valueStored, lib.utils.encryptionSecret).toString(lib.enc._Utf8)); 261 | expect(data.toString()).to.equal(valueRetrieved.toString()); 262 | 263 | lib.removeAll(); 264 | }); 265 | }); 266 | 267 | describe('RABBIT encyption and data compression', () => { 268 | it('should encrypt data with RABBIT before storing to localStorage', () => { 269 | let valueStored, valueRetrieved; 270 | let data = [1, 2, 3]; 271 | let key = 'key-1'; 272 | 273 | lib = new SecureLS({encodingType: 'RABBIT', isCompression: true}); 274 | lib.ls = mockStorage; 275 | lib.set(key, data); 276 | 277 | // corresponding to [1, 2, 3] => "⪂恢ೠ☎ڰځ᠉쁩ㆠ倖쀡´ِᅀ༁搄㇀ิ欬臬ע" i.e. compressed RABBIT encrypted 278 | valueStored = lib.LZString.compressToUTF16(lib.RABBIT.encrypt(JSON.stringify(data), 279 | lib.utils.encryptionSecret).toString()); 280 | 281 | expect(mockLS.storage[key]).to.exist; 282 | expect(mockLS.storage[key]).to.be.a('string'); 283 | 284 | // Can't check exact values since CryptoJS encryption is time-dependent 285 | // expect(mockLS.storage[key]).to.equal(valueStored); 286 | 287 | valueRetrieved = lib.LZString.decompressFromUTF16(valueStored); 288 | valueRetrieved = JSON.parse(lib.RABBIT.decrypt(valueRetrieved, 289 | lib.utils.encryptionSecret).toString(lib.enc._Utf8)); 290 | expect(data.toString()).to.equal(valueRetrieved.toString()); 291 | 292 | lib.removeAll(); 293 | }); 294 | }); 295 | 296 | describe('RABBIT encyption, data compression but no secret key', () => { 297 | it('should encrypt data with RABBIT before storing to localStorage', () => { 298 | let valueStored, valueRetrieved; 299 | let data = [1, 2, 3]; 300 | let key = 'key-1'; 301 | 302 | lib = new SecureLS({ 303 | encodingType: 'RABBIT', 304 | isCompression: true, 305 | encryptionSecret: '' 306 | }); 307 | lib.ls = mockStorage; 308 | lib.set(key, data); 309 | 310 | expect(lib.config.encryptionSecret).to.not.equal('mySecretKey123'); 311 | expect(lib.utils.encryptionSecret).to.not.equal('mySecretKey123'); 312 | 313 | expect(lib.config.encryptionSecret).to.equal(''); 314 | expect(lib.utils.encryptionSecret).to.equal(''); 315 | 316 | // corresponding to [1, 2, 3] => "⪂恢ೠ☎ڰځ᠉쁩ㆠ倖쀡´ِᅀ༁搄㇀ิ欬臬ע" i.e. compressed RABBIT encrypted 317 | valueStored = lib.LZString.compressToUTF16(lib.RABBIT.encrypt(JSON.stringify(data), 318 | lib.utils.encryptionSecret).toString()); 319 | 320 | expect(mockLS.storage[key]).to.exist; 321 | expect(mockLS.storage[key]).to.be.a('string'); 322 | 323 | // Can't check exact values since CryptoJS encryption is time-dependent 324 | // expect(mockLS.storage[key]).to.equal(valueStored); 325 | 326 | valueRetrieved = lib.LZString.decompressFromUTF16(valueStored); 327 | valueRetrieved = JSON.parse(lib.RABBIT.decrypt(valueRetrieved, 328 | lib.utils.encryptionSecret).toString(lib.enc._Utf8)); 329 | expect(data.toString()).to.equal(valueRetrieved.toString()); 330 | 331 | lib.removeAll(); 332 | }); 333 | }); 334 | 335 | describe('RC4 encyption and no data compression', () => { 336 | it('should encrypt data with RC4 before storing to localStorage', () => { 337 | let valueStored, valueRetrieved; 338 | let data = [1, 2, 3]; 339 | let key = 'key-1'; 340 | 341 | lib = new SecureLS({encodingType: 'RC4', isCompression: false}); 342 | lib.ls = mockStorage; 343 | lib.set(key, data); 344 | 345 | // corresponding to [1, 2, 3] => "U2FsdGVkX1+kG77vLYAGhcPRdgH5GsQ=" i.e. RC4 encrypted 346 | valueStored = lib.RC4.encrypt(JSON.stringify(data), lib.utils.encryptionSecret).toString(); 347 | 348 | expect(mockLS.storage[key]).to.exist; 349 | expect(mockLS.storage[key]).to.be.a('string'); 350 | 351 | // Can't check exact values since CryptoJS encryption is time-dependent 352 | // expect(mockLS.storage[key]).to.equal(valueStored); 353 | 354 | valueRetrieved = JSON.parse(lib.RC4.decrypt(valueStored, lib.utils.encryptionSecret).toString(lib.enc._Utf8)); 355 | expect(data.toString()).to.equal(valueRetrieved.toString()); 356 | 357 | lib.removeAll(); 358 | }); 359 | }); 360 | 361 | describe('RC4 encyption and data compression', () => { 362 | it('should encrypt data with RC4 before storing to localStorage', () => { 363 | let valueStored, valueRetrieved; 364 | let data = [1, 2, 3]; 365 | let key = 'key-1'; 366 | 367 | lib = new SecureLS({encodingType: 'RC4', isCompression: true}); 368 | lib.ls = mockStorage; 369 | lib.set(key, data); 370 | 371 | // corresponding to [1, 2, 3] => "⪂恢ೠ☎ڰځ᠍䁅̘ࡀ⡀⢀丈٠ⶀ㙸໠ވɘའ̀눂 " i.e. compressed RC4 encrypted 372 | valueStored = lib.LZString.compressToUTF16(lib.RC4.encrypt(JSON.stringify(data), lib.utils.encryptionSecret).toString()); 373 | 374 | expect(mockLS.storage[key]).to.exist; 375 | expect(mockLS.storage[key]).to.be.a('string'); 376 | 377 | // Can't check exact values since CryptoJS encryption is time-dependent 378 | // expect(mockLS.storage[key]).to.equal(valueStored); 379 | 380 | valueRetrieved = lib.LZString.decompressFromUTF16(valueStored); 381 | valueRetrieved = JSON.parse(lib.RC4.decrypt(valueRetrieved, lib.utils.encryptionSecret).toString(lib.enc._Utf8)); 382 | expect(data.toString()).to.equal(valueRetrieved.toString()); 383 | 384 | lib.removeAll(); 385 | }); 386 | }); 387 | }); 388 | -------------------------------------------------------------------------------- /dist/secure-ls.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("SecureLS",[],e):"object"==typeof exports?exports.SecureLS=e():t.SecureLS=e()}(this,function(){return function(t){function e(i){if(r[i])return r[i].exports;var n=r[i]={exports:{},id:i,loaded:!1};return t[i].call(n.exports,n,n.exports,e),n.loaded=!0,n.exports}var r={};return e.m=t,e.c=r,e.p="",e(0)}([function(t,e,r){"use strict";function i(t){return t&&t.__esModule?t:{"default":t}}function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var s=function(){function t(t,e){for(var r=0;r>16)&r,t=18e3*(65535&t)+(t>>16)&r;var i=(e<<16)+t&r;return i/=4294967296,i+=.5,i*(Math.random()>.5?1:-1)}},n=0;n>>2]>>>24-s%4*8&255;e[i+s>>>2]|=o<<24-(i+s)%4*8}else for(var s=0;s>>2]=r[s>>>2];return this.sigBytes+=n,this},clamp:function(){var e=this.words,r=this.sigBytes;e[r>>>2]&=4294967295<<32-r%4*8,e.length=t.ceil(r/4)},clone:function(){var t=s.clone.call(this);return t.words=this.words.slice(0),t},random:function(e){for(var r,i=[],n=function(e){var e=e,r=987654321,i=4294967295;return function(){r=36969*(65535&r)+(r>>16)&i,e=18e3*(65535&e)+(e>>16)&i;var n=(r<<16)+e&i;return n/=4294967296,n+=.5,n*(t.random()>.5?1:-1)}},s=0;s>>2]>>>24-n%4*8&255;i.push((s>>>4).toString(16)),i.push((15&s).toString(16))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>3]|=parseInt(t.substr(i,2),16)<<24-i%8*4;return new o.init(r,e/2)}},u=a.Latin1={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=[],n=0;n>>2]>>>24-n%4*8&255;i.push(String.fromCharCode(s))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>2]|=(255&t.charCodeAt(i))<<24-i%4*8;return new o.init(r,e)}},h=a.Utf8={stringify:function(t){try{return decodeURIComponent(escape(u.stringify(t)))}catch(e){throw new Error("Malformed UTF-8 data")}},parse:function(t){return u.parse(unescape(encodeURIComponent(t)))}},f=n.BufferedBlockAlgorithm=s.extend({reset:function(){this._data=new o.init,this._nDataBytes=0},_append:function(t){"string"==typeof t&&(t=h.parse(t)),this._data.concat(t),this._nDataBytes+=t.sigBytes},_process:function(e){var r=this._data,i=r.words,n=r.sigBytes,s=this.blockSize,a=4*s,c=n/a;c=e?t.ceil(c):t.max((0|c)-this._minBufferSize,0);var u=c*s,h=t.min(4*u,n);if(u){for(var f=0;f>>31}var f=(i<<5|i>>>27)+c+o[u];f+=u<20?(n&s|~n&a)+1518500249:u<40?(n^s^a)+1859775393:u<60?(n&s|n&a|s&a)-1894007588:(n^s^a)-899497514,c=a,a=s,s=n<<30|n>>>2,n=i,i=f}r[0]=r[0]+i|0,r[1]=r[1]+n|0,r[2]=r[2]+s|0,r[3]=r[3]+a|0,r[4]=r[4]+c|0},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return e[i>>>5]|=128<<24-i%32,e[(i+64>>>9<<4)+14]=Math.floor(r/4294967296),e[(i+64>>>9<<4)+15]=r,t.sigBytes=4*e.length,this._process(),this._hash},clone:function(){var t=n.clone.call(this);return t._hash=this._hash.clone(),t}});e.SHA1=n._createHelper(a),e.HmacSHA1=n._createHmacHelper(a)}(),t.SHA1})},function(t,e,r){!function(i,n){t.exports=e=n(r(5))}(this,function(t){!function(){var e=t,r=e.lib,i=r.Base,n=e.enc,s=n.Utf8,o=e.algo;o.HMAC=i.extend({init:function(t,e){t=this._hasher=new t.init,"string"==typeof e&&(e=s.parse(e));var r=t.blockSize,i=4*r;e.sigBytes>i&&(e=t.finalize(e)),e.clamp();for(var n=this._oKey=e.clone(),o=this._iKey=e.clone(),a=n.words,c=o.words,u=0;u>>2]>>>24-n%4*8&255,i.push(String.fromCharCode(s));return i.join("")}},r._Utf8={stringify:function(t){try{return decodeURIComponent(escape(r.Latin1.stringify(t)))}catch(e){throw new Error("Malformed UTF-8 data")}}},t.exports=r},function(t,e){"use strict";var r={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(t){var e="",i=void 0,n=void 0,s=void 0,o=void 0,a=void 0,c=void 0,u=void 0,h=0;for(t=r._utf8Encode(t);h>2,a=(3&i)<<4|n>>4,c=(15&n)<<2|s>>6,u=63&s,isNaN(n)?c=u=64:isNaN(s)&&(u=64),e=e+this._keyStr.charAt(o)+this._keyStr.charAt(a)+this._keyStr.charAt(c)+this._keyStr.charAt(u);return e},decode:function(t){var e="",i=void 0,n=void 0,s=void 0,o=void 0,a=void 0,c=void 0,u=void 0,h=0;for(t=t.replace(/[^A-Za-z0-9\+\/\=]/g,"");h>4,n=(15&a)<<4|c>>2,s=(3&c)<<6|u,e+=String.fromCharCode(i),64!==c&&(e+=String.fromCharCode(n)),64!==u&&(e+=String.fromCharCode(s));return e=r._utf8Decode(e)},_utf8Encode:function(t){t=t.replace(/\r\n/g,"\n");for(var e="",r=0;r127&&i<2048?(e+=String.fromCharCode(i>>6|192),e+=String.fromCharCode(63&i|128)):(e+=String.fromCharCode(i>>12|224),e+=String.fromCharCode(i>>6&63|128),e+=String.fromCharCode(63&i|128))}return e},_utf8Decode:function(t){var e="",r=0,i=void 0,n=void 0,s=void 0;for(i=n=0;r191&&i<224?(n=t.charCodeAt(r+1),e+=String.fromCharCode((31&i)<<6|63&n),r+=2):(n=t.charCodeAt(r+1),s=t.charCodeAt(r+2),e+=String.fromCharCode((15&i)<<12|(63&n)<<6|63&s),r+=3);return e}};t.exports=r},function(t,e,r){var i,n=function(){function t(t,e){if(!n[t]){n[t]={};for(var r=0;r>>8,r[2*i+1]=o%256}return r},decompressFromUint8Array:function(t){if(null===t||void 0===t)return s.decompress(t);for(var r=new Array(t.length/2),i=0,n=r.length;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++),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;a6&&a%r==4&&(c=s[c>>>24]<<24|s[c>>>16&255]<<16|s[c>>>8&255]<<8|s[255&c]):(c=c<<8|c>>>24,c=s[c>>>24]<<24|s[c>>>16&255]<<16|s[c>>>8&255]<<8|s[255&c],c^=y[a/r|0]<<24),o[a]=o[a-r]^c}for(var u=this._invKeySchedule=[],h=0;h>>24]]^l[s[c>>>16&255]]^p[s[c>>>8&255]]^d[s[255&c]]}}},encryptBlock:function(t,e){this._doCryptBlock(t,e,this._keySchedule,a,c,u,h,s)},decryptBlock:function(t,e){var r=t[e+1];t[e+1]=t[e+3],t[e+3]=r,this._doCryptBlock(t,e,this._invKeySchedule,f,l,p,d,o);var r=t[e+1];t[e+1]=t[e+3],t[e+3]=r},_doCryptBlock:function(t,e,r,i,n,s,o,a){for(var c=this._nRounds,u=t[e]^r[0],h=t[e+1]^r[1],f=t[e+2]^r[2],l=t[e+3]^r[3],p=4,d=1;d>>24]^n[h>>>16&255]^s[f>>>8&255]^o[255&l]^r[p++],v=i[h>>>24]^n[f>>>16&255]^s[l>>>8&255]^o[255&u]^r[p++],_=i[f>>>24]^n[l>>>16&255]^s[u>>>8&255]^o[255&h]^r[p++],g=i[l>>>24]^n[u>>>16&255]^s[h>>>8&255]^o[255&f]^r[p++];u=y,h=v,f=_,l=g}var y=(a[u>>>24]<<24|a[h>>>16&255]<<16|a[f>>>8&255]<<8|a[255&l])^r[p++],v=(a[h>>>24]<<24|a[f>>>16&255]<<16|a[l>>>8&255]<<8|a[255&u])^r[p++],_=(a[f>>>24]<<24|a[l>>>16&255]<<16|a[u>>>8&255]<<8|a[255&h])^r[p++],g=(a[l>>>24]<<24|a[u>>>16&255]<<16|a[h>>>8&255]<<8|a[255&f])^r[p++];t[e]=y,t[e+1]=v,t[e+2]=_,t[e+3]=g},keySize:8});e.AES=i._createHelper(v)}(),t.AES})},function(t,e,r){!function(i,n){t.exports=e=n(r(5))}(this,function(t){return function(){function e(t,e,r){for(var i=[],s=0,o=0;o>>6-o%4*2;i[s>>>2]|=(a|c)<<24-s%4*8,s++}return n.create(i,s)}var r=t,i=r.lib,n=i.WordArray,s=r.enc;s.Base64={stringify:function(t){var e=t.words,r=t.sigBytes,i=this._map;t.clamp();for(var n=[],s=0;s>>2]>>>24-s%4*8&255,a=e[s+1>>>2]>>>24-(s+1)%4*8&255,c=e[s+2>>>2]>>>24-(s+2)%4*8&255,u=o<<16|a<<8|c,h=0;h<4&&s+.75*h>>6*(3-h)&63));var f=i.charAt(64);if(f)for(;n.length%4;)n.push(f);return n.join("")},parse:function(t){var r=t.length,i=this._map,n=this._reverseMap;if(!n){n=this._reverseMap=[];for(var s=0;s>>32-s)+e}function i(t,e,r,i,n,s,o){var a=t+(e&i|r&~i)+n+o;return(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<>>t^this._lBlock)&e;this._lBlock^=r,this._rBlock^=r<>>5]>>>31-n%32&1}for(var s=this._subKeys=[],o=0;o<16;o++){for(var a=s[o]=[],f=h[o],i=0;i<24;i++)a[i/6|0]|=r[(u[i]-1+f)%28]<<31-i%6,a[4+(i/6|0)]|=r[28+(u[i+24]-1+f)%28]<<31-i%6;a[0]=a[0]<<1|a[0]>>>31;for(var i=1;i<7;i++)a[i]=a[i]>>>4*(i-1)+3;a[7]=a[7]<<5|a[7]>>>27}for(var l=this._invSubKeys=[],i=0;i<16;i++)l[i]=s[15-i]},encryptBlock:function(t,e){this._doCryptBlock(t,e,this._subKeys)},decryptBlock:function(t,e){this._doCryptBlock(t,e,this._invSubKeys)},_doCryptBlock:function(t,i,n){this._lBlock=t[i],this._rBlock=t[i+1],e.call(this,4,252645135),e.call(this,16,65535),r.call(this,2,858993459),r.call(this,8,16711935),e.call(this,1,1431655765);for(var s=0;s<16;s++){for(var o=n[s],a=this._lBlock,c=this._rBlock,u=0,h=0;h<8;h++)u|=f[h][((c^o[h])&l[h])>>>0];this._lBlock=c,this._rBlock=a^u}var p=this._lBlock;this._lBlock=this._rBlock,this._rBlock=p,e.call(this,1,1431655765),r.call(this,8,16711935),r.call(this,2,858993459),e.call(this,16,65535),e.call(this,4,252645135),t[i]=this._lBlock,t[i+1]=this._rBlock},keySize:2,ivSize:2,blockSize:2});i.DES=o._createHelper(p);var d=a.TripleDES=o.extend({_doReset:function(){var t=this._key,e=t.words;this._des1=p.createEncryptor(s.create(e.slice(0,2))),this._des2=p.createEncryptor(s.create(e.slice(2,4))),this._des3=p.createEncryptor(s.create(e.slice(4,6)))},encryptBlock:function(t,e){this._des1.encryptBlock(t,e),this._des2.decryptBlock(t,e),this._des3.encryptBlock(t,e)},decryptBlock:function(t,e){this._des3.decryptBlock(t,e),this._des2.encryptBlock(t,e),this._des1.decryptBlock(t,e)},keySize:6,ivSize:2,blockSize:2});i.TripleDES=o._createHelper(d)}(),t.TripleDES})},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(){for(var t=this._X,e=this._C,r=0;r<8;r++)a[r]=e[r];e[0]=e[0]+1295307597+this._b|0,e[1]=e[1]+3545052371+(e[0]>>>0>>0?1:0)|0,e[2]=e[2]+886263092+(e[1]>>>0>>0?1:0)|0,e[3]=e[3]+1295307597+(e[2]>>>0>>0?1:0)|0,e[4]=e[4]+3545052371+(e[3]>>>0>>0?1:0)|0,e[5]=e[5]+886263092+(e[4]>>>0>>0?1:0)|0,e[6]=e[6]+1295307597+(e[5]>>>0>>0?1:0)|0,e[7]=e[7]+3545052371+(e[6]>>>0>>0?1:0)|0,this._b=e[7]>>>0>>0?1:0;for(var r=0;r<8;r++){var i=t[r]+e[r],n=65535&i,s=i>>>16,o=((n*n>>>17)+n*s>>>15)+s*s,u=((4294901760&i)*i|0)+((65535&i)*i|0);c[r]=o^u}t[0]=c[0]+(c[7]<<16|c[7]>>>16)+(c[6]<<16|c[6]>>>16)|0,t[1]=c[1]+(c[0]<<8|c[0]>>>24)+c[7]|0,t[2]=c[2]+(c[1]<<16|c[1]>>>16)+(c[0]<<16|c[0]>>>16)|0,t[3]=c[3]+(c[2]<<8|c[2]>>>24)+c[1]|0,t[4]=c[4]+(c[3]<<16|c[3]>>>16)+(c[2]<<16|c[2]>>>16)|0,t[5]=c[5]+(c[4]<<8|c[4]>>>24)+c[3]|0,t[6]=c[6]+(c[5]<<16|c[5]>>>16)+(c[4]<<16|c[4]>>>16)|0,t[7]=c[7]+(c[6]<<8|c[6]>>>24)+c[5]|0}var r=t,i=r.lib,n=i.StreamCipher,s=r.algo,o=[],a=[],c=[],u=s.Rabbit=n.extend({_doReset:function(){for(var t=this._key.words,r=this.cfg.iv,i=0;i<4;i++)t[i]=16711935&(t[i]<<8|t[i]>>>24)|4278255360&(t[i]<<24|t[i]>>>8);var n=this._X=[t[0],t[3]<<16|t[2]>>>16,t[1],t[0]<<16|t[3]>>>16,t[2],t[1]<<16|t[0]>>>16,t[3],t[2]<<16|t[1]>>>16],s=this._C=[t[2]<<16|t[2]>>>16,4294901760&t[0]|65535&t[1],t[3]<<16|t[3]>>>16,4294901760&t[1]|65535&t[2],t[0]<<16|t[0]>>>16,4294901760&t[2]|65535&t[3],t[1]<<16|t[1]>>>16,4294901760&t[3]|65535&t[0]];this._b=0;for(var i=0;i<4;i++)e.call(this);for(var i=0;i<8;i++)s[i]^=n[i+4&7];if(r){var o=r.words,a=o[0],c=o[1],u=16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8),h=16711935&(c<<8|c>>>24)|4278255360&(c<<24|c>>>8),f=u>>>16|4294901760&h,l=h<<16|65535&u;s[0]^=u,s[1]^=f,s[2]^=h,s[3]^=l,s[4]^=u,s[5]^=f,s[6]^=h,s[7]^=l;for(var i=0;i<4;i++)e.call(this)}},_doProcessBlock:function(t,r){var i=this._X;e.call(this),o[0]=i[0]^i[5]>>>16^i[3]<<16,o[1]=i[2]^i[7]>>>16^i[5]<<16,o[2]=i[4]^i[1]>>>16^i[7]<<16,o[3]=i[6]^i[3]>>>16^i[1]<<16;for(var n=0;n<4;n++)o[n]=16711935&(o[n]<<8|o[n]>>>24)|4278255360&(o[n]<<24|o[n]>>>8),t[r+n]^=o[n]},blockSize:4,ivSize:2});r.Rabbit=n._createHelper(u)}(),t.Rabbit})},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(){for(var t=this._S,e=this._i,r=this._j,i=0,n=0;n<4;n++){e=(e+1)%256,r=(r+t[e])%256;var s=t[e];t[e]=t[r],t[r]=s,i|=t[(t[e]+t[r])%256]<<24-8*n}return this._i=e,this._j=r,i}var r=t,i=r.lib,n=i.StreamCipher,s=r.algo,o=s.RC4=n.extend({_doReset:function(){for(var t=this._key,e=t.words,r=t.sigBytes,i=this._S=[],n=0;n<256;n++)i[n]=n;for(var n=0,s=0;n<256;n++){var o=n%r,a=e[o>>>2]>>>24-o%4*8&255;s=(s+i[n]+a)%256;var c=i[n];i[n]=i[s],i[s]=c}this._i=this._j=0},_doProcessBlock:function(t,r){t[r]^=e.call(this)},keySize:8,ivSize:0});r.RC4=n._createHelper(o);var a=s.RC4Drop=o.extend({cfg:o.cfg.extend({drop:192}),_doReset:function(){o._doReset.call(this);for(var t=this.cfg.drop;t>0;t--)e.call(this)}});r.RC4Drop=n._createHelper(a)}(),t.RC4})}])}); 3 | //# sourceMappingURL=secure-ls.min.js.map --------------------------------------------------------------------------------