E2EE Backend part 3: Passkeys with the PRF Extension

This is the third post in a series on building a truly privacy-preserving, end-to-end encrypted backend and client.

The 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.

Why This Matters

End-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.

Passkeys solve authentication. They use Face ID or Touch ID and work across devices.

The 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.

How Passkeys Improve Security

Passkeys 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.

This 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.

Passkeys 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.

The Demo: Passkey Login + Instant Encryption

Two ready-to-run projects:

The 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.

Key client code:

// Unified flow (auto register or login)
await authManager.authenticate(
	email: email, 
	presentationAnchor: window
)

// After success
if let prfOutput = credential.prf {
	self.encryptionKey = prfOutput.first  // AES-256 key ready
}

// Use it
let encrypted = try authManager.encryptString(
	"My secret data"
)

let decrypted = try authManager.decryptToString(
	encrypted
)

How the PRF Extension Works and Helps with Key Management

  1. Registration
    Client creates a 512-bit salt and sends it to the backend.
    Passkey is created with the PRF extension.
    Client receives the first key output.

  2. Login
    Backend returns the stored salt.
    Client performs Passkey login with the same salt.
    Device returns the exact same key.

The PRF extension helps key management in three important ways:

  1. the key stays on the device. It never reaches the server.
  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.
  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.

Better User Experience Through Simple Key Management

This 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.

Encryption 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.

In 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.

Security Guarantees


Enjoyed this post?

Well, you could share the post with others, follow me with RSS Feeds, send me a comment via email, and/or leave a donation in the Tip Jar.


Tags

Category:

Year:


#100DaysToOffload 22 of 100