You're Invited: Meet the Socket team at BSidesSF and RSAC - April 27 - May 1.RSVP
Socket
Sign inDemoInstall
Socket

crypto-storage

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

crypto-storage - npm Package Compare versions

Comparing version

to
0.4.0

.eslintrc

278

index.js
'use strict'
const { EventEmitter } = require('events')
const SALT_KEY = 'salt_key'
const IV_KEY = 'iv_key'
const PASSWORD_LENGTH = 5
const {EventEmitter} = require('events')
const {crypto, utils} = require('./lib')
const {
SALT_KEY,
IV_KEY,
PASSWORD_LENGTH,
NAME_LENGTH,
TABLE_NAME_KEY,
} = require('./constant')
function CryptoStorage (password) {
if (!(this instanceof CryptoStorage)) return new CryptoStorage(password)
const {getDerivedKey, getIv} = crypto
const {
encode,
decode,
arrayBufferToBase64,
base64ToArrayBuffer,
hashString,
} = utils
function CryptoStorage(ctx) {
if (!(this instanceof CryptoStorage)) return new CryptoStorage(ctx)
EventEmitter.call(this)

@@ -14,6 +28,8 @@

this._ready = false
this._userPw = null
this._userPassword = null
this._userName = null
this._storage = null
// init
this.open(password)
this.open(ctx)
}

@@ -28,2 +44,3 @@

}
this._storage = window.localStorage
resolve()

@@ -33,40 +50,97 @@ })

