Web Crypto

generateKey()
This generates a new key (for symmetric algorithms) or key pair (for public-key algorithms). const result = crypto.subtle.generateKey(algorithm, extractable, keyUsages); extractable is a Boolean indicating whether it will be possible to export the key using SubtleCrypto.exportKey() or SubtleCrypto.wrapKey().

keyUsages is an Array indicating what can be done with the newly generated key. Possible values for array elements are: let keyPair = window.crypto.subtle.generateKey( { name: "RSA-OAEP", modulusLength: 4096, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" }, true, ["encrypt", "decrypt"]);
encrypt() / decrypt()
This encrypts and decrypts data. function getMessageEncoding() { const messageBox = document.querySelector("#msg"); let message = messageBox.value; let enc = new TextEncoder(); return enc.encode(message); } function encryptMessage(publicKey) { let encoded = getMessageEncoding(); return window.crypto.subtle.encrypt( { name: "RSA-OAEP" }, publicKey, encoded ); }function decryptMessage(privateKey, ciphertext) { return window.crypto.subtle.decrypt( { name: "RSA-OAEP" }, privateKey, ciphertext ); } Algorithms:
sign() / verify()
This creates and verifies a digital signature. const signature = crypto.subtle.sign(algorithm, key, data); const result = crypto.subtle.verify(algorithm, key, signature, data);function getMessageEncoding() { const messageBox = document.querySelector("#msg"); let message = messageBox.value; let enc = new TextEncoder(); return enc.encode(message); } let encoded = getMessageEncoding(); let signature = await window.crypto.subtle.sign("RSASSA-PKCS1-v1_5", privateKey, encoded);function getMessageEncoding() { const messageBox = document.querySelector("#msg"); let message = messageBox.value; let enc = new TextEncoder(); return enc.encode(message); } async function verifyMessage(publicKey) { const signatureValue = document.querySelector("#val"); signatureValue.classList.remove("valid", "invalid"); let encoded = getMessageEncoding(); let result = await window.crypto.subtle.verify( "RSASSA-PKCS1-v1_5", publicKey, signature, encoded ); signatureValue.classList.add(result ? "valid" : "invalid");} Algorithms:
digest()
A digest is a short fixed-length hash derived from some variable-length input. Cryptographic digests should exhibit collision-resistance, meaning that it's hard to come up with two different inputs that have the same digest value. const digest = crypto.subtle.digest(algorithm, data);const text = `An obscure body in the S-K System, your majesty. The inhabitants refer to it as the planet Earth.`; async function digestMessage(message) { const msgUint8 = new TextEncoder().encode(message); const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); return hashHex; } const digestHex = await digestMessage(text); console.log(digestHex); Algorithms:
deriveKey() / deriveBits()
This is used to derive a secret key from a master key. /* Derive an AES key, given: - our ECDH private key - their ECDH public key */ function deriveSecretKey(privateKey, publicKey) { return window.crypto.subtle.deriveKey( { name: "ECDH", public: publicKey }, privateKey, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"] ); } async function agreeSharedSecretKey() { /* 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. */ let alicesKeyPair = await crypto.subtle.generateKey( { name: "ECDH", namedCurve: "P-384" }, false, ["deriveKey"]); let bobsKeyPair = await crypto.subtle.generateKey( { name: "ECDH", namedCurve: "P-384" }, false, ["deriveKey"]); // Alice then generates a secret key using her private key and Bob's public key. let alicesSecretKey = await deriveSecretKey(alicesKeyPair.privateKey, bobsKeyPair.publicKey); // Bob generates the same secret key using his private key and Alice's public key. let bobsSecretKey = await deriveSecretKey(bobsKeyPair.privateKey, alicesKeyPair.publicKey); // Alice can then use her copy of the secret key to encrypt a // message to Bob. let encryptButton = document.querySelector(".ecdh .encrypt-button"); encryptButton.addEventListener("click", () => { encrypt(alicesSecretKey); }); // Bob can use his copy to decrypt the message. let decryptButton = document.querySelector(".ecdh .decrypt-button"); decryptButton.addEventListener("click", () => { decrypt(bobsSecretKey); });}async function deriveSharedSecret(privateKey, publicKey) { const sharedSecret = await crypto.subtle.deriveBits( { name: "ECDH", namedCurve: "P-384", public: publicKey }, privateKey, 128 ); 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); }); }); Algorithms:
wrapKey() / unwrapKey()
Wrapping a key exports the key in an external, portable format, then encrypts the exported key. This helps protect it in untrusted environments, such as inside an otherwise unprotected data store or in transmission over an unprotected network. let salt; // Get some key material to use as input to the deriveKey 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"] ); } // Given some key material and some random saltderive an AES-KW key using PBKDF2. function getKey(keyMaterial, salt) { return window.crypto.subtle.deriveKey( { "name": "PBKDF2", salt: salt, "iterations": 100000, "hash": "SHA-256" }, keyMaterial, { "name": "AES-KW", "length": 256}, true, [ "wrapKey", "unwrapKey" ] ); } // Wrap the given key. async function wrapCryptoKey(keyToWrap) { // get the key encryption key const keyMaterial = await getKeyMaterial(); salt = window.crypto.getRandomValues(new Uint8Array(16)); const wrappingKey = await getKey(keyMaterial, salt); return window.crypto.subtle.wrapKey("raw", keyToWrap, wrappingKey, "AES-KW"); } // Generate an encrypt/decrypt secret key, then wrap it. window.crypto.subtle.generateKey( { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"] ).then((secretKey) => { return wrapCryptoKey(secretKey); }).then((wrappedKey) => { console.log(wrappedKey); });/*Salt that is to be used in derivation of the key-wrapping key, alongside the password the user supplies. This must match the salt value that was originally used to derivethe key.*/ const saltBytes = [89,113,135,234,168,204,21,36,55,93,1,132,242,242,192,156]; // The wrapped key itself. const wrappedKeyBytes = [171,223,14,36,201,233,233,120,164,68,217,192,226,80,224,39,199,235,239,60,212,169,100,23,61,54,244,197,160,80,109,230,207,225,57,197,175,71,80,209]; // Convert an array of byte values to an ArrayBuffer. function bytesToArrayBuffer(bytes) { const bytesAsArrayBuffer = new ArrayBuffer(bytes.length); const bytesUint8 = new Uint8Array(bytesAsArrayBuffer); bytesUint8.set(bytes); return bytesAsArrayBuffer; } // Get some key material to use as input to the deriveKey method. The key material is a password supplied by the user. function getKeyMaterial() { let password = window.prompt("Enter your password"); let enc = new TextEncoder(); return window.crypto.subtle.importKey( "raw", enc.encode(password), {name: "PBKDF2"}, false, ["deriveBits", "deriveKey"] ); } // Derive an AES-KW key using PBKDF2. async function getUnwrappingKey() { // 1. get the key material (user-supplied password) const keyMaterial = await getKeyMaterial(); // 2 initialize the salt parameter. // The salt must match the salt originally used to derive the key. // In this example it's supplied as a constant "saltBytes". const saltBuffer = bytesToArrayBuffer(saltBytes); // 3 derive the key from key material and salt return window.crypto.subtle.deriveKey( { "name": "PBKDF2", salt: saltBuffer, "iterations": 100000, "hash": "SHA-256" }, keyMaterial, { "name": "AES-KW", "length": 256}, true, [ "wrapKey", "unwrapKey" ] ); } /*Unwrap an AES secret key from an ArrayBuffer containing the raw bytes. Takes an array containing the bytes, and returns a Promise that will resolve to a CryptoKey representing the secret key.*/ async function unwrapSecretKey(wrappedKey) { // 1. get the unwrapping key const unwrappingKey = await getUnwrappingKey(); // 2. initialize the wrapped key const wrappedKeyBuffer = bytesToArrayBuffer(wrappedKey); // 3. unwrap the key return window.crypto.subtle.unwrapKey( "raw", // import format wrappedKeyBuffer, // ArrayBuffer representing key to unwrap unwrappingKey, // CryptoKey representing key encryption key "AES-KW", // algorithm identifier for key encryption key "AES-GCM", // algorithm identifier for key to unwrap true, // extractability of key to unwrap ["encrypt", "decrypt"] // key usages for key to unwrap );} Algorithms:
exportKey() / importKey()
This converts a key to/from an external portable format. const result = crypto.subtle.exportKey(format, key); const result = crypto.subtle.importKey( format, keyData, algorithm, extractable, keyUsages); const jwkEcKey = { "crv": "P-384", "d": "wouCtU7Nw4E8_7n5C1-xBjB4xqSb_liZhYM6Q8N…", "ext": true, "key_ops": ["sign"], "kty": "EC", "x": "SzrRXmyI8VWFJg1dPUNbFcc9jZvjZEfH7ulKI1cwu…", "y": "hHUag3OvDzEr0uUQND4PXHQTXP5IDGdYhJhL-Wy…" }; // Import a PEM encoded RSA private key, to use for RSA-PSS signing. Takes a string containing the PEM encoded key, and returns a Promise that will resolve to a CryptoKey representing the private key. function importPrivateKey(jwk) { return window.crypto.subtle.importKey( "jwk", jwk, { name: "ECDSA", namedCurve: "P-384"}, true, ["sign"] ); }