/ src / test / resources / local-key-storage.html
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>