Browse Source

Demo HTML/JS page to show encrypted local storage of private key

pull/67/head
catbref 5 years ago
parent
commit
c83d888a7d
  1. 192
      src/test/resources/local-key-storage.html

192
src/test/resources/local-key-storage.html

@ -0,0 +1,192 @@
<!doctype html>
<html>
<head>
<script src="Base58.js"></script>
<script src="nacl_factory.js"></script>
<script>
var cachedPrivateKey;
// Returns private key, or null if unable to decrypt or password is missing, etc.
function getPrivateKey() {
// If we have decrypted private key from local storage already then return it.
if (cachedPrivateKey != undefined)
return cachedPrivateKey;
var storedData = getLocallyStoredData();
// If there is nothing in local storage then we can't provide a private key.
// Prompt user for mnemonic phrase and new password so private key can be stored.
if (storedData == undefined)
return savePrivateKey();
// We need also password to decrypt private key.
// Prompting user for password and checking password isn't empty, etc. goes here
var password = document.getElementById('password').value;
if (password == "") {
document.getElementById('passwordContainer').style.display = "";
return null;
}
// Use password to create encryption key used to decrypt private key
var naclPromise = nacl_factory.instantiate(function (nacl) {
// Stored data is made up of salt(hex) + ':' + encrypted private key(hex)
var storedDataParts = storedData.split(':');
var salt = nacl.from_hex(storedDataParts[0]);
var encryptedPrivateKey = nacl.from_hex(storedDataParts[1]);
// Convert password to Uint8Array
var passwordArray = nacl.encode_utf8(password);
// Use salt & password to calculate decryption key
var decryptionKey = deriveStorageKey(nacl, salt, passwordArray);
// Decrypt private key - throws exception on failure (e.g. wrong password)
var decryptedPrivateKey = nacl.crypto_secretbox_open(encryptedPrivateKey, salt, decryptionKey);
// Decryption worked so we can hide password input
document.getElementById('passwordContainer').style.display = "none";
// Convert decrypted private key to string
cachedPrivateKey = nacl.decode_utf8(decryptedPrivateKey);
});
return naclPromise;
}
function savePrivateKey() {
var phrase = document.getElementById('phrase').value;
var newPassword = document.getElementById('newPassword').value;
if (phrase == "" || newPassword == "") {
document.getElementById('setupContainer').style.display = "";
return null;
}
// Converting mnemonic phrase to private key goes here
// For demo we use fake private key
var privateKey = 'FakePrivateKey from mnemonic: ' + phrase;
// Use password to create encryption key used to encrypt private key
var naclPromise = nacl_factory.instantiate(function (nacl) {
// Generate "salt" used to protect encrypted key
var salt = nacl.crypto_secretbox_random_nonce();
// Convert password to Uint8Array
var passwordArray = nacl.encode_utf8(newPassword);
// Use salt & password to calculate encryption key
var encryptionKey = deriveStorageKey(nacl, salt, passwordArray);
// Convert private key to Uint8Array
var privateKeyArray = nacl.encode_utf8(privateKey);
var encryptedPrivateKey = nacl.crypto_secretbox(privateKeyArray, salt, encryptionKey);
// Stored data is made up of salt(hex) + ':' + encrypted private key(hex)
var dataToStore = nacl.to_hex(salt) + ':' + nacl.to_hex(encryptedPrivateKey);
setLocallyStoredData(dataToStore);
// Save worked so we can hide inputs for mnemonic phrase and password
document.getElementById('setupContainer').style.display = "none";
cachedPrivateKey = privateKey;
});
return naclPromise;
}
function deriveStorageKey(nacl, salt, passwordArray) {
// First round
var toBeHashed = concatUint8Arrays(salt, passwordArray);
var hash = nacl.crypto_hash_sha256(toBeHashed);
// Many rounds of further hashing
// To be more efficient, we only need to do this once
toBeHashed = new Uint8Array(passwordArray.length + hash.length);
toBeHashed.set(passwordArray, 0);
for (var rounds = 0; rounds < 100; ++rounds) {
toBeHashed.set(hash, passwordArray.length);
hash = nacl.crypto_hash_sha256(toBeHashed);
}
return hash;
}
function clearPrivateKey() {
clearLocallyStoredData();
cachedPrivateKey = null;
document.getElementById('privateKey').innerHTML = "";
document.getElementById('passwordContainer').style.display = "none";
document.getElementById('setupContainer').style.display = "";
}
function concatUint8Arrays(array1, array2) {
var bigArray = new Uint8Array(array1.length + array2.length);
bigArray.set(array1, 0);
bigArray.set(array2, array1.length);
return bigArray;
}
function getLocallyStoredData() {
return window.localStorage.getItem("encryptedPrivateKey");
}
function setLocallyStoredData(data) {
window.localStorage.setItem("encryptedPrivateKey", data);
}
function clearLocallyStoredData() {
window.localStorage.removeItem("encryptedPrivateKey");
}
window.addEventListener('load', getPrivateKey, false);
</script>
<style>
DIV {
margin-bottom: 40px;
}
#privateKey {
background: #eeeeee;
width: 600px;
display: inline-block;
}
#privateKey:empty:before {
content: "\200b"; /* Unicode zero-width space character to force span to have content */
}
</style>
</head>
<body>
<div id="activeContainer">
Decrypted private key: <span id="privateKey"></span><br>
<button onclick="clearPrivateKey(); return false">Clear Storage</button><br>
<button onclick="window.location = window.location; return false">Reload page</button><br>
</div>
<div id="passwordContainer" style="display: none">
For decrypting stored private key:<br>
Password: <input type="password" name="password" id="password"><br>
<button onclick="Promise.resolve(getPrivateKey()).then(function() { document.getElementById('privateKey').innerHTML = cachedPrivateKey; }).catch(function() { window.alert('wrong password'); }); return false">Decrypt</button>
</div>
<div id="setupContainer" style="display: none">
For storing private key:<br>
Mnemonic phrase: <input type="text" name="phrase" id="phrase"><br>
New password: <input type="password" name="newPassword" id="newPassword"><br>
<button onclick="Promise.resolve(savePrivateKey()).then(function() { document.getElementById('privateKey').innerHTML = cachedPrivateKey; }); return false">Save</button>
</div>
</body>
</html>
Loading…
Cancel
Save