Created
August 16, 2017 07:43
-
-
Save cat-in-136/de9344f7194f0b1856756b701ea10b1c to your computer and use it in GitHub Desktop.
window.crypto test inspired by Firefox Send
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<title>window.crypto test</title> | |
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" type="text/css"> | |
<link href="https://cdnjs.cloudflare.com/ajax/libs/material-design-lite/1.3.0/material.min.css" rel="stylesheet" type="text/css"> | |
<script type="application/javascript" defer="defer" src="https://cdnjs.cloudflare.com/ajax/libs/material-design-lite/1.3.0/material.min.js"></script> | |
</head> | |
<body> | |
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-header | |
mdl-layout--fixed-tabs"> | |
<header class="mdl-layout__header"> | |
<div class="mdl-layout__header-row"> | |
<!-- Title --> | |
<span class="mdl-layout-title">window.crypto test</span> | |
</div> | |
<!-- Tabs --> | |
<div class="mdl-layout__tab-bar mdl-js-ripple-effect"> | |
<a href="#encrypt-tab" class="mdl-layout__tab is-active">Encrypt</a> | |
<a href="#decrypt-tab" class="mdl-layout__tab">Decrypt</a> | |
</div> | |
</header> | |
<main class="mdl-layout__content"> | |
<section class="mdl-layout__tab-panel is-active" id="encrypt-tab"> | |
<div class="mdl-layout__container"> | |
<div class="mdl-grid"> | |
<div class="mdl-button mdl-button--raised mdl-button--colored"> | |
<label for="file-upload">Encrypt File ...</label> | |
<input id="file-upload" name="fileUpload" type="file" style="display:none;"> | |
</div> | |
</div> | |
<output id="file-encrypted" class="mdl-grid"> | |
</output> | |
</div> | |
</section> | |
<section class="mdl-layout__tab-panel" id="decrypt-tab"> | |
<div class="mdl-layout__container"> | |
<div class="mdl-grid"> | |
<div class="mdl-button mdl-button--raised mdl-button--colored"> | |
<label for="file-download">Decrypt File ...</label> | |
<input id="file-download" name="fileDownload" type="file" style="display:none;"> | |
</div> | |
</div> | |
<output id="file-decrypted" class="mdl-grid"> | |
</output> | |
</div> | |
</section> | |
</main> | |
</div> | |
<script type="application/javascript"> | |
function arrayToHex(iv) { | |
let hexStr = ''; | |
// eslint-disable-next-line prefer-const | |
for (let i in iv) { | |
if (iv[i] < 16) { | |
hexStr += '0' + iv[i].toString(16); | |
} else { | |
hexStr += iv[i].toString(16); | |
} | |
} | |
return hexStr; | |
} | |
function hexToArray(str) { | |
const iv = new Uint8Array(str.length / 2); | |
for (let i = 0; i < str.length; i += 2) { | |
iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16); | |
} | |
return iv; | |
} | |
document.getElementById("file-upload").addEventListener("change", function (event) { | |
"use strict"; | |
event.preventDefault(); | |
const file = event.target.files[0]; | |
const stat = document.createElement("div"); | |
stat.setAttribute("class", "mdl-card mdl-shadow--2dp mdl-cell"); | |
stat.innerHTML = ` | |
<div class="mdl-card__title"></div> | |
<div class="mdl-card__supporting-text"></div> | |
<div class="mdl-card__actions"></div> | |
<div class="mdl-card__menu"> | |
<button class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect"> | |
<i class="material-icons">clear</i> | |
</button> | |
</div> | |
`; | |
stat.querySelector(".mdl-card__menu button").addEventListener("click", () => { | |
stat.parentNode.removeChild(stat); | |
}, false); | |
document.getElementById("file-encrypted").appendChild(stat); | |
const iv = window.crypto.getRandomValues(new Uint8Array(12)); | |
let t0 = undefined; | |
Promise.all([ | |
window.crypto.subtle.generateKey( | |
{ | |
name: 'AES-GCM', | |
length: 128 | |
}, | |
true, | |
['encrypt', 'decrypt'] | |
), | |
new Promise((resolve, reject) => { | |
const reader = new FileReader(); | |
reader.readAsArrayBuffer(file); | |
reader.onload = function(event) { | |
const plaintext = new Uint8Array(this.result); | |
resolve(plaintext); | |
}; | |
reader.onerror = function(err) { | |
reject(err); | |
}; | |
}) | |
]).then(([secretKey, plaintext]) => { | |
t0 = performance.now(); | |
stat.querySelector(".mdl-card__supporting-text").innerHTML = ` | |
<div class="mdl-spinner mdl-js-spinner is-active"></div> | |
encrypting... | |
`; | |
console.log("encrypting"); | |
return Promise.all([ | |
window.crypto.subtle.encrypt( | |
{ | |
name: "AES-GCM", | |
iv, | |
tagLength: 128 | |
}, | |
secretKey, | |
plaintext | |
), | |
window.crypto.subtle.exportKey('jwk', secretKey) | |
]); | |
}).then(([encrypted, keydata]) => { | |
console.log("encrypted"); | |
const encryptedURL = window.URL.createObjectURL(new Blob([encrypted])); | |
const encryptedFileName = file.name + ".encrypteddat"; | |
stat.querySelector(".mdl-card__title").appendChild(document.createTextNode(encryptedFileName)); | |
stat.querySelector(".mdl-card__supporting-text").innerHTML = ` | |
<div data-paramname="k"> | |
<label> | |
K (Secret Key): | |
<button class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect"> | |
<i class="material-icons">content_copy</i> | |
</button> | |
<input class="mdl-textfield__input" type="text" readonly="readonly" value="${keydata.k}" onfocus="this.select();"> | |
</label> | |
</div> | |
<div data-paramname="iv"> | |
<label> | |
IV (Inital Vector): | |
<button class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect"> | |
<i class="material-icons">content_copy</i> | |
</button> | |
<input class="mdl-textfield__input" type="text" readonly="readonly" value="${arrayToHex(iv)}" onfocus="this.select();"> | |
</label> | |
</div> | |
<div> | |
Progressing time: ${(performance.now() - t0).toFixed(2)}ms | |
</div> | |
`; | |
for (let param of ["k", "iv"]) { | |
let button = stat.querySelector(`.mdl-card__supporting-text [data-paramname=${param}] button`); | |
let textfield = stat.querySelector(`.mdl-card__supporting-text [data-paramname=${param}] input[type=text]`); | |
button.addEventListener("click", (event) => { | |
event.preventDefault(); | |
textfield.select(); | |
if (document.execCommand("copy")) { | |
console.log("Copied!"); | |
} | |
}, false); | |
} | |
stat.querySelector(".mdl-card__actions").innerHTML = ` | |
<a href="${encryptedURL}" class="mdl-button mdl-button--colored mdl-js-button mdl-js-ripple-effect"> | |
<i class="material-icons">file_download</i> | |
Download | |
</a> | |
`; | |
stat.querySelector(".mdl-card__actions a[href]").setAttribute("download", encryptedFileName); | |
stat.querySelector(".mdl-card__menu button").addEventListener("click", () => { | |
window.URL.revokeObjectURL(encryptedURL); | |
}, false); | |
return Promise.resolve(encryptedURL); | |
}).then((encryptedURL) => { | |
console.log("done : " + encryptedURL); | |
}).catch((error) => { | |
stat.querySelector(".mdl-card__title").innerHTML = "Error"; | |
stat.querySelector(".mdl-card__supporting-text").innerHTML = ""; | |
stat.querySelector(".mdl-card__supporting-text").appendChild(document.createTextNode(error.toString())); | |
stat.querySelector(".mdl-card__actions").innerHTML = ""; | |
console.error(error); | |
}).then(() => { | |
document.getElementById("file-upload").value = ""; | |
}); | |
}, false); | |
document.getElementById("file-download").addEventListener("change", function (event) { | |
"use strict"; | |
event.preventDefault(); | |
const file = event.target.files[0]; | |
const stat = document.createElement("div"); | |
stat.setAttribute("class", "mdl-card mdl-shadow--2dp mdl-cell"); | |
stat.innerHTML = ` | |
<div class="mdl-card__title"></div> | |
<div class="mdl-card__supporting-text"></div> | |
<div class="mdl-card__actions"></div> | |
<div class="mdl-card__menu"> | |
<button class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect"> | |
<i class="material-icons">clear</i> | |
</button> | |
</div> | |
`; | |
stat.querySelector(".mdl-card__menu button").addEventListener("click", () => { | |
stat.parentNode.removeChild(stat); | |
}, false); | |
document.getElementById("file-decrypted").appendChild(stat); | |
let t0 = undefined; | |
Promise.all([ | |
new Promise((resolve, reject) => { | |
let k = window.prompt("K?", ""); | |
if (typeof(k) === "string") { | |
k = k.trim(); | |
} else { | |
return reject(new Error("Cancelled")); | |
} | |
let iv = window.prompt("IV?", ""); | |
if (typeof(iv) === "string") { | |
iv = iv.trim(); | |
} else { | |
return reject(new Error("Cancelled")); | |
} | |
resolve({k, iv}); | |
}), | |
new Promise((resolve, reject) => { | |
const reader = new FileReader(); | |
reader.readAsArrayBuffer(file); | |
reader.onload = function(event) { | |
const encryptedtext = new Uint8Array(this.result); | |
resolve(encryptedtext); | |
}; | |
reader.onerror = function(err) { | |
reject(err); | |
}; | |
}) | |
]).then(([{k, iv}, data]) => { | |
t0 = performance.now(); | |
console.log(`loaded: k=${k}, iv=${iv}`); | |
return Promise.all([ | |
window.crypto.subtle.importKey( | |
'jwk', | |
{ | |
kty: 'oct', | |
k, | |
alg: 'A128GCM', | |
ext: true | |
}, | |
{ | |
name: 'AES-GCM' | |
}, | |
true, | |
['encrypt', 'decrypt'] | |
), | |
Promise.resolve(iv), | |
Promise.resolve(data) | |
]); | |
}).then(([key, iv, data]) => { | |
stat.querySelector(".mdl-card__supporting-text").innerHTML = ` | |
<div class="mdl-spinner mdl-js-spinner is-active"></div> | |
decrypting... | |
`; | |
console.log("decrypting"); | |
return window.crypto.subtle.decrypt( | |
{ | |
name: 'AES-GCM', | |
iv: hexToArray(iv), | |
tagLength: 128 | |
}, | |
key, | |
data | |
); | |
}).then((plaintext) => { | |
console.log("decrypted"); | |
const decryptedURL = window.URL.createObjectURL(new Blob([plaintext])); | |
const decryptedFileName = file.name.replace(/\.encrypteddat$/, ""); | |
stat.querySelector(".mdl-card__title").appendChild(document.createTextNode(decryptedFileName)); | |
stat.querySelector(".mdl-card__supporting-text").innerHTML = ` | |
<div> | |
Progressing time: ${(performance.now() - t0).toFixed(2)}ms | |
</div> | |
`; | |
stat.querySelector(".mdl-card__actions").innerHTML = ` | |
<a href="${decryptedURL}" class="mdl-button mdl-button--colored mdl-js-button mdl-js-ripple-effect"> | |
<i class="material-icons">file_download</i> | |
Download | |
</a> | |
`; | |
stat.querySelector(".mdl-card__actions a[href]").setAttribute("download", decryptedFileName); | |
stat.querySelector(".mdl-card__menu button").addEventListener("click", () => { | |
window.URL.revokeObjectURL(encryptedURL); | |
}, false); | |
return Promise.resolve(decryptedURL); | |
}).catch((error) => { | |
console.error(error); | |
stat.querySelector(".mdl-card__title").innerHTML = "Error"; | |
stat.querySelector(".mdl-card__supporting-text").innerHTML = ""; | |
stat.querySelector(".mdl-card__supporting-text").appendChild(document.createTextNode(error.toString())); | |
stat.querySelector(".mdl-card__actions").innerHTML = ""; | |
console.error(error); | |
}).then(() => { | |
document.getElementById("file-download").value = ""; | |
}); | |
}, false); | |
</script> | |
</body> | |
</html> | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment