diff --git a/web-crypto/derive-bits/ecdh.js b/web-crypto/derive-bits/ecdh.js new file mode 100644 index 00000000..0b565be8 --- /dev/null +++ b/web-crypto/derive-bits/ecdh.js @@ -0,0 +1,56 @@ +(() => { + + async function deriveSharedSecret(privateKey, publicKey) { + const sharedSecret = await window.crypto.subtle.deriveBits( + { + name: "ECDH", + namedCurve: "P-384", + public: publicKey + }, + privateKey, + 256 + ); + + const buffer = new Uint8Array(sharedSecret, 0, 5); + const sharedSecretValue = document.querySelector(".ecdh .derived-bits-value"); + sharedSecretValue.classList.add("fade-in"); + sharedSecretValue.addEventListener("animationend", () => { + sharedSecretValue.classList.remove("fade-in"); + }); + sharedSecretValue.textContent = `${buffer}...[${sharedSecret.byteLength} bytes total]`; + } + + // Generate 2 ECDH key pairs: one for Alice and one for Bob + // In more normal usage, they would generate their key pairs + // separately and exchange public keys securely + const generateAlicesKeyPair = window.crypto.subtle.generateKey( + { + name: "ECDH", + namedCurve: "P-384" + }, + false, + ["deriveBits"] + ); + + const generateBobsKeyPair = window.crypto.subtle.generateKey( + { + name: "ECDH", + namedCurve: "P-384" + }, + false, + ["deriveBits"] + ); + + Promise.all([generateAlicesKeyPair, generateBobsKeyPair]).then(values => { + const alicesKeyPair = values[0]; + const bobsKeyPair = values[1]; + + const deriveBitsButton = document.querySelector(".ecdh .derive-bits-button"); + deriveBitsButton.addEventListener("click", () => { + // Alice then generates a secret using her private key and Bob's public key. + // Bob could generate the same secret using his private key and Alice's public key. + deriveSharedSecret(alicesKeyPair.privateKey, bobsKeyPair.publicKey); + }); + }); + +})(); diff --git a/web-crypto/derive-bits/index.html b/web-crypto/derive-bits/index.html new file mode 100644 index 00000000..b195bf1c --- /dev/null +++ b/web-crypto/derive-bits/index.html @@ -0,0 +1,55 @@ + + + + + Web Crypto API example + + + + +
+

Web Crypto: deriveBits

+ +
+

This page shows how to use the deriveBits() function of the Web Crypto API. It contains two separate examples, one for PBKDF2 and one for ECDH.

+ +

It's important to note that although both are defined in the API as key derivation functions, PBKDF2 and ECDH have very different use cases and characteristics.

+ +
+

PBKDF2 example

+

The PBKDF2 algorithm is used here to derive some bits from a password.

+

When you click "Derive bits" the example prompts you for a password and then derives some bits from the password using PBKDF2, and a representation of the derived bits is displayed.

+ +
+

ECDH example

+

The ECDH algorithm is more commonly called a "key agreement" algorithm. It enables two parties (conventionally called "Alice" and "Bob"), each of whom has a public/private key pair, to establish a shared secret key.

+

With this example we've created two ECDH key pairs, one for Alice and one for Bob.

+

When you click "Derive bits" the example uses Alice's private key and Bob's public key to derive a shared secret, and a representation of the shared secret is displayed.

+ +
+ +
+ +
+

PBKDF2

+
+
Derived bits:
+ +
+
+ +
+

ECDH

+
+
Shared secret:
+ +
+
+ +
+
+ + + + + diff --git a/web-crypto/derive-bits/pbkdf2.js b/web-crypto/derive-bits/pbkdf2.js new file mode 100644 index 00000000..80037e9e --- /dev/null +++ b/web-crypto/derive-bits/pbkdf2.js @@ -0,0 +1,52 @@ +(() => { + + let salt; + + /* + Get some key material to use as input to the deriveBits method. + The key material is a password supplied by the user. + */ + function getKeyMaterial() { + const password = window.prompt("Enter your password"); + const enc = new TextEncoder(); + return window.crypto.subtle.importKey( + "raw", + enc.encode(password), + {name: "PBKDF2"}, + false, + ["deriveBits", "deriveKey"] + ); + } + + /* + Derive some bits from a password supplied by the user. + */ + async function getDerivedBits() { + const keyMaterial = await getKeyMaterial(); + salt = window.crypto.getRandomValues(new Uint8Array(16)); + const derivedBits = await window.crypto.subtle.deriveBits( + { + "name": "PBKDF2", + salt: salt, + "iterations": 100000, + "hash": "SHA-256" + }, + keyMaterial, + 256 + ); + + const buffer = new Uint8Array(derivedBits, 0, 5); + const derivedBitsValue = document.querySelector(".pbkdf2 .derived-bits-value"); + derivedBitsValue.classList.add("fade-in"); + derivedBitsValue.addEventListener("animationend", () => { + derivedBitsValue.classList.remove("fade-in"); + }); + derivedBitsValue.textContent = `${buffer}...[${derivedBits.byteLength} bytes total]`; + } + + const deriveBitsButton = document.querySelector(".pbkdf2 .derive-bits-button"); + deriveBitsButton.addEventListener("click", () => { + getDerivedBits(); + }); + +})(); diff --git a/web-crypto/derive-bits/style.css b/web-crypto/derive-bits/style.css new file mode 100644 index 00000000..d5ceac2b --- /dev/null +++ b/web-crypto/derive-bits/style.css @@ -0,0 +1,93 @@ +/* General setup */ + +* { + box-sizing: border-box; +} + +html,body { + font-family: sans-serif; + line-height: 1.2rem; +} + +/* Layout and styles */ + +h1 { + color: green; + margin-left: .5rem; +} + +.description, .derive-bits { + margin: 0 .5rem; +} + +.description > p { + margin-top: 0; +} + +.derive-bits { + box-shadow: -1px 2px 5px gray; + padding: .5rem; + margin-bottom: 2rem; +} + +input[type="button"] { + width: 5rem; +} + +.derived-bits-value { + padding-left: .5rem; + font-family: monospace; +} + +/* Whole page grid */ +main { + display: grid; + grid-template-columns: 40rem 1fr; + grid-template-rows: 4rem 1fr; +} + +h1 { + grid-column: 1/2; + grid-row: 1; +} + +.examples { + grid-column: 1; + grid-row: 2; +} + +.description { + grid-column: 2; + grid-row: 2; +} + +/* derive-bits controls grid */ +.derive-bits-controls { + display: grid; + grid-template-columns: 1fr 5rem; + grid-template-rows: 1fr; +} + +.derived-bits { + grid-column-start: 1; + grid-row-start: 1; +} + +.derive-bits-button { + grid-column-start: 2; + grid-row-start: 1; +} + +/* Animate output display */ +.fade-in { + animation: fadein .5s; +} + +@keyframes fadein { + from { + opacity: 0; + } + to { + opacity: 1; + } +}