CryptoStorage.prototype._setPassword = async function (password) {
if (!password || typeof password !== 'string' || password.length < PASSWORD_LENGTH) {
throw new Error('password should be a string of 5 characters')
CryptoStorage.prototype._setTableName = function setTableName(name) {
const rawTable = this._storage.getItem(TABLE_NAME_KEY)
const hashName = hashString(name)
if (!rawTable) {
this._storage.setItem(TABLE_NAME_KEY, JSON.stringify([hashName]))
} else {
const tableName = JSON.parse(rawTable)
if (!Array.isArray(tableName)) {
throw new Error(
'Table name was corrupted you should clear storage and kill instances',
)
} else {
if (tableName.includes(hashName)) {
throw new Error(`Name ${name} is already used`)
} else {
tableName.push(hashName)
}
}
}
const bufferPW = new TextEncoder('utf-8').encode(password)
this._userPw = await window.crypto.subtle.importKey(
}
CryptoStorage.prototype._setContext = async function setContext(ctx) {
const {password, name} = ctx
if (!name || typeof name !== 'string' || name.length < NAME_LENGTH) {
throw new Error(`name should be a string of ${NAME_LENGTH} characters`)
} else {
this._setTableName(name)
}
if (
!password ||
typeof password !== 'string' ||
password.length < PASSWORD_LENGTH
) {
throw new Error(`name should be a string of ${PASSWORD_LENGTH} characters`)
}
const bufferName = new TextEncoder('utf-8').encode(name)
this._userName = await window.crypto.subtle.importKey(
'raw',
bufferPW,
bufferName,
'PBKDF2',
false,
['deriveKey']
['deriveKey'],
)
return this._userPw
const bufferPassword = new TextEncoder('utf-8').encode(password)
this._userPassword = await window.crypto.subtle.importKey(
'raw',
bufferPassword,
'PBKDF2',
false,
['deriveKey'],
)
return {password: this._userPassword, name: this._userName}
}
CryptoStorage.prototype.setItem = async function (key, value) {
if (!this._userPw || !this._ready) throw new Error('CryptoStorage instance is not ready')
if (!key || !value) throw new Error('key (String) and value (String | Array) args are required')
if (typeof key !== 'string') throw new Error('key (String) arg should be a string')
if (!this._userPassword || !this._userName || !this._ready)
throw new Error('CryptoStorage instance is not ready')
if (!key || !value)
throw new Error('key (String) and value (String | Array) args are required')
if (typeof key !== 'string')
throw new Error('key (String) arg should be a string')
const hashedKey = hashString(key)
const hashedKey = hashString(key + this._userName)
const bufferValue = encode(value)
const iv = getIv()
const algorithm = { name: 'AES-GCM', iv }
const derivedKey = await getDerivedKey(this._userPw)
const iv = getIv(this._storage)
const algorithm = {name: 'AES-GCM', iv}
const derivedKey = await getDerivedKey(this._userPassword, this._storage)
const cryptoValue = await window.crypto.subtle.encrypt(algorithm, derivedKey, bufferValue)
const cryptoValue = await window.crypto.subtle.encrypt(
algorithm,
derivedKey,
bufferValue,
)
const formattedCryptoValue = arrayBufferToBase64(cryptoValue)
window.localStorage.setItem(hashedKey, formattedCryptoValue)
this.emit('data', { [key]: value })
const unEncryptedData = {[key]: value}
this._storage.setItem(hashedKey, formattedCryptoValue)
this.emit('data', unEncryptedData)
return unEncryptedData
}
CryptoStorage.prototype.getItem = async function (key) {
if (!this._userPw || !this._ready) throw new Error('CryptoStorage instance is not ready')
if (!key || typeof key !== 'string') throw new Error('key arg (String) is required')
if (!this._userPassword || !this._ready)
throw new Error('CryptoStorage instance is not ready')
if (!key || typeof key !== 'string')
throw new Error('key arg (String) is required')
const hashedKey = hashString(key)
const item = window.localStorage.getItem(hashedKey)
const hashedKey = hashString(key + this._userName)
const item = this._storage.getItem(hashedKey)
if (!item) {

@@ -77,6 +151,11 @@ console.error(`key '${key}' are not store in the CryptoStorage instance`)

const base64Value = base64ToArrayBuffer(item)
const iv = getIv()
const algorithm = { name: 'AES-GCM', iv }
const derivedKey = await getDerivedKey(this._userPw)
const bufferValue = await window.crypto.subtle.decrypt(algorithm, derivedKey, base64Value)
const iv = getIv(this._storage)
const algorithm = {name: 'AES-GCM', iv}
const derivedKey = await getDerivedKey(this._userPassword, this._storage)
const bufferValue = await window.crypto.subtle.decrypt(
algorithm,
derivedKey,
base64Value,
)
return decode(bufferValue)

@@ -86,8 +165,11 @@ }

CryptoStorage.prototype.removeItem = function (key) {
if (!this._userPw || !this._ready) throw new Error('CryptoStorage instance is not ready')
if (!key || typeof key !== 'string') throw new Error('key arg (String) is required')
if (!this._userPassword || !this._ready)
throw new Error('CryptoStorage instance is not ready')
if (!key || typeof key !== 'string')
throw new Error('key arg (String) is required')
if (key === SALT_KEY || key === IV_KEY) throw new Error('unsafe operation')
const hashedKey = hashString(key)
const item = window.localStorage.getItem(hashedKey)
const hashedKey = hashString(key + this._userName)
const item = this._storage.getItem(hashedKey)
if (!item) {

@@ -97,19 +179,22 @@ console.error(`key '${key}' are not store in the CryptoStorage instance`)

}
window.localStorage.removeItem(hashedKey)
this._storage.removeItem(hashedKey)
return key
}
CryptoStorage.prototype.open = function (password) {
Promise.all([
this._checkStorage(),
this._setPassword(password)
]).then(() => {
this._ready = true
this.emit('ready', null)
}).catch(error => {
this.emit('ready', error)
})
CryptoStorage.prototype.open = function (ctx) {
this._checkStorage().catch(error => this.emit('ready', error))
this._setContext(ctx)
.then(() => {
this._ready = true
this.emit('ready', null)
})
.catch(error => {
this.emit('ready', error)
})
}
CryptoStorage.prototype.close = function () {
this._userPw = null
this._userPassword = null
this._ready = false

@@ -120,94 +205,1 @@ this.emit('close')

module.exports = CryptoStorage
// utils
function encode (value) {
if (!value) return ''
if (typeof value !== 'object') {
value = { '-1': value }
}
return new TextEncoder('utf-8').encode(JSON.stringify(value))
}
function decode (buffer) {
if (!(!buffer || !buffer.constructor || buffer.constructor !== Uint8Array)) {
throw new Error('buffer args (Uint8Array) is required')
}
const stringValue = new TextDecoder('utf-8').decode(buffer)
const objectValue = JSON.parse(stringValue)
if (objectValue['-1']) return objectValue['-1']
return objectValue
}
function generateSalt () {
const salt = window.crypto.getRandomValues(new Uint8Array(8))
window.localStorage.setItem(SALT_KEY, JSON.stringify(Array.from(salt)))
return salt
}
function getSalt () {
return window.localStorage.getItem(SALT_KEY)
? new Uint8Array(JSON.parse(window.localStorage.getItem(SALT_KEY)))
: generateSalt()
}
async function getDerivedKey (pw) {
const salt = getSalt()
const params = {
name: 'PBKDF2',
hash: 'SHA-1',
salt: salt,
iterations: 5000
}
const algorithm = { name: 'AES-GCM', length: 256 }
return window.crypto.subtle.deriveKey(
params,
pw,
algorithm,
false,
['encrypt', 'decrypt']
)
}
function generateIv () {
const nonce = window.crypto.getRandomValues(new Uint8Array(16))
window.localStorage.setItem(IV_KEY, JSON.stringify(Array.from(nonce)))
return nonce
}
function getIv () {
return window.localStorage.getItem(IV_KEY)
? new Uint8Array(JSON.parse(window.localStorage.getItem(IV_KEY)))
: generateIv()
}
function arrayBufferToBase64 (buffer) {
let binary = ''
const bytes = new Uint8Array(buffer)
const len = bytes.byteLength
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i])
}
return window.btoa(binary)
}
function base64ToArrayBuffer (stringBase64) {
const binary = window.atob(stringBase64)
const len = binary.length
const bytes = new Uint8Array(len)
for (let i = 0; i < len; i++) {
bytes[i] += binary.charCodeAt(i)
}
return bytes.buffer
}
function hashString (string) {
let hash = 0
let i
let chr
if (string.length === 0) return hash.toString(16)
for (i = 0; i < string.length; i++) {
chr = string.charCodeAt(i)
hash = ((hash << 5) - hash) + chr
}
return hash.toString(16)
}
{
"name": "crypto-storage",
"version": "0.3.0",
"version": "0.4.0",
"description": "A light & secure way to store data in browser.",
"main": "index.js",
"scripts": {
"test:lint": "standard",
"test:unit": "browserify test.js --debug | tape-run",
"test": "npm run test:lint && npm run test:unit"
"lint": "eslint",
"format": "prettier --write .",
"test": "browserify test.js --debug | tape-run",
"validate": "npm-run-all -p format lint test"
},

@@ -16,10 +17,12 @@ "keywords": [

],
"author": "Tony Gorez (@tonygo_)",
"author": "Tony Gorez (tony-go)",
"license": "MIT",
"devDependencies": {
"browserify": "^16.5.0",
"eslint": "^7.3.1",
"eslint-config-prettier": "^6.11.0",
"npm-run-all": "^4.1.5",
"standard": "^13.1.0",
"prettier": "2.0.5",
"tape-run": "^6.0.0",
"vhs-tape": "^3.2.0",
"browserify": "^16.5.0"
"vhs-tape": "^3.2.0"
},

@@ -26,0 +29,0 @@ "repository": {

@@ -1,2 +0,2 @@

# crypto-storage
# crypto-storage

@@ -9,3 +9,4 @@ ![logo][logo]

**Note** : the current version is a kind of 'beta', development is still in progress ...
**Note** : the current version is a kind of 'beta', development is still in
progress ...

@@ -15,3 +16,3 @@ ## Install

```
npm install crypto-storage
yarn add crypto-storage
```

@@ -25,8 +26,8 @@

const CryptoStorage = require('crypto-storage')
const storage = CryptoStorage('super-pw')
const storage = CryptoStorage({name: 'tester', password: 'super-pw'})
storage.on('ready', async function(err) {
storage.on('ready', async function (err) {
if (err) throw err
console.log('CryptoStorage is ready !')
// know you can append and get data safely

@@ -51,18 +52,25 @@ await storage.setItem('name', 'tony')

#### const storage = CryptoStorage(password: String)
Create a new storage. Event 'ready' should be emitted when instance will be ready.
#### const storage = CryptoStorage({name: String, password: String})
#### await storage.setItem(key: String, value: String|Array|Object)
Set an item in the storage.
Create a new storage. Event 'ready' should be emitted when instance will be
ready.
#### await storage.getItem(key: String)
#### await storage.setItem(key: String, value: String|Array|Object): {[key]: value}
Set an item in the storage and emit a `data` and return it.
#### await storage.getItem(key: String): {[key]: value}
Get an item from the storage.
#### storage.removeItem(key: String)
Remove an item from the storage.
#### storage.removeItem(key: String): String
Remove an item from the storage and return the key.
#### storage.close()
Close access to CryptoStorage disabling set/get/removeItem
#### storage.open(password :String)
Open access to CryptoStorage enabling set/get/removeItem

@@ -73,10 +81,14 @@

#### storage.on('ready')
Emitted when the storage is ready
#### storage.on('data', data)
Emitted when the new data is appended to the storage
#### storage.on('close')
Emitted when CryptoStorage instance is closed
[logo]: https://user-images.githubusercontent.com/22824417/63122825-eb526500-bfa7-11e9-9e6d-d7f8e95b361b.png
[logo]:
https://user-images.githubusercontent.com/22824417/63122825-eb526500-bfa7-11e9-9e6d-d7f8e95b361b.png
const vhs = require('vhs-tape')
const CryptoStorage = require('.')
vhs('CryptoStorage opening depends on password argument', async t => {
const storage = CryptoStorage('password')
const storage2 = CryptoStorage('pass')
vhs('CryptoStorage opening depends on context argument', async t => {
const storage = CryptoStorage({name: 'g-ray', password: 'password'})
const storage2 = CryptoStorage({name: 'A', password: 'pass'})
const storage3 = CryptoStorage()
const storage4 = CryptoStorage({name: 'A'})
const storage5 = CryptoStorage({password: 'my-pass'})
const storage6 = CryptoStorage({name: '', password: 'my-pass'})
storage.on('ready', err => {
t.equal(err, null, 'Good password')
t.equal(err, null, 'Good context')
})
storage2.on('ready', err => {
t.equal(err.message, 'password should be a string of 5 characters', 'Wrong password length')
t.equal(!!err.message, true, 'Wrong password length')
})
storage3.on('ready', err => {
t.equal(err.message, 'password should be a string of 5 characters', 'No password')
t.equal(!!err.message, true, 'No context')
})
storage4.on('ready', err => {
t.equal(!!err.message, true, 'No password in context')
})
storage5.on('ready', err => {
t.equal(!!err.message, true, 'No password in name')
})
storage6.on('ready', err => {
t.equal(!!err.message, true, 'Wrong name length')
})
})
vhs('Set/get crypt items in localStorage', t => {
const storage = CryptoStorage('appendDataTest')
const storage = CryptoStorage({name: 'tester', password: 'appendDataTest'})
storage.on('ready', async err => {
if (err) console.log(err)
// string

@@ -32,3 +48,3 @@ await storage.setItem('name', 'crypto-storage')

// array
const arrayKey = ['gray', 'braet', 'vivien']
const arrayKey = ['g-ray', 'braet', 'vivien']
await storage.setItem('friends', arrayKey)

@@ -39,7 +55,11 @@ const array = await storage.getItem('friends')

// object
await storage.setItem('details', { age: 30, birthplace: 'neptune' })
await storage.setItem('details', {age: 30, birthplace: 'neptune'})
const object = await storage.getItem('details')
t.equal(JSON.stringify(object), JSON.stringify({ age: 30, birthplace: 'neptune' }), 'object')
t.equal(
JSON.stringify(object),
JSON.stringify({age: 30, birthplace: 'neptune'}),
'object',
)
t.end()
})
})