forked from Qortal/qortal
Demo HTML/JS page to show encrypted local storage of private key
This commit is contained in:
parent
3f2453e404
commit
c83d888a7d
192
src/test/resources/local-key-storage.html
Normal file
192
src/test/resources/local-key-storage.html
Normal file
@ -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…
Reference in New Issue
Block a user