Why is an authenticator app's shared secret stored in recoverable encrypted form rather than hashed like a password?
answer
- verified by recomputation, not comparison
- both sides hold the same value
- a digest cannot be fed back in
- ciphertext, key kept elsewhere
- stops a dump, not a breach
basics
~20 sA time-derived code is recomputed by the server from the shared secret and the clock, so it must hold that secret itself, not a digest. Encrypting it at rest protects it from a database read, and nothing more.
solid answer
~50 sA password is verified one way: the server derives a value from what was typed and compares it against a stored derivation, so it never needs the original. An authenticator code is verified the other way round. The app and the server hold the *same* shared secret, and the server recomputes the digits it expects from that secret and the current time step. Nothing can be recomputed from a digest, so the secret has to be stored in a form the server can get back: ciphertext, under a key that does not live in the same database. Then be honest about what that buys. It defeats a stolen dump, a leaked backup, a read-only reporting replica. It does not defeat a compromised application process, which holds the key by design and can decrypt any enrolment it is asked about.
go deeper
Recall the shape: the server keeps the authenticator's shared secret itself, encrypted, because it has to recompute the expected digits. A password is kept one-way because the server never has to reproduce it.
Explain the mechanics. The code is a function of the shared secret and the current time step, computed independently on both sides, so verification is recomputation rather than one-way comparison - and that is what rules hashing out.
Show where the key lives and what the boundary is worth. Name what stored-secret encryption stops - a dump, a backup, a reporting replica - and say plainly that it does not stop code running with the key in hand.
Frame it as blast radius and exit cost: how many enrolments one key protects, how a key rotation re-wraps them without disturbing a single holder, and what the organisation accepts by running a recoverable second factor at all.
## Two credentials, two different verification jobs An account on a livestock movement-licensing service can carry two secrets that look superficially alike and are verified by opposite means. The holder's **password** is checked one way. The server keeps a value derived from the password, derives the same way from whatever was submitted, and compares the two. It never needs the original string, and that is exactly why a stolen store of derived values is not immediately a store of passwords. A **time-derived code** from an authenticator app is checked by recomputation. After enrolment nothing travels between the app and the server at all. Both sides hold the same **shared secret**, and both compute the same function of that secret and the current time step. The construction underneath is the HMAC-based one-time password `HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))`, where `K` is the shared secret and the counter `C` is derived from the clock rather than from a button press; the `Dynamic Truncation` step is what turns the `HMAC` output into the short run of digits the holder reads off the handset. The holder types those digits; the server computes what they should have been and compares. That single difference settles the storage question. To recompute, the server needs `K` itself. A digest of `K` cannot be fed back into the computation. So the secret is stored in a form the server can recover: **encrypted, not hashed**. | | password | authenticator shared secret | |---|---|---| | who holds it | only the person | the person's app **and** the server | | what the server stores | a one-way derived value | ciphertext of the secret itself | | how verification works | derive the submission, compare | recompute the expected code, compare | | can the server reproduce it | no | yes, and it must | | after a database dump | the values still resist recovery | recoverable, if the key is also taken | This is the structural reason a shared-secret second factor is described as **symmetric**: two parties hold the same value and either can produce the proof. A password is not symmetric in that sense, and a public-key credential is not either. ## What encryption at rest is actually buying Stored-secret encryption is a real control with a narrow, describable boundary. It protects the enrolment rows against anyone who obtains **the data without the key**: - a database dump taken by an attacker who found a query path but no code execution; - a nightly backup file copied off shared storage; - a read-only reporting replica handed to an analytics job; - a disk or volume snapshot that outlives the cluster it came from; - a developer who restores production data into a scratch environment. In every one of those, the attacker or the accident ends up with ciphertext and no key, and the second factor survives. ## What it does not buy 1. **It does not survive a compromised application.** The verification path decrypts on every sign-in, so the process that does it holds the key. Code running there can decrypt any enrolment it asks for. Saying otherwise in an interview is the classic overclaim. 2. **It does not make the secret one-way.** A `HMAC` appears inside the algorithm, which tempts people to call the stored value a hash. The stored value is the key to that `HMAC`, and it is recoverable by design. 3. **It does not remove the operator from the trust model.** Somebody can, in principle, decrypt a secret. That has to be made procedurally visible rather than argued away. ## Where the key has to live The whole control collapses if the key is stored beside the ciphertext, because whatever produced the dump produces the key with it. Practical placements, in increasing strength: an environment-supplied value injected at deploy; a secret manager the service authenticates to at start-up; a key-management service that never releases the key and performs the unwrap itself. Each row should carry a **key identifier** beside its ciphertext so more than one key can be live at once. That identifier is what makes rotation a background job rather than a re-enrolment campaign: introduce the new key for writes, then walk the table decrypting with the identified old key and re-encrypting with the new one, updating ciphertext and identifier in a single write. No holder is disturbed, because the secret itself never changes — only its wrapping. ## Operating it honestly - Expose **no read path** that returns a decrypted secret. Decryption happens inside the verification routine and nowhere else. - Log and alert on any decryption that is not a verification, because that is the only signal that the recoverable-secret risk has been realised. - Bound the blast radius deliberately: one key protecting every enrolment is one theft away from every account, and per-tenant or per-shard keys trade operational cost for a smaller loss. - Never re-display the secret after enrolment. Once the recoverable value has a user-facing route out of the system, the key boundary stops being the control.
- If the shared secret has to be recoverable, what stops an administrator reading one out of the system?Nothing structural, so it is made procedural and observable. No endpoint or report returns a decrypted secret, decryption happens only inside the verification routine, and every decryption outside that routine is logged and alerted on. The honest interview answer is that a recoverable second factor trusts its operators, and the audit trail is what you design in exchange.
- How do you rotate the key that encrypts stored authenticator secrets without a re-enrolment campaign?Store a key identifier beside each ciphertext. Point new writes at the new key, then re-wrap existing rows in the background: decrypt with the identified old key, encrypt with the new one, update ciphertext and identifier together. The shared secrets themselves never change, so no holder notices, and the old key is retired once no row references it.
- Does an authenticator factor still help once the database has been dumped?Only while the encryption key is out of reach. If the dump includes the key, valid codes can be computed for every enrolment and the factor is worth nothing. That is why the key boundary rather than the ciphertext is the control you should be able to describe, and why a single key covering every enrolment is a blast-radius decision, not a detail.
Checking a password is like matching a wax seal against an impression kept on file - you never need the stamp itself. Checking an authenticator code is like reading a combination off a duplicate dial: the office can only know what number shows this minute because it owns an identical dial, and a photograph of the dial would not do.
saying these in an interview costs you the question
- Says the authenticator shared secret should be hashed like a password
- Claims encryption at rest also protects it from a compromised application
- Keeps the decryption key in the same database as the ciphertext
- Thinks the app sends its shared secret to the server at sign-in
- Calls the shared secret one-way because a hash appears in the algorithm