keyboard_backspace wifi_off
wifi_off

Your device is currently offline.
Encryption will not work.

Encryption with pseudo random function (PRF)

This feature is not (yet) supported on your device

The WebAuthn PRF extension enables web apps to derive encryption keys using the user's passkey, without ever accessing the private key itself. This ensures that the private key remains secure and never leaves the device.

With solutions like the Web Crypto API, a web app can also generate encryption keys, but they need to be stored in localStorage or IndexedDB. However, these keys are not protected by the user's passkey and can be stolen if the device is hacked. With the PRF extension, the private key never leaves the device and is never accessed by the web app.

The web app can use the derived encryption keys to securely encrypt and decrypt data without ever having access to the private key itself. This ensures that sensitive data remains protected even if the device is compromised.

After authentication with a passkey, the web app can generate a new encryption by providing a text label. Every time the web app provides the same text label, the same encryption key is derived. This ensures that the same encryption key is used for the same data, providing consistency and security.

Demo

Register a passkey, add some text to encrypt to the first text area and click "Encrypt text". The encrypted text will be displayed in the second text area. Refresh the page or close and reopen the app. Then authenticate with the passkey and the encrypted text will be shown in the second text area again. Click the "Decrypt text" button to see the decrypted text in the first text area.

After authentication, the demo code will regenerate the exact same key using the same text label. While using the same key, you will see that the text is always correctly decrypted.

delete autorenew

Encryption

Code

// create the passkey and provide a text label to derive the encryption key const credential = await navigator.credentials.create({ publicKey: { challenge, rp: { name: "Secure Notes" }, user: { id: userIdBytes, name: "pwa@whatpwacando.today", displayName: "PWA" }, pubKeyCredParams: [{ alg: -7, type: "public-key" }], authenticatorSelection: { userVerification: "required" }, extensions: { prf: { eval: { first: new TextEncoder().encode("prf-key-v1") // the text label to derive the key } } } } }); // get the secret to derive the key const prfResult = credential.getClientExtensionResults().prf.results.first; // create a CryptoKey object from the PRF result const keyMaterial = await crypto.subtle.importKey( "raw", prfResult, "HKDF", false, ["deriveKey"] ); // derive the actual encryption/decryption key const encryptionKey = await crypto.subtle.deriveKey( { name: "HKDF", hash: "SHA-256", salt: new Uint8Array([]), info: new TextEncoder().encode("prf-demo"), }, keyMaterial, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"] ); // you can now encrypt text // if you want to save this to a server, send the ciphertext and iv // the key stays save on the user's device const iv = crypto.getRandomValues(new Uint8Array(12)); // encrypt text const ciphertext = await crypto.subtle.encrypt( { name: "AES-GCM", iv }, encryptionKey, new TextEncoder().encode("My secret text") // the text to encrypt ); // decrypt text const decryptedText = await crypto.subtle.decrypt( { name: "AES-GCM", iv }, encryptionKey, ciphertext ); // authenticate and derive the same key by providing the same text label const assertion = await navigator.credentials.get({ publicKey: { challenge, allowCredentials: [{ id: credentialId, type: "public-key" }], userVerification: "required", extensions: { prf: { eval: { first: new TextEncoder().encode("prf-key-v1") // same text label to derive the key } } } } }); // derive the same key in the same way as above const prfResult = credential.getClientExtensionResults().prf.results.first; // create a CryptoKey object from the PRF result const keyMaterial = await crypto.subtle.importKey( "raw", prfResult, "HKDF", false, ["deriveKey"] ); // derive the same encryption/decryption key as with registration const encryptionKey = await crypto.subtle.deriveKey( { name: "HKDF", hash: "SHA-256", salt: new Uint8Array([]), info: new TextEncoder().encode("prf-demo"), }, keyMaterial, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"] );

Documentation

WebAuthn PRF extension on MDN

Browser support

WebAuthn PRF extension on caniuse.com

What PWA Can Do Today A showcase of what is possible with Progressive Web Apps today