I broke out the encryptor lib into its own module on npm called browser-passworder.feature/default_network_editable
parent
ead8329877
commit
26f1e6cbd2
@ -1,156 +0,0 @@ |
||||
module.exports = { |
||||
|
||||
// Simple encryption methods:
|
||||
encrypt, |
||||
decrypt, |
||||
|
||||
// More advanced encryption methods:
|
||||
keyFromPassword, |
||||
encryptWithKey, |
||||
decryptWithKey, |
||||
|
||||
// Buffer <-> String methods
|
||||
convertArrayBufferViewtoString, |
||||
convertStringToArrayBufferView, |
||||
|
||||
// Buffer <-> Hex string methods
|
||||
serializeBufferForStorage, |
||||
serializeBufferFromStorage, |
||||
|
||||
// Buffer <-> base64 string methods
|
||||
encodeBufferToBase64, |
||||
decodeBase64ToBuffer, |
||||
|
||||
generateSalt, |
||||
} |
||||
|
||||
// Takes a Pojo, returns cypher text.
|
||||
function encrypt (password, dataObj) { |
||||
const salt = this.generateSalt() |
||||
|
||||
return keyFromPassword(password + salt) |
||||
.then(function (passwordDerivedKey) { |
||||
return encryptWithKey(passwordDerivedKey, dataObj) |
||||
}) |
||||
.then(function (payload) { |
||||
payload.salt = salt |
||||
return JSON.stringify(payload) |
||||
}) |
||||
} |
||||
|
||||
function encryptWithKey (key, dataObj) { |
||||
var data = JSON.stringify(dataObj) |
||||
var dataBuffer = convertStringToArrayBufferView(data) |
||||
var vector = global.crypto.getRandomValues(new Uint8Array(16)) |
||||
return global.crypto.subtle.encrypt({ |
||||
name: 'AES-GCM', |
||||
iv: vector, |
||||
}, key, dataBuffer).then(function (buf) { |
||||
var buffer = new Uint8Array(buf) |
||||
var vectorStr = encodeBufferToBase64(vector) |
||||
var vaultStr = encodeBufferToBase64(buffer) |
||||
return { |
||||
data: vaultStr, |
||||
iv: vectorStr, |
||||
} |
||||
}) |
||||
} |
||||
|
||||
// Takes encrypted text, returns the restored Pojo.
|
||||
function decrypt (password, text) { |
||||
const payload = JSON.parse(text) |
||||
const salt = payload.salt |
||||
return keyFromPassword(password + salt) |
||||
.then(function (key) { |
||||
return decryptWithKey(key, payload) |
||||
}) |
||||
} |
||||
|
||||
function decryptWithKey (key, payload) { |
||||
const encryptedData = decodeBase64ToBuffer(payload.data) |
||||
const vector = decodeBase64ToBuffer(payload.iv) |
||||
return crypto.subtle.decrypt({name: 'AES-GCM', iv: vector}, key, encryptedData) |
||||
.then(function (result) { |
||||
const decryptedData = new Uint8Array(result) |
||||
const decryptedStr = convertArrayBufferViewtoString(decryptedData) |
||||
const decryptedObj = JSON.parse(decryptedStr) |
||||
return decryptedObj |
||||
}) |
||||
.catch(function (reason) { |
||||
throw new Error('Incorrect password') |
||||
}) |
||||
} |
||||
|
||||
function convertStringToArrayBufferView (str) { |
||||
var bytes = new Uint8Array(str.length) |
||||
for (var i = 0; i < str.length; i++) { |
||||
bytes[i] = str.charCodeAt(i) |
||||
} |
||||
|
||||
return bytes |
||||
} |
||||
|
||||
function convertArrayBufferViewtoString (buffer) { |
||||
var str = '' |
||||
for (var i = 0; i < buffer.byteLength; i++) { |
||||
str += String.fromCharCode(buffer[i]) |
||||
} |
||||
|
||||
return str |
||||
} |
||||
|
||||
function keyFromPassword (password) { |
||||
var passBuffer = convertStringToArrayBufferView(password) |
||||
return global.crypto.subtle.digest('SHA-256', passBuffer) |
||||
.then(function (passHash) { |
||||
return global.crypto.subtle.importKey('raw', passHash, {name: 'AES-GCM'}, false, ['encrypt', 'decrypt']) |
||||
}) |
||||
} |
||||
|
||||
function serializeBufferFromStorage (str) { |
||||
var stripStr = (str.slice(0, 2) === '0x') ? str.slice(2) : str |
||||
var buf = new Uint8Array(stripStr.length / 2) |
||||
for (var i = 0; i < stripStr.length; i += 2) { |
||||
var seg = stripStr.substr(i, 2) |
||||
buf[i / 2] = parseInt(seg, 16) |
||||
} |
||||
return buf |
||||
} |
||||
|
||||
// Should return a string, ready for storage, in hex format.
|
||||
function serializeBufferForStorage (buffer) { |
||||
var result = '0x' |
||||
var len = buffer.length || buffer.byteLength |
||||
for (var i = 0; i < len; i++) { |
||||
result += unprefixedHex(buffer[i]) |
||||
} |
||||
return result |
||||
} |
||||
|
||||
function unprefixedHex (num) { |
||||
var hex = num.toString(16) |
||||
while (hex.length < 2) { |
||||
hex = '0' + hex |
||||
} |
||||
return hex |
||||
} |
||||
|
||||
function encodeBufferToBase64 (buf) { |
||||
var b64encoded = btoa(String.fromCharCode.apply(null, buf)) |
||||
return b64encoded |
||||
} |
||||
|
||||
function decodeBase64ToBuffer (base64) { |
||||
var buf = new Uint8Array(atob(base64).split('') |
||||
.map(function (c) { |
||||
return c.charCodeAt(0) |
||||
})) |
||||
return buf |
||||
} |
||||
|
||||
function generateSalt (byteCount = 32) { |
||||
var view = new Uint8Array(byteCount) |
||||
global.crypto.getRandomValues(view) |
||||
var b64encoded = btoa(String.fromCharCode.apply(null, view)) |
||||
return b64encoded |
||||
} |
@ -1,71 +0,0 @@ |
||||
var encryptor = require('../../../app/scripts/lib/encryptor') |
||||
|
||||
QUnit.module('encryptor') |
||||
|
||||
QUnit.test('encryptor:serializeBufferForStorage', function (assert) { |
||||
assert.expect(1) |
||||
var buf = new Buffer(2) |
||||
buf[0] = 16 |
||||
buf[1] = 1 |
||||
|
||||
var output = encryptor.serializeBufferForStorage(buf) |
||||
|
||||
var expect = '0x1001' |
||||
assert.equal(expect, output) |
||||
}) |
||||
|
||||
QUnit.test('encryptor:serializeBufferFromStorage', function (assert) { |
||||
assert.expect(2) |
||||
var input = '0x1001' |
||||
var output = encryptor.serializeBufferFromStorage(input) |
||||
|
||||
assert.equal(output[0], 16) |
||||
assert.equal(output[1], 1) |
||||
}) |
||||
|
||||
QUnit.test('encryptor:encrypt & decrypt', function(assert) { |
||||
var done = assert.async(); |
||||
var password, data, encrypted |
||||
|
||||
password = 'a sample passw0rd' |
||||
data = { foo: 'data to encrypt' } |
||||
|
||||
encryptor.encrypt(password, data) |
||||
.then(function(encryptedStr) { |
||||
assert.equal(typeof encryptedStr, 'string', 'returns a string') |
||||
return encryptor.decrypt(password, encryptedStr) |
||||
}) |
||||
.then(function (decryptedObj) { |
||||
assert.deepEqual(decryptedObj, data, 'decrypted what was encrypted') |
||||
done() |
||||
}) |
||||
.catch(function(reason) { |
||||
assert.ifError(reason, 'threw an error') |
||||
done(reason) |
||||
}) |
||||
|
||||
}) |
||||
|
||||
QUnit.test('encryptor:encrypt & decrypt with wrong password', function(assert) { |
||||
var done = assert.async(); |
||||
var password, data, encrypted, wrongPassword |
||||
|
||||
password = 'a sample passw0rd' |
||||
wrongPassword = 'a wrong password' |
||||
data = { foo: 'data to encrypt' } |
||||
|
||||
encryptor.encrypt(password, data) |
||||
.then(function(encryptedStr) { |
||||
assert.equal(typeof encryptedStr, 'string', 'returns a string') |
||||
return encryptor.decrypt(wrongPassword, encryptedStr) |
||||
}) |
||||
.then(function (decryptedObj) { |
||||
assert.equal(!decryptedObj, true, 'Wrong password should not decrypt') |
||||
done() |
||||
}) |
||||
.catch(function(reason) { |
||||
done() |
||||
}) |
||||
}) |
||||
|
||||
|
Loading…
Reference in new issue