Skip to content

Commit 2f4e902

Browse files
Merge pull request mdn#35 from wbamberg/add-webcrypto-derivekey
Added examples for deriveKey
2 parents 9712d74 + 99061f0 commit 2f4e902

File tree

4 files changed

+471
-0
lines changed

4 files changed

+471
-0
lines changed

web-crypto/derive-key/ecdh.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
(() => {
2+
3+
let iv;
4+
5+
/*
6+
Fetch the contents of the "message" textbox, and encode it
7+
in a form we can use for the encrypt operation.
8+
*/
9+
function getMessageEncoding() {
10+
let message = document.querySelector("#ecdh-message").value;
11+
let enc = new TextEncoder();
12+
return enc.encode(message);
13+
}
14+
15+
/*
16+
Encrypt the message using the secret key.
17+
Update the "ciphertextValue" box with a representation of part of
18+
the ciphertext.
19+
*/
20+
async function encrypt(secretKey) {
21+
const ciphertextValue = document.querySelector(".ecdh .ciphertext-value");
22+
ciphertextValue.textContent = "";
23+
const decryptedValue = document.querySelector(".ecdh .decrypted-value");
24+
decryptedValue.textContent = "";
25+
26+
iv = window.crypto.getRandomValues(new Uint8Array(12));
27+
let encoded = getMessageEncoding();
28+
29+
ciphertext = await window.crypto.subtle.encrypt(
30+
{
31+
name: "AES-GCM",
32+
iv: iv
33+
},
34+
secretKey,
35+
encoded
36+
);
37+
38+
let buffer = new Uint8Array(ciphertext, 0, 5);
39+
ciphertextValue.classList.add("fade-in");
40+
ciphertextValue.addEventListener("animationend", () => {
41+
ciphertextValue.classList.remove("fade-in");
42+
});
43+
ciphertextValue.textContent = `${buffer}...[${ciphertext.byteLength} bytes total]`;
44+
}
45+
46+
/*
47+
Decrypt the message using the secret key.
48+
If the ciphertext was decrypted successfully,
49+
update the "decryptedValue" box with the decrypted value.
50+
If there was an error decrypting,
51+
update the "decryptedValue" box with an error message.
52+
*/
53+
async function decrypt(secretKey) {
54+
const decryptedValue = document.querySelector(".ecdh .decrypted-value");
55+
decryptedValue.textContent = "";
56+
decryptedValue.classList.remove("error");
57+
58+
try {
59+
let decrypted = await window.crypto.subtle.decrypt(
60+
{
61+
name: "AES-GCM",
62+
iv: iv
63+
},
64+
secretKey,
65+
ciphertext
66+
);
67+
68+
let dec = new TextDecoder();
69+
decryptedValue.classList.add("fade-in");
70+
decryptedValue.addEventListener("animationend", () => {
71+
decryptedValue.classList.remove("fade-in");
72+
});
73+
decryptedValue.textContent = dec.decode(decrypted);
74+
} catch (e) {
75+
decryptedValue.classList.add("error");
76+
decryptedValue.textContent = "*** Decryption error ***";
77+
}
78+
}
79+
80+
/*
81+
Derive an AES key, given:
82+
- our ECDH private key
83+
- their ECDH public key
84+
*/
85+
function deriveSecretKey(privateKey, publicKey) {
86+
return window.crypto.subtle.deriveKey(
87+
{
88+
name: "ECDH",
89+
public: publicKey
90+
},
91+
privateKey,
92+
{
93+
name: "AES-GCM",
94+
length: 256
95+
},
96+
false,
97+
["encrypt", "decrypt"]
98+
);
99+
}
100+
101+
async function agreeSharedSecretKey() {
102+
// Generate 2 ECDH key pairs: one for Alice and one for Bob
103+
// In more normal usage, they would generate their key pairs
104+
// separately and exchange public keys securely
105+
let alicesKeyPair = await window.crypto.subtle.generateKey(
106+
{
107+
name: "ECDH",
108+
namedCurve: "P-384"
109+
},
110+
false,
111+
["deriveKey"]
112+
);
113+
114+
let bobsKeyPair = await window.crypto.subtle.generateKey(
115+
{
116+
name: "ECDH",
117+
namedCurve: "P-384"
118+
},
119+
false,
120+
["deriveKey"]
121+
);
122+
123+
// Alice then generates a secret key using her private key and Bob's public key.
124+
let alicesSecretKey = await deriveSecretKey(alicesKeyPair.privateKey, bobsKeyPair.publicKey);
125+
126+
// Bob generates the same secret key using his private key and Alice's public key.
127+
let bobsSecretKey = await deriveSecretKey(bobsKeyPair.privateKey, alicesKeyPair.publicKey);
128+
129+
// Alice can then use her copy of the secret key to encrypt a message to Bob.
130+
let encryptButton = document.querySelector(".ecdh .encrypt-button");
131+
encryptButton.addEventListener("click", () => {
132+
encrypt(alicesSecretKey);
133+
});
134+
135+
// Bob can use his copy to decrypt the message.
136+
let decryptButton = document.querySelector(".ecdh .decrypt-button");
137+
decryptButton.addEventListener("click", () => {
138+
decrypt(bobsSecretKey);
139+
});
140+
}
141+
142+
agreeSharedSecretKey();
143+
144+
})();

