diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6f9a44 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode/settings.json diff --git a/README.md b/README.md index 32ccbdb..698e40b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# Website-keysign-example +# Website Key signing and Encryption example -An example showcasing basic cryptography in-browser without using third-party libraries. \ No newline at end of file +An example website showcasing basic cryptography in-browser without using third-party libraries. diff --git a/index.css b/index.css new file mode 100644 index 0000000..76559a5 --- /dev/null +++ b/index.css @@ -0,0 +1,103 @@ +body { + margin: 0; + display: flex; + height: 100vh; + flex-direction: column; + background-color: #20242b; + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.page { + width: 1200px; + margin: 0 auto; + padding: 1rem 0; + box-sizing: border-box; + margin-bottom: 1rem; +} +.page .disclaimer { + font-size: 18px; +} +.page .data-area { + width: 100%; + min-height: 10px; + background-color: #0d0f11; + padding: 10px; + box-sizing: border-box; + border-radius: 10px; + margin-bottom: 1rem; + color: white; +} +.page .data-area .data-title { + font-size: 24px; + margin-bottom: 10px; +} +.page .data-area textarea { + resize: vertical; + width: 100%; + margin: 0; + box-sizing: border-box; + font-family: Verdana, Geneva, Tahoma, sans-serif; + border-radius: 5px; +} +.page .action-area { + display: flex; + flex-direction: row; + margin-bottom: 1rem; + width: 100%; + padding: 5px; + box-sizing: border-box; + background-color: #0d0f11; + border-radius: 10px; +} +.page .action-area button { + width: 130px; + height: 100%; + margin: auto; +} +.page .result-area { + display: flex; + flex-direction: row; + flex-flow: row wrap; + place-content: space-around; +} +.page .result-area .result { + min-width: 150px; + max-width: 49%; + flex-grow: 1; + min-height: 100px; + background-color: #0d0f11; + border-radius: 10px; + padding: 5px; + box-sizing: border-box; + color: gray; + margin-bottom: 10px; + word-wrap: anywhere; +} +.page .result-area .result .result-title { + font-size: 20px; + text-align: center; + text-decoration: underline; + color: white; +} + +@media screen and (max-width: 1210px) { + .page { + width: 95%; + } + .page .result-area .result { + min-width: initial; + max-width: initial; + width: 45%; + margin: 5px; + } +} +@media screen and (max-width: 1010px) { + .page { + width: 95%; + } + .page .result-area .result { + min-width: initial; + max-width: initial; + width: 100%; + } +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..106fe92 --- /dev/null +++ b/index.html @@ -0,0 +1,74 @@ + + + + + + + + In-Browser Keysigning + + +
+
+ This online key signing and encryption example should + NEVER + be used in a production environment. This was made for educational purposes in exploring the use of cryptography in the web without the use of + third-party libraries. You can find the source code for this website here. +
+
+
+ The resulting data is manipulated to allow for viewing in-line in the browser so the relationships can be more easily observed. +
+
+
Your unencrypted text
+ +
+ +
+ + +
+ +
+
+
Encrypted String
+
+
+
+
Decrypted String
+
+
+ +
+
Signature String
+
+
+ +
+
Public Key (Encryption)
+
+
+ +
+
Private Key (Encryption)
+
+
+ +
+
Public Key (Signing)
+
+
+ +
+
Private Key (Signing)
+
+
+ +
+
Results
+
+
+
+
+ + diff --git a/index.js b/index.js new file mode 100644 index 0000000..7dc64d9 --- /dev/null +++ b/index.js @@ -0,0 +1,92 @@ +let public_key; +let private_key; + +let public_signing_key; +let private_signing_key; + +const qs = (selector) => document.querySelector(selector); + +async function generateRSAKeyPair() { + // From what I can gather from online sources, + // the in browser crypto module can not generate keys that can be used for both signing and encryption. + // For this example we will just generate two key pairs, and use them for their recommended purpose. + + // Create Encryption/Decryption keypair + const encryption_keypair = await window.crypto.subtle.generateKey( + { + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537 + hash: "SHA-256", + }, + true, + ["encrypt", "decrypt"] + ); + + // Create signing keypair + const signing_keypair = await window.crypto.subtle.generateKey( + { + name: "RSASSA-PKCS1-v1_5", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537 + hash: "SHA-256", + }, + true, + ["sign", "verify"] + ); + + // These keys will be used to encrypt data + public_key = encryption_keypair.publicKey; + private_key = encryption_keypair.privateKey; + + // These keys will be used to "sign" messages + public_signing_key = signing_keypair.publicKey; + private_signing_key = signing_keypair.privateKey; + + // Update displays + qs("#pub-key-enc").innerText = await _exportKey(public_key, "spki"); + qs("#priv-key-enc").innerText = await _exportKey(private_key, "jwk"); + + qs("#pub-key-sign").innerText = await _exportKey(public_signing_key, "spki"); + qs("#priv-key-sign").innerText = await _exportKey(private_signing_key, "jwk"); +} +async function executeTests() { + const user_string = document.querySelector("#my-secret-string").value; + + // Get our data with our generated keys and user-submitted text + const encrypted_string = await encryptString(user_string); + const decrypted_string = await decryptString(encrypted_string); + const signed_message = await signMessage(user_string); + const message_verified = await verifyMessage(user_string, signed_message); + + // Generate Results + let results = `Message Signature Verified? ${message_verified}
`; + + qs("#encrypted-string").innerText = new Uint8Array(encrypted_string).toString(); + qs("#decrypted-string").innerText = decrypted_string; + qs("#signature-string").innerText = new Uint8Array(signed_message).toString(); + qs("#results").innerHTML = results; +} + +async function encryptString(string_d) { + return await window.crypto.subtle.encrypt({ name: "RSA-OAEP" }, public_key, new TextEncoder().encode(string_d)); +} +async function decryptString(string_d) { + return new TextDecoder().decode(await window.crypto.subtle.decrypt({ name: "RSA-OAEP" }, private_key, string_d)); +} + +async function signMessage(string_d) { + return await window.crypto.subtle.sign({ name: "RSASSA-PKCS1-v1_5" }, private_signing_key, new TextEncoder().encode(string_d)); +} +async function verifyMessage(string_d, signed_message) { + return await window.crypto.subtle.verify("RSASSA-PKCS1-v1_5", public_signing_key, signed_message, new TextEncoder().encode(string_d)); +} + +// This is just used to turn the data into string that can be read. +// Ordinarily you won't need this, as the keys won't need to be visible in the way this website wants them to be. +// (Hint, this is very bad! Do not do this!) +async function _exportKey(key, method) { + const exported = await window.crypto.subtle.exportKey(method, key); + if (exported.e) return JSON.stringify(exported, null, 2); + return String.fromCharCode.apply(null, new Uint8Array(exported)); +} diff --git a/index.scss b/index.scss new file mode 100644 index 0000000..b20631b --- /dev/null +++ b/index.scss @@ -0,0 +1,117 @@ +body { + margin: 0; + display: flex; + height: 100vh; + flex-direction: column; + background-color: #20242b; + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.page { + width: 1200px; + margin: 0 auto; + padding: 1rem 0; + box-sizing: border-box; + margin-bottom: 1rem; + + .disclaimer { + font-size: 18px; + } + + .data-area { + width: 100%; + min-height: 10px; + background-color: #0d0f11; + padding: 10px; + box-sizing: border-box; + border-radius: 10px; + margin-bottom: 1rem; + color: white; + + .data-title { + font-size: 24px; + margin-bottom: 10px; + } + + textarea { + resize: vertical; + width: 100%; + margin: 0; + box-sizing: border-box; + font-family: Verdana, Geneva, Tahoma, sans-serif; + border-radius: 5px; + } + } + + .action-area { + display: flex; + flex-direction: row; + margin-bottom: 1rem; + width: 100%; + padding: 5px; + box-sizing: border-box; + background-color: #0d0f11; + border-radius: 10px; + + button { + width: 130px; + height: 100%; + margin: auto; + } + } + + .result-area { + display: flex; + flex-direction: row; + flex-flow: row wrap; + place-content: space-around; + + .result { + min-width: 150px; + max-width: 49%; + flex-grow: 1; + min-height: 100px; + background-color: #0d0f11; + border-radius: 10px; + padding: 5px; + box-sizing: border-box; + color: gray; + margin-bottom: 10px; + word-wrap: anywhere; + + .result-title { + font-size: 20px; + text-align: center; + text-decoration: underline; + color: white; + } + } + } +} + +@media screen and (max-width: 1210px) { + .page { + width: 95%; + .result-area { + .result { + min-width: initial; + max-width: initial; + width: 45%; + margin: 5px; + } + } + } +} + +@media screen and (max-width: 1010px) { + .page { + width: 95%; + .result-area { + .result { + min-width: initial; + max-width: initial; + width: 100%; + } + } + } +}