Version: 0.2.0 (Draft)
Status: Work in Progress
License: CC0 1.0 Universal (Public Domain)
Status: Work in Progress
License: CC0 1.0 Universal (Public Domain)
Abstract
BioKey is an open protocol for deriving a stable cryptographic identity from a biometric signal captured via a platform authenticator. It enables passwordless authentication without storing biometric data on any server, and without delegating identity custody to a device vendor (Apple, Google, Microsoft, etc.). This specification defines the enrollment handshake, key derivation standard, authentication flow, and challenge/response format.Terminology
| Term | Definition |
|---|---|
| Identity Key | A 256-bit value derived from the enrollment credential. Serves as the user’s public identity. |
| Credential | A WebAuthn PublicKeyCredential returned by navigator.credentials.create() or .get(). |
| rawId | The raw byte identifier of a WebAuthn credential. Used as V1 HKDF keying material. |
| PRF | WebAuthn pseudo-random function extension (defined in WebAuthn Level 3). Produces a hardware-backed deterministic secret output per credential. |
| rpId | Relying Party ID. Must match the hostname of the origin. |
| Challenge | A 32-byte random nonce issued by the server. Used once. Expires after 5 minutes. |
| Enrollment | The process of registering a biometric and deriving an Identity Key. |
| Authentication | The process of proving possession of the enrolled biometric to obtain a verified session. |
| Platform Authenticator | A biometric sensor built into the device (fingerprint, Face ID, Windows Hello). |
| method | Either prf (V2, preferred) or rawid (V1, fallback). Recorded at enrollment and stored with the identity. |
Key Derivation Standard
BioKey v0.2 defines two derivation paths. Implementations MUST attempt V2 first and fall back to V1 if the platform does not support the PRF extension.V2 — WebAuthn PRF Extension (Preferred)
The WebAuthn PRF extension (defined in WebAuthn Level 3) allows a platform authenticator to produce a deterministic, hardware-backed symmetric key tied to a passkey. The output is secret and never exposed outside the authenticator.Enrollment (navigator.credentials.create)
Pass the PRF extension with a fixed salt in the eval field:
credential.getClientExtensionResults().prf.results.first contains a 32-byte ArrayBuffer. This is the Identity Key.
Authentication (navigator.credentials.get)
Pass the PRF extension via evalByCredential, keyed by the stored credentialId:
publicKey and reject if they differ.
PRF Salt
| Parameter | Value |
|---|---|
PRF_SALT | UTF-8 encoding of "biokey-prf-v2-salt" |
V1 — rawId + HKDF (Fallback)
Algorithm
HKDF as defined in RFC 5869, using SHA-256.Parameters
| Parameter | Value |
|---|---|
| Hash | SHA-256 |
| IKM (Input Keying Material) | credential.rawId bytes |
| Salt | UTF-8 encoding of "biokey-v1-salt" |
| Info | UTF-8 encoding of "biokey-identity-seed" |
| L (Output Length) | 32 bytes (256 bits) |
Output
A 32-byte (256-bit) value, hex-encoded as a 64-character lowercase string.Reference Implementation
Enrollment Flow
Steps
- Client generates a random 32-byte challenge and 16-byte userId.
- Client calls
navigator.credentials.create()with:authenticatorAttachment: 'platform'userVerification: 'required'rp.idset to the current hostnameextensions.prf.eval.firstset toPRF_SALT
- Platform authenticator is triggered. User provides biometric.
- If
prf.results.firstis present: Identity Key =hex(prf.results.first),method = 'prf'. - Otherwise: Identity Key =
hex(HKDF(rawId)),method = 'rawid'. - Client stores
{ credentialId, publicKey, deviceId, enrolledAt, method }locally. - If a server is present: client sends
POST /enroll.
Implementation Example
Authentication Flow
Steps
- Client fetches a fresh challenge from
GET /challenge. - Client calls
navigator.credentials.get()with storedcredentialIdandextensions.prf.evalByCredential. - Platform authenticator is triggered. User provides biometric.
- Client re-derives Identity Key (PRF or rawId-HKDF) and compares against stored
publicKey. Rejects on mismatch. - Client sends
POST /verifywithuserIdand challenge hex. - Server validates challenge (single-use, 5-minute TTL) and returns
{ verified: true, publicKey, method }.
Implementation Example
Offline / Local-Only Authentication
If no server is configured, the client may authenticate locally by verifying the re-derived key matches the storedpublicKey. No challenge verification is performed. Suitable for local device unlock only.
Challenge Format
Issuance
- 32 bytes, cryptographically random
- Hex-encoded (64 lowercase hex characters)
- Stored server-side with creation timestamp
- Expires: 5 minutes from issuance
- Single-use: deleted on first verification attempt
Request / Response
Server API
All endpoints accept and returnapplication/json.
POST /enroll
Register a new identity. Request body:400: Invalid request body (missing fields, invalid publicKey format)409: User already enrolled (publicKey already exists)
GET /challenge
Issue a fresh challenge for authentication. Response (200):POST /verify
Verify an authentication challenge. Request body:401: Invalid or expired challenge404: Unknown userId (not enrolled)
Identity Format
The client stores identities locally in this format:Known Limitations
Cross-Sensor Variance
Different hardware sensors produce differentrawId values and PRF outputs for the same finger. Each device enrollment produces a distinct Identity Key. The server may link multiple public keys to one user account.
PIN Fallback
WebAuthn permits the device PIN as a fallback authenticator. BioKey cannot enforce biometric-only via the WebAuthn API alone. V3 goal: Native Android app usingBiometricPrompt with BIOMETRIC_STRONG to block PIN fallback.
PRF Platform Support (as of 2025)
PRF is well-supported on Android (Chrome). macOS and iOS support is available in Safari 18+ but remains inconsistent. The V1 rawId-HKDF fallback ensures BioKey works across all platforms while PRF coverage matures.Irrevocable Biometric
A fingerprint cannot be changed if compromised. Implementations should consider multi-finger enrollment, liveness detection, and server-side revocation via public key deletion.Versioning
| Version | Derivation | Salt / Info | Status |
|---|---|---|---|
| v0 | rawId → HKDF | biokey-v0-salt / biokey-identity-key | Deprecated |
| v1 | rawId → HKDF | biokey-v1-salt / biokey-identity-seed | Fallback only |
| v2 | PRF extension | biokey-prf-v2-salt | Current (preferred) |
Protocol stability: The V1 and V2 salt/info strings are frozen. Changing them would break compatibility with existing enrollments. Future versions (V3+) may introduce new derivation paths with different parameters, but V1 and V2 will remain supported for backward compatibility.
Security Model
What BioKey protects against
- Credential theft via server breach (server holds only public keys)
- Vendor lock-in (no iCloud/Google dependency)
- Replay attacks (challenges are single-use, time-limited)
- Cross-origin abuse (rpId is bound to the domain)
What BioKey does NOT protect against
- Compromised device (if the device is owned, the platform authenticator is owned)
- PIN fallback (WebAuthn permits PIN as authenticator fallback; the OS controls this)
- Cross-sensor attacks (V1/V2 — same identity across different hardware sensors is not guaranteed)
- Biometric compromise (fingerprints are irrevocable; liveness detection is recommended)
Trust Boundaries
Trusted (on device):- Platform authenticator
- WebAuthn API + PRF extension
- HKDF derivation (V1 fallback only)
- Identity Key storage (localStorage)
- Only public keys stored
- Challenge issuance and verification
- No biometric data, ever
Conformance Requirements
For an implementation to be BioKey-compatible, it MUST:- Attempt V2 (PRF) derivation first using
prf.eval.firstduring enrollment - Fall back to V1 (rawId-HKDF) if PRF is unavailable
- Use exact salt/info strings specified:
biokey-prf-v2-salt,biokey-v1-salt,biokey-identity-seed - Store
methodfield ('prf'or'rawid') with identity - Verify re-derived key matches stored
publicKeyduring authentication - Use 32-byte cryptographically random challenges
- Enforce single-use, 5-minute expiration on challenges
- Hex-encode all binary data (Identity Key, challenge) as lowercase
- Set
authenticatorAttachment: 'platform'anduserVerification: 'required' - Bind
rp.idto the application’s domain
- Add additional fields to the identity object (e.g.,
displayName,email) - Implement custom device fingerprinting beyond the 16-char
deviceId - Support offline/local-only authentication (no server challenge)
- Link multiple Identity Keys to one user account (multi-device)
- Implement additional security measures (device attestation, anomaly detection)
Reference Implementation
The canonical BioKey implementation is available at:- Core library:
packages/biokey-core/(enrollment, authentication, key derivation) - Server:
packages/biokey-server/(Bun + Hono + SQLite) - Browser SDK:
packages/biokey-js/(client-side integration) - React hooks:
packages/biokey-react/(useBioKey hook)
Authors
BioKey Protocol — open standard, not owned by any company.Originated by Md Ratul Islam, 2025.
This specification is released into the public domain under CC0 1.0. No permission required to implement, fork, or build upon it.
Next Steps
How It Works
Understand the biometric-to-identity flow
Quick Start
Implement BioKey in your application