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