How Passkeys Work, Part 1: The Crypto Primitive
This is part 1 of a 3-part series on passkeys. We start from the ground up -- the cryptography underneath everything. No assumed knowledge.
Why this matters for you as a Software Engineer?
This is cybersecurity 101 -- but understanding it has real practical payoff.
It explains why your web app needs a TLS certificate.
It explains what you are actually building when you implement passkeys.
And it makes the case for why passkeys are worth adopting over rolling your own password auth from a scratch.
If you are a backend engineer building authentication today, this is the foundation you need to make that decision well.
Why passwords are broken
When you log in with a password, the server stores a version of your password (usually hashed). That means your secret lives on someone else’s machine.
If that server gets breached, attackers have something to work with. With enough compute, they can crack weak passwords. And most people reuse passwords, so one breach becomes many.
The core problem: passwords are a shared secret. You know it. The server knows it. Anyone who compromises the server gets a shot at it.
Passkeys eliminate the shared secret entirely. Here’s how.
Public and private keys
Passkeys are built on asymmetric cryptography. The idea is simple: instead of one shared secret, you have two mathematically linked keys.
Private key -- only you have this. It never leaves your device.
Public key -- anyone can have this. You give it to the server.
The critical property: what the private key produces, the public key can verify. But you cannot reverse engineer the private key from the public key.
Think of it like a lock and key. You give the server a lock (public key). You keep the key (private key). The server can check whether something was locked with your key -- without ever needing to hold the key itself.
What happens when “signing”
When you authenticate with a passkey, your device doesn’t send a password. It sends a signature.
Here’s what that means mechanically:
The server sends you a challenge -- a random string, freshly generated for this login attempt
Your device hashes the challenge -- produces a fixed-length fingerprint of it
Your device encrypts that hash with your private key -- this is the signature
Your device sends the signature back to the server
The server then:
Decrypts the signature using your public key -- gets back the hash
Independently hashes the original challenge it sent
Compares the two hashes -- if they match, the signature is valid
If they match, the server knows two things:
The message wasn’t tampered with (the hashes match)
Only your device could have produced this signature (only your private key produces something that decrypts correctly with your public key)
Why you can’t replay a captured signature
Say an attacker intercepts your signed response. Can they just send it again next time?
No - because the challenge is a nonce (number used once). The server generates a fresh random challenge for every single login attempt.
The attacker’s captured signature was produced by signing challenge abc123. Next login, the server sends challenge zzq987. The attacker replays the old signature. The server decrypts it and gets the hash of abc123 -- which doesn’t match the hash of zzq987.
Rejected.
What the server actually stores
During registration, the server stores:
Your public key
A credential ID -- a unique identifier for your passkey
That’s it. No password. No secret. Just a public key it can use to verify signatures.
If the server gets breached, attackers get your public key. That’s useless to them -- you can’t derive the private key from it, and you can’t forge signatures with it.
This is the fundamental shift. The server never holds anything an attacker can exploit.
How both sides agree on the algorithm
One small detail worth knowing: hashing and signing require an algorithm. How do both sides know which one to use?
During registration, the server advertises a list of algorithms it supports. The device picks one from that list. The agreed algorithm is stored alongside your public key.
At authentication time, both sides already know the algorithm. If they somehow used different ones, the hashes wouldn’t match and verification would fail.
In WebAuthn (the protocol passkeys are built on), the most common algorithm is ES256 -- ECDSA with SHA-256.
Putting it all together: a concrete example
Registration (one time setup)
You open Google and set up a passkey. Your device generates two linked keys:
Private key: stays on your device, never leaves
Public key: sent to Google and stored against your account + a credential ID
Authentication (every login)
You try to log in to Google
Google sends your device a fresh random challenge -- say
abc123xyz(the nonce)Your device hashes it:
hash("abc123xyz")=HYour device encrypts
Hwith your private key = signatureYour device sends Google: the signature + your credential ID
Google looks up your credential ID, finds your public key
Google decrypts the signature using your public key -- gets back
HGoogle independently hashes the original challenge it sent:
hash("abc123xyz")=HBoth
Hs match -- you’re in
Why an attacker can’t replay it
Next login, Google sends a completely different challenge -- zzq987abc. The attacker’s captured signature from last time decrypts to the old H, which doesn’t match the new challenge’s hash. Rejected.
The full picture
What’s next
This is the foundation. In Part 2, we go up a layer -- the WebAuthn/FIDO2 protocol. What actually happens during registration and authentication flows end to end, and what your server needs to implement to be a proper relying party.
I'm a backend engineer writing about integrating AI and modern infrastructure into real systems. If this was useful, subscribe for Part 2.



