local-key-storage.html
1 <!doctype html> 2 <html> 3 <head> 4 <script src="Base58.js"></script> 5 <script src="nacl_factory.js"></script> 6 <script> 7 var cachedPrivateKey; 8 9 // Returns private key, or null if unable to decrypt or password is missing, etc. 10 function getPrivateKey() { 11 // If we have decrypted private key from local storage already then return it. 12 if (cachedPrivateKey != undefined) 13 return cachedPrivateKey; 14 15 var storedData = getLocallyStoredData(); 16 17 // If there is nothing in local storage then we can't provide a private key. 18 // Prompt user for mnemonic phrase and new password so private key can be stored. 19 if (storedData == undefined) 20 return savePrivateKey(); 21 22 // We need also password to decrypt private key. 23 24 // Prompting user for password and checking password isn't empty, etc. goes here 25 var password = document.getElementById('password').value; 26 if (password == "") { 27 document.getElementById('passwordContainer').style.display = ""; 28 return null; 29 } 30 31 // Use password to create encryption key used to decrypt private key 32 var naclPromise = nacl_factory.instantiate(function (nacl) { 33 // Stored data is made up of salt(hex) + ':' + encrypted private key(hex) 34 var storedDataParts = storedData.split(':'); 35 36 var salt = nacl.from_hex(storedDataParts[0]); 37 var encryptedPrivateKey = nacl.from_hex(storedDataParts[1]); 38 39 // Convert password to Uint8Array 40 var passwordArray = nacl.encode_utf8(password); 41 42 // Use salt & password to calculate decryption key 43 var decryptionKey = deriveStorageKey(nacl, salt, passwordArray); 44 45 // Decrypt private key - throws exception on failure (e.g. wrong password) 46 var decryptedPrivateKey = nacl.crypto_secretbox_open(encryptedPrivateKey, salt, decryptionKey); 47 48 // Decryption worked so we can hide password input 49 document.getElementById('passwordContainer').style.display = "none"; 50 51 // Convert decrypted private key to string 52 cachedPrivateKey = nacl.decode_utf8(decryptedPrivateKey); 53 }); 54 55 return naclPromise; 56 } 57 58 function savePrivateKey() { 59 var phrase = document.getElementById('phrase').value; 60 var newPassword = document.getElementById('newPassword').value; 61 62 if (phrase == "" || newPassword == "") { 63 document.getElementById('setupContainer').style.display = ""; 64 return null; 65 } 66 67 // Converting mnemonic phrase to private key goes here 68 69 // For demo we use fake private key 70 var privateKey = 'FakePrivateKey from mnemonic: ' + phrase; 71 72 // Use password to create encryption key used to encrypt private key 73 var naclPromise = nacl_factory.instantiate(function (nacl) { 74 // Generate "salt" used to protect encrypted key 75 var salt = nacl.crypto_secretbox_random_nonce(); 76 77 // Convert password to Uint8Array 78 var passwordArray = nacl.encode_utf8(newPassword); 79 80 // Use salt & password to calculate encryption key 81 var encryptionKey = deriveStorageKey(nacl, salt, passwordArray); 82 83 // Convert private key to Uint8Array 84 var privateKeyArray = nacl.encode_utf8(privateKey); 85 86 var encryptedPrivateKey = nacl.crypto_secretbox(privateKeyArray, salt, encryptionKey); 87 88 // Stored data is made up of salt(hex) + ':' + encrypted private key(hex) 89 var dataToStore = nacl.to_hex(salt) + ':' + nacl.to_hex(encryptedPrivateKey); 90 91 setLocallyStoredData(dataToStore); 92 93 // Save worked so we can hide inputs for mnemonic phrase and password 94 document.getElementById('setupContainer').style.display = "none"; 95 96 cachedPrivateKey = privateKey; 97 }); 98 99 return naclPromise; 100 } 101 102 103 function deriveStorageKey(nacl, salt, passwordArray) { 104 // First round 105 var toBeHashed = concatUint8Arrays(salt, passwordArray); 106 107 var hash = nacl.crypto_hash_sha256(toBeHashed); 108 109 // Many rounds of further hashing 110 111 // To be more efficient, we only need to do this once 112 toBeHashed = new Uint8Array(passwordArray.length + hash.length); 113 toBeHashed.set(passwordArray, 0); 114 115 for (var rounds = 0; rounds < 100; ++rounds) { 116 toBeHashed.set(hash, passwordArray.length); 117 118 hash = nacl.crypto_hash_sha256(toBeHashed); 119 } 120 121 return hash; 122 } 123 124 function clearPrivateKey() { 125 clearLocallyStoredData(); 126 127 cachedPrivateKey = null; 128 129 document.getElementById('privateKey').innerHTML = ""; 130 131 document.getElementById('passwordContainer').style.display = "none"; 132 document.getElementById('setupContainer').style.display = ""; 133 } 134 135 function concatUint8Arrays(array1, array2) { 136 var bigArray = new Uint8Array(array1.length + array2.length); 137 bigArray.set(array1, 0); 138 bigArray.set(array2, array1.length); 139 return bigArray; 140 } 141 142 function getLocallyStoredData() { 143 return window.localStorage.getItem("encryptedPrivateKey"); 144 } 145 146 function setLocallyStoredData(data) { 147 window.localStorage.setItem("encryptedPrivateKey", data); 148 } 149 150 function clearLocallyStoredData() { 151 window.localStorage.removeItem("encryptedPrivateKey"); 152 } 153 154 window.addEventListener('load', getPrivateKey, false); 155 </script> 156 <style> 157 DIV { 158 margin-bottom: 40px; 159 } 160 161 #privateKey { 162 background: #eeeeee; 163 width: 600px; 164 display: inline-block; 165 } 166 167 #privateKey:empty:before { 168 content: "\200b"; /* Unicode zero-width space character to force span to have content */ 169 } 170 </style> 171 </head> 172 <body> 173 <div id="activeContainer"> 174 Decrypted private key: <span id="privateKey"></span><br> 175 <button onclick="clearPrivateKey(); return false">Clear Storage</button><br> 176 <button onclick="window.location = window.location; return false">Reload page</button><br> 177 </div> 178 179 <div id="passwordContainer" style="display: none"> 180 For decrypting stored private key:<br> 181 Password: <input type="password" name="password" id="password"><br> 182 <button onclick="Promise.resolve(getPrivateKey()).then(function() { document.getElementById('privateKey').innerHTML = cachedPrivateKey; }).catch(function() { window.alert('wrong password'); }); return false">Decrypt</button> 183 </div> 184 185 <div id="setupContainer" style="display: none"> 186 For storing private key:<br> 187 Mnemonic phrase: <input type="text" name="phrase" id="phrase"><br> 188 New password: <input type="password" name="newPassword" id="newPassword"><br> 189 <button onclick="Promise.resolve(savePrivateKey()).then(function() { document.getElementById('privateKey').innerHTML = cachedPrivateKey; }); return false">Save</button> 190 </div> 191 </body> 192 </html>