Skip to main content
Version: next

PRF encrypted store

PRF (pseudo-random function) is a deterministic function that generates a random value from two input values.

When implemented in security solutions like FIDO2 HMAC secret ("hmac-secret" extension) and Web Authentication API's PRF extension, it can generate a random but deterministic value from the hidden secret key and a developer-specified input.

The output value can then be used in various key derivation function (KDF), such as HKDF, to create a symmetric key to encrypt or decrypt data.

Because the hidden secret key is not extractable, generating the PRF output can only be performed within the security solution. Doing so would usually also require user interaction (pressing a button, biometric authentication, entering a password, etc.). This enables softwares to securely store sensitive data in untrusted environments.

Architecture

The TangoPrfStorage class uses PRF to encrypt the ADB key.

It takes another TangoDataStorage to actually store the encrypted data, and a TangoPrfSource for runtime-specified PRF API.

The key derivation step uses HKDF to derive an AES-256 key from the PRF output.

All random number generations, key derivations and encryptions are performed using Web Crypto API.

Create a new key

Data flow

PRF key ID, HKDF info and salt, AES initialization vector, and the encrypted data are all stored in the underlying TangoDataStorage for the decryption process.

Enumerate existing keys

The stored information is used to produce the same PRF output, the same AES key, and the same cleartext data.

Data flow

PRF source

To support different runtime environments, TangoPrfStorage uses a TangoPrfSource to create and get the PRF output:

import type { MaybePromiseLike } from "@yume-chan/async";

interface TangoPrfCreationResult {
/**
* The generated PRF output (random number)
*/
output: BufferSource;

/**
* ID of the created secret key
*/
id: Uint8Array<ArrayBuffer>;
}

export interface TangoPrfSource {
/**
* Create a new secret key and generate PRF output using the key and input data.
*
* @param input The input data
*/
create(
input: Uint8Array<ArrayBuffer>
): MaybePromiseLike<TangoPrfCreationResult>;

/**
* Generate PRF output using the secret key and input data.
*
* @param id ID of the secret key
* @param input The input data
*/
get(
id: BufferSource,
input: Uint8Array<ArrayBuffer>
): MaybePromiseLike<BufferSource>;
}

WebAuthn PRF extension

An implementation of TangoPrfSource is provided for WebAuthn PRF extension.

Support

To use the PRF extension, both the runtime (browser) and the authenticator (the hardware, software or cloud service that actually stores the credentials) must both support it.

Runtime

Authenticators

AuthenticatorSupport
Windows HelloRequires Windows 11 insider preview
iCloud KeychainRequires macOS 15.0 or iOS/iPadOS 18.3
Google Password ManagerYes
FIDO2 security keysYes

Definition

export declare class TangoWebAuthnPrfSource implements TangoPrfSource {
/**
* Checks if the runtime supports WebAuthn PRF extension.
*
* Note that using the extension also requires a supported authenticator.
* Whether an authenticator supports the extension can only be checked
* during the `create` process.
* @returns `true` if the runtime supports WebAuthn PRF extension
*/
static isSupported(): Promise<boolean>;

/**
* Create a new instance of `TangoWebAuthnPrfSource`
*
* @param appName Name of your website shows in Passkey manager
* @param userName Display name of the credential shows in Passkey manager
*/
constructor(appName: string, userName: string);

create(input: Uint8Array<ArrayBuffer>): Promise<{
output: BufferSource;
id: Uint8Array<ArrayBuffer>;
}>;

get(id: BufferSource, input: Uint8Array<ArrayBuffer>): Promise<BufferSource>;
}

export declare namespace TangoWebAuthnPrfSource {
declare class NotSupportedError extends Error {
constructor();
}

declare class AssertionFailedError extends Error {
constructor();
}
}

Basic Usage

The most basic usage is with a LocalStorage storage and a WebAuthn PRF source.

import {
TangoPrfStorage,
TangoLocalStorage,
TangoWebAuthnPrfSource,
} from "@yume-chan/adb-credential-web";

const storage = new TangoPrfStorage(
new TangoLocalStorage("key"),
new TangoWebAuthnPrfSource("Tango", "ADB Key")
);

Or, an IndexedDB storage can also be used. Note that since TangoWebAuthnPrfSource can't run in Web Workers, so using IndexedDB storage doesn't have any big benefits.

import {
TangoPrfStorage,
TangoIndexedDbStorage,
TangoWebAuthnPrfSource,
} from "@yume-chan/adb-credential-web";

const storage = new TangoPrfStorage(
new TangoIndexedDbStorage("key"),
new TangoWebAuthnPrfSource("Tango", "ADB Key")
);

Two-factor authentication

For a more secure authentication, you can chain the password storage and the PRF store to get two-factor authentication.

import {
TangoPrfStorage,
TangoLocalStorage,
TangoPasswordProtectedStorage,
TangoWebAuthnPrfSource,
} from "@yume-chan/adb-credential-web";

const storage = new TangoPrfStorage(
new TangoPasswordProtectedStorage(
new TangoLocalStorage("key"),
() => "password"
),
new TangoWebAuthnPrfSource("Tango", "ADB Key")
);

This will first ask for a password (here we just return "password"), then ask for WebAuthn user authentication.