{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreie5dadqxpyk2vbjy7fmpzgk6nxnrq626xq5dpq4ndkvqnqhzayssi",
    "uri": "at://did:plc:ffyfov3xy6shybcwf7hgoqda/app.bsky.feed.post/3mg2a4r4vtud2"
  },
  "path": "/blog/dev-e2ee-backend-part-3-passkeys-with-the-prf-extension/",
  "publishedAt": "2026-03-01T17:00:00.000Z",
  "site": "https://peterspath.net",
  "tags": [
    "github.com/peterspath/PasskeyBackend",
    "github.com/peterspath/PasskeyDemo"
  ],
  "textContent": "This is the third post in a series on building a truly privacy-preserving, end-to-end encrypted backend and client.\n\nThe goal of this part is user-friendly key management. We do that by using Passkeys with the PRF extension to create a deterministic encryption key.\n\n### Why This Matters\n\nEnd-to-end encryption needs a strong key on the client. The server must never see it. Old solutions either stored keys on the server or asked users to remember extra passwords. Both are bad.\n\n**Passkeys** solve authentication. They use Face ID or Touch ID and work across devices.\n\nThe **PRF extension** (Pseudo-Random Function) adds the missing piece. It lets the client create a deterministic encryption key from the Passkey itself. The server stores only a salt. The key stays on the device and never leaves the Secure Enclave.\n\n### How Passkeys Improve Security\n\nPasskeys use public key cryptography. The device holds the private key. The server holds only the matching public key. No shared secret travels across the internet.\n\nThis design stops phishing. A Passkey is bound to one exact domain. The browser and operating system check the website address before they allow the Passkey to work. If you land on a fake site, even a perfect copy, the Passkey will not activate. The private key never leaves your device. An attacker cannot steal it by tricking you into typing something. Passkeys also resist reuse. Each Passkey works only for its registered service. Attackers cannot take a Passkey from one site and use it on another.\n\nPasskeys remove other common risks too. There is no password to guess, reuse, or leak in a breach. Biometric checks such as Face ID or Touch ID add another layer. The device must confirm it is really you before it signs the challenge.\n\n### The Demo: Passkey Login + Instant Encryption\n\nTwo ready-to-run projects:\n\n  * **Backend** : Vapor server with full WebAuthn support\nRepo: github.com/peterspath/PasskeyBackend\n\n  * **Client** : macOS SwiftUI app\nRepo: github.com/peterspath/PasskeyDemo\n\n\n\n\nThe user enters an email and clicks one button. The app decides whether to register or log in. After Face ID or Touch ID, an encryption key is ready.\n\nKey client code:\n\n\n    // Unified flow (auto register or login)\n    await authManager.authenticate(\n    \temail: email,\n    \tpresentationAnchor: window\n    )\n\n    // After success\n    if let prfOutput = credential.prf {\n    \tself.encryptionKey = prfOutput.first  // AES-256 key ready\n    }\n\n    // Use it\n    let encrypted = try authManager.encryptString(\n    \t\"My secret data\"\n    )\n\n    let decrypted = try authManager.decryptToString(\n    \tencrypted\n    )\n\n\n### How the PRF Extension Works and Helps with Key Management\n\n  1. **Registration**\nClient creates a 512-bit salt and sends it to the backend.\nPasskey is created with the PRF extension.\nClient receives the first key output.\n\n  2. **Login**\nBackend returns the stored salt.\nClient performs Passkey login with the same salt.\nDevice returns the exact same key.\n\n\n\n\nThe PRF extension helps key management in three important ways:\n\n  1. the key stays on the device. It never reaches the server.\n  2. the key is deterministic. The same Passkey plus the same salt always gives the exact same key. You do not store the key anywhere. You derive it fresh each login.\n  3. rotation is simple. The server can issue a new salt. The client then derives a new key. Old encrypted data can stay safe while new data uses the fresh key.\n\n\n\n### Better User Experience Through Simple Key Management\n\nThis way of managing keys makes the user experience much better. Users do not need to remember extra passwords or handle key files. Encryption becomes automatic. After login the app has the key ready. People can encrypt and decrypt data with one click.\n\nEncryption of data must be usable. Otherwise people will not use it. The PRF extension and seamless flow fix this problem. Strong security now comes with simple, natural steps.\n\nIn our demo the encryption section appears right after login. Users see it works instantly. This is how we make privacy features something people actually enjoy.\n\n### Security Guarantees\n\n  * No passwords\n  * No server-side keys\n  * Keys never leave the device\n  * Full zero-knowledge encryption\n  * Phishing resistant\n\n",
  "title": "E2EE Backend part 3: Passkeys with the PRF Extension"
}