web-crypto/derive-key/index.html

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Web Crypto API example</title>
6+
<link rel="stylesheet" href="style.css">
7+
</head>
8+
9+
<body>
10+
<main>
11+
<h1>Web Crypto: deriveKey</h1>
12+
13+
<section class="description">
14+
<p>This page shows how to use the <code>deriveKey()</code> function of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API">Web Crypto API</a>. It contains two separate examples, one for PBKDF2 and one for ECDH.</p>
15+
16+
<p>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.</p>
17+
18+
<hr/>
19+
<h2>PBKDF2 example</h2>
20+
<p>The PBKDF2 algorithm is used here to derive a secret key from a password.</p>
21+
22+
<p>When you click "Encrypt" the example prompts you for a password and then derives an AES key from the password using PBKDF2. It then uses that key to encrypt the message, and writes a representation of the ciphertext into the "Ciphertext" output.</p>
23+
24+
<p>When you click "Decrypt" the example prompts you for the password and derives an AES key from the password using PBKDF2. It then uses that key to decrypt the ciphertext, and writes a representation of the decrypted message into the "Decrypted" output.</p>
25+
26+
<p>If the "Decrypt" password doesn't match the original, decryption will fail and an error is shown.</p>
27+
28+
<hr/>
29+
<h2>ECDH example</h2>
30+
<p>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.</p>
31+
32+
<p>With this example we've created two key pairs, one for Alice and one for Bob. Alice derives an AES key using her private key and Bob's public key. Bob independently derives the same key using his private key and Alice's public key.</p>
33+
34+
<p>When you click "Encrypt" the example uses Alice's copy of the key to encrypt a message for Bob.</p>
35+
36+
<p>When you click "Decrypt" the example uses Bob's copy of the key to decrypt the message.</p>
37+
38+
</section>
39+
40+
<section class="examples">
41+
<section class="derive-key pbkdf2">
42+
<h2>PBKDF2</h2>
43+
<section class="derive-key-controls">
44+
<div class="message-control">
45+
<label for="pbkdf2-message">Enter a message to encrypt:</label>
46+
<input type="text" id="pbkdf2-message" name="message" size="25"
47+
value="The bunny hops at teatime">
48+
</div>
49+
<div class="ciphertext">Ciphertext:<span class="ciphertext-value"></span></div>
50+
<div class="decrypted">Decrypted:<span class="decrypted-value"></span></div>
51+
52+
<input class="encrypt-button" type="button" value="Encrypt">
53+
<input class="decrypt-button" type="button" value="Decrypt">
54+
55+
</section>
56+
</section>
57+
58+
<section class="derive-key ecdh">
59+
<h2>ECDH</h2>
60+
<section class="derive-key-controls">
61+
<div class="message-control">
62+
<label for="ecdh-message">Enter a message to encrypt:</label>
63+
<input type="text" id="ecdh-message" name="message" size="25"
64+
value="The bunny hops at teatime">
65+
</div>
66+
<div class="ciphertext">Ciphertext:<span class="ciphertext-value"></span></div>
67+
<div class="decrypted">Decrypted:<span class="decrypted-value"></span></div>
68+
69+
<input class="encrypt-button" type="button" value="Encrypt">
70+
<input class="decrypt-button" type="button" value="Decrypt">
71+
72+
</section>
73+
</section>
74+
75+
</section>
76+
</main>
77+
78+
</body>
79+
<script src="ecdh.js"></script>
80+
<script src="pbkdf2.js"></script>
81+
</html>

