|
168 | 168 | var impl = ((function(){ |
169 | 169 | const exports = {}; |
170 | 170 |
|
| 171 | +var IV_BITS = 16 * 8; |
| 172 | +var HEX_BITS = 4; |
| 173 | + |
171 | 174 | /** |
172 | 175 | * Salt and encrypt a msg with a password. |
173 | 176 | * Inspired by https://github.com/adonespitogo |
174 | 177 | */ |
175 | 178 | function encrypt(msg, hashedPassphrase) { |
176 | | - var iv = CryptoJS.lib.WordArray.random(128 / 8); |
| 179 | + var iv = CryptoJS.lib.WordArray.random(IV_BITS / 8); |
177 | 180 |
|
178 | 181 | var encrypted = CryptoJS.AES.encrypt(msg, hashedPassphrase, { |
179 | 182 | iv: iv, |
|
183 | 186 |
|
184 | 187 | // iv will be hex 16 in length (32 characters) |
185 | 188 | // we prepend it to the ciphertext for use in decryption |
186 | | - return iv.toString() + encrypted.toString(); |
| 189 | + var encoded = iv.toString() + encrypted.toString(); |
| 190 | + return Promise.resolve(encoded); |
187 | 191 | } |
188 | 192 | exports.encrypt = encrypt; |
189 | 193 |
|
|
196 | 200 | * @returns {string} |
197 | 201 | */ |
198 | 202 | function decrypt(encryptedMsg, hashedPassphrase) { |
199 | | - var iv = CryptoJS.enc.Hex.parse(encryptedMsg.substr(0, 32)); |
200 | | - var encrypted = encryptedMsg.substring(32); |
| 203 | + var ivLength = IV_BITS / HEX_BITS; |
| 204 | + var iv = CryptoJS.enc.Hex.parse(encryptedMsg.substr(0, ivLength)); |
| 205 | + var encrypted = encryptedMsg.substring(ivLength); |
201 | 206 |
|
202 | | - return CryptoJS.AES.decrypt(encrypted, hashedPassphrase, { |
| 207 | + const decrypted = CryptoJS.AES.decrypt(encrypted, hashedPassphrase, { |
203 | 208 | iv: iv, |
204 | 209 | padding: CryptoJS.pad.Pkcs7, |
205 | 210 | mode: CryptoJS.mode.CBC, |
206 | 211 | }).toString(CryptoJS.enc.Utf8); |
| 212 | + |
| 213 | + return Promise.resolve(decrypted); |
207 | 214 | } |
208 | 215 | exports.decrypt = decrypt; |
209 | 216 |
|
|
220 | 227 | iterations: 1000, |
221 | 228 | }); |
222 | 229 |
|
223 | | - return hashedPassphrase.toString(); |
| 230 | + return Promise.resolve(hashedPassphrase.toString()); |
224 | 231 | } |
225 | 232 | exports.hashPassphrase = hashPassphrase; |
226 | 233 |
|
|
230 | 237 | exports.generateRandomSalt = generateRandomSalt; |
231 | 238 |
|
232 | 239 | function signMessage(hashedPassphrase, message) { |
233 | | - return CryptoJS.HmacSHA256( |
| 240 | + const signature = CryptoJS.HmacSHA256( |
234 | 241 | message, |
235 | 242 | CryptoJS.SHA256(hashedPassphrase).toString() |
236 | 243 | ).toString(); |
| 244 | + return Promise.resolve(signature); |
237 | 245 | } |
238 | 246 | exports.signMessage = signMessage; |
239 | 247 |
|
|
254 | 262 | * @param {string} msg |
255 | 263 | * @param {string} passphase |
256 | 264 | * @param {sting} salt |
257 | | - * @returns {string} The encoded text |
| 265 | + * @returns {Promise<string>} The encoded text |
258 | 266 | */ |
259 | 267 | function encode(msg, passphrase, salt) { |
260 | | - const hashedPassphrase = impl.hashPassphrase(passphrase, salt); |
261 | | - const encrypted = impl.encrypt(msg, hashedPassphrase); |
262 | | - // we use the hashed passphrase in the HMAC because this is effectively what will be used a passphrase (so we can store |
263 | | - // it in localStorage safely, we don't use the clear text passphrase) |
264 | | - const hmac = impl.signMessage(hashedPassphrase, encrypted); |
265 | | - return hmac + encrypted; |
| 268 | + return impl.hashPassphrase(passphrase, salt).then(function(hashedPassphrase) { |
| 269 | + return impl.encrypt(msg, hashedPassphrase).then(function(encrypted) { |
| 270 | + return impl.signMessage(hashedPassphrase, encrypted).then(function(hmac) { |
| 271 | + // we use the hashed passphrase in the HMAC because this is effectively what will be used a passphrase (so we can store |
| 272 | + // it in localStorage safely, we don't use the clear text passphrase) |
| 273 | + return hmac + encrypted; |
| 274 | + }); |
| 275 | + }); |
| 276 | + }); |
266 | 277 | } |
267 | 278 | exports.encode = encode; |
268 | 279 |
|
|
272 | 283 | * |
273 | 284 | * @param {*} encoded |
274 | 285 | * @param {*} hashedPassphrase |
275 | | - * @returns {Object} {success: true, decoded: string} | {succss: false, message: string} |
| 286 | + * @returns {Promise<Object>} {success: true, decoded: string} | {succss: false, message: string} |
276 | 287 | */ |
277 | 288 | function decode(signedMsg, hashedPassphrase) { |
278 | 289 | const encryptedHMAC = signedMsg.substring(0, 64); |
279 | 290 | const encryptedMsg = signedMsg.substring(64); |
280 | | - const decryptedHMAC = impl.signMessage(hashedPassphrase, encryptedMsg); |
281 | | - |
282 | | - if (decryptedHMAC !== encryptedHMAC) { |
283 | | - return { success: false, message: "Signature mismatch" }; |
284 | | - } |
285 | | - return { |
286 | | - success: true, |
287 | | - decoded: impl.decrypt(encryptedMsg, hashedPassphrase), |
288 | | - }; |
| 291 | + return impl.signMessage(hashedPassphrase, encryptedMsg).then(function(decryptedHMAC) { |
| 292 | + if (decryptedHMAC !== encryptedHMAC) { |
| 293 | + return { success: false, message: "Signature mismatch" }; |
| 294 | + } |
| 295 | + return impl.decrypt(encryptedMsg, hashedPassphrase).then(function(decoded) { |
| 296 | + return { |
| 297 | + success: true, |
| 298 | + decoded: decoded, |
| 299 | + }; |
| 300 | + }); |
| 301 | + }); |
289 | 302 | } |
290 | 303 | exports.decode = decode; |
291 | 304 |
|
|
316 | 329 | var decode = codec.init(impl).decode; |
317 | 330 |
|
318 | 331 | // variables to be filled when generating the file |
319 | | - var encryptedMsg = '5b8ddda06f7972f55040c4e53ac58581408b427bb14d0856b3e8fa275836489fa2195f5c68889a2c9b81f3bad4d84683U2FsdGVkX1+ZRQKlbRyks/JsZcQ+tKL24t+BOrwQilIyt5/uiwboh274a5/NfJs+iJG7meL6uQlKNUKgVPNW7pL00Wvsj2PTZbve2YuB7Qq1QqZZ/jAM9IXPWAMKs9VoGBEvDw3BUhgiCVOq0ibTYFBz9LO5xcuI1Gwdnw7DNYE2/aadml4/eo4D3yQI7U61vbBo1KYjEGr9rBgoUHnkSw==', |
| 332 | + var encryptedMsg = '8af5413a09610945eaf71ded7559ecaaa93c8414c8e39e3e4951cf8ade51c9a399c664369959d92b91ab900fc4f7f7d6U2FsdGVkX18uzOPNOJ7JLg2NQCum1dvLl7CO+XZCJU6JdN7QwWmx4aSJDqnWfu6lmttosCYmZFfbTg8eT/8NcVoP2O9dCRohqIBzXJpfnFiJ0SRSSxWF/vkImNJUOamBQyd+6ND41LEC7YmkzQAJpu4z7TIrTqXIv37a0wICxwmpFC4AO8SoIYvubRXXVDZipUJQetvK3ZBOCBF6OSZbag==', |
320 | 333 | salt = 'b93bbaf35459951c47721d1f3eaeb5b9', |
321 | 334 | isRememberEnabled = true, |
322 | 335 | rememberDurationInDays = 0; // 0 means forever |
|
332 | 345 | * @returns |
333 | 346 | */ |
334 | 347 | function decryptAndReplaceHtml(hashedPassphrase) { |
335 | | - var result = decode(encryptedMsg, hashedPassphrase); |
336 | | - if (!result.success) { |
337 | | - return false; |
338 | | - } |
339 | | - var plainHTML = result.decoded; |
| 348 | + return decode(encryptedMsg, hashedPassphrase).then(function(result) { |
| 349 | + if (!result.success) { |
| 350 | + return false; |
| 351 | + } |
| 352 | + var plainHTML = result.decoded; |
340 | 353 |
|
341 | | - document.write(plainHTML); |
342 | | - document.close(); |
343 | | - return true; |
| 354 | + document.write(plainHTML); |
| 355 | + document.close(); |
| 356 | + return true; |
| 357 | + }); |
344 | 358 | } |
345 | 359 |
|
346 | 360 | /** |
|
378 | 392 |
|
379 | 393 | if (hashedPassphrase) { |
380 | 394 | // try to decrypt |
381 | | - var isDecryptionSuccessful = decryptAndReplaceHtml(hashedPassphrase); |
382 | | - |
383 | | - // if the decryption is unsuccessful the password might be wrong - silently clear the saved data and let |
384 | | - // the user fill the password form again |
385 | | - if (!isDecryptionSuccessful) { |
386 | | - return clearLocalStorage(); |
387 | | - } |
| 395 | + decryptAndReplaceHtml(hashedPassphrase).then(function(isDecryptionSuccessful) { |
| 396 | + // if the decryption is unsuccessful the password might be wrong - silently clear the saved data and let |
| 397 | + // the user fill the password form again |
| 398 | + if (!isDecryptionSuccessful) { |
| 399 | + return clearLocalStorage(); |
| 400 | + } |
| 401 | + }); |
388 | 402 | } |
389 | 403 | } |
390 | 404 | } |
|
397 | 411 | shouldRememberPassphrase = document.getElementById('staticrypt-remember').checked; |
398 | 412 |
|
399 | 413 | // decrypt and replace the whole page |
400 | | - var hashedPassphrase = impl.hashPassphrase(passphrase, salt); |
401 | | - var isDecryptionSuccessful = decryptAndReplaceHtml(hashedPassphrase); |
402 | | - |
403 | | - if (isDecryptionSuccessful) { |
| 414 | + impl.hashPassphrase(passphrase, salt).then(function(hashedPassphrase) { |
| 415 | + return decryptAndReplaceHtml(hashedPassphrase); |
| 416 | + }).then(function(isDecryptionSuccessful) { |
| 417 | + if (!isDecryptionSuccessful) { |
| 418 | + return alert('Bad passphrase!'); |
| 419 | + } |
404 | 420 | // remember the hashedPassphrase and set its expiration if necessary |
405 | 421 | if (isRememberEnabled && shouldRememberPassphrase) { |
406 | 422 | window.localStorage.setItem(rememberPassphraseKey, hashedPassphrase); |
|
413 | 429 | ); |
414 | 430 | } |
415 | 431 | } |
416 | | - } else { |
417 | | - alert('Bad passphrase!'); |
418 | | - } |
| 432 | + }); |
419 | 433 | }); |
420 | 434 | </script> |
421 | 435 | </body> |
|
0 commit comments