web-crypto/derive-key/pbkdf2.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
(() => {
2+
3+
let salt;
4+
let ciphertext;
5+
let iv;
6+
7+
/*
8+
Fetch the contents of the "message" textbox, and encode it
9+
in a form we can use for the encrypt operation.
10+
*/
11+
function getMessageEncoding() {
12+
let message = document.querySelector("#pbkdf2-message").value;
13+
let enc = new TextEncoder();
14+
return enc.encode(message);
15+
}
16+
17+
/*
18+
Get some key material to use as input to the deriveKey method.
19+
The key material is a password supplied by the user.
20+
*/
21+
function getKeyMaterial() {
22+
let password = window.prompt("Enter your password");
23+
let enc = new TextEncoder();
24+
return window.crypto.subtle.importKey(
25+
"raw",
26+
enc.encode(password),
27+
{name: "PBKDF2"},
28+
false,
29+
["deriveBits", "deriveKey"]
30+
);
31+
}
32+
33+
/*
34+
Given some key material and some random salt
35+
derive an AES-GCM key using PBKDF2.
36+
*/
37+
function getKey(keyMaterial, salt) {
38+
return window.crypto.subtle.deriveKey(
39+
{
40+
"name": "PBKDF2",
41+
salt: salt,
42+
"iterations": 100000,
43+
"hash": "SHA-256"
44+
},
45+
keyMaterial,
46+
{ "name": "AES-GCM", "length": 256},
47+
true,
48+
[ "encrypt", "decrypt" ]
49+
);
50+
}
51+
52+
/*
53+
Derive a key from a password supplied by the user, and use the key
54+
to encrypt the message.
55+
Update the "ciphertextValue" box with a representation of part of
56+
the ciphertext.
57+
*/
58+
async function encrypt() {
59+
const ciphertextValue = document.querySelector(".pbkdf2 .ciphertext-value");
60+
ciphertextValue.textContent = "";
61+
const decryptedValue = document.querySelector(".pbkdf2 .decrypted-value");
62+
decryptedValue.textContent = "";
63+
64+
let keyMaterial = await getKeyMaterial();
65+
salt = window.crypto.getRandomValues(new Uint8Array(16));
66+
let key = await getKey(keyMaterial, salt);
67+
iv = window.crypto.getRandomValues(new Uint8Array(12));
68+
let encoded = getMessageEncoding();
69+
70+
ciphertext = await window.crypto.subtle.encrypt(
71+
{
72+
name: "AES-GCM",
73+
iv: iv
74+
},
75+
key,
76+
encoded
77+
);
78+
79+
let buffer = new Uint8Array(ciphertext, 0, 5);
80+
ciphertextValue.classList.add("fade-in");
81+
ciphertextValue.addEventListener("animationend", () => {
82+
ciphertextValue.classList.remove("fade-in");
83+
});
84+
ciphertextValue.textContent = `${buffer}...[${ciphertext.byteLength} bytes total]`;
85+
}
86+
87+
/*
88+
Derive a key from a password supplied by the user, and use the key
89+
to decrypt the ciphertext.
90+
If the ciphertext was decrypted successfully,
91+
update the "decryptedValue" box with the decrypted value.
92+
If there was an error decrypting,
93+
update the "decryptedValue" box with an error message.
94+
*/
95+
async function decrypt() {
96+
const decryptedValue = document.querySelector(".pbkdf2 .decrypted-value");
97+
decryptedValue.textContent = "";
98+
decryptedValue.classList.remove("error");
99+
100+
let keyMaterial = await getKeyMaterial();
101+
let key = await getKey(keyMaterial, salt);
102+
103+
try {
104+
let decrypted = await window.crypto.subtle.decrypt(
105+
{
106+
name: "AES-GCM",
107+
iv: iv
108+
},
109+
key,
110+
ciphertext
111+
);
112+
113+
let dec = new TextDecoder();
114+
decryptedValue.classList.add("fade-in");
115+
decryptedValue.addEventListener("animationend", () => {
116+
decryptedValue.classList.remove("fade-in");
117+
});
118+
decryptedValue.textContent = dec.decode(decrypted);
119+
} catch (e) {
120+
decryptedValue.classList.add("error");
121+
decryptedValue.textContent = "*** Decryption error ***";
122+
}
123+
}
124+
125+
const encryptButton = document.querySelector(".pbkdf2 .encrypt-button");
126+
encryptButton.addEventListener("click", encrypt);
127+
128+
const decryptButton = document.querySelector(".pbkdf2 .decrypt-button");
129+
decryptButton.addEventListener("click", decrypt);
130+
})();

0 commit comments

Comments
 (0)