I found this NPM package that looks like can be used to verify two factor authentication for websites, and also generating tokens for signing into other websites.

In the README there is a section about generating secrets.

For password security there is a huge emphasis on hashing and salting passwords and ensuring they remain secure and protected. Is there any such advice around storage of these secrets for the web server/platform that is providing the login service?

Obviously checking to make sure that there are no collisions between two different users secrets.

I haven’t seen very many tutorials or discussions around implementation tips and suggestions around two factor authentication.

  • If you're doing it right, checking for collisions is a waste of time. If you're doing it wrong, you've got much bigger issues. – AndrolGenhald Mar 21 '19 at 21:56
  • @AndrolGenhald Makes sense. That is a good point since I’m assuming the chance of collisions is very slim in this case. Any other tips or pointers on anything else regarding it? – Charlie Fish Mar 21 '19 at 22:04

No clever storage method exists for HOTP and TOTP. You should do your best to limit which programs can look up shared secrets using ordinary access control methods. (Plus physically securing the computer and erasing hard dives before they need to be discarded.)

Encrypting the shared secrets with a key only the server knows helps, but only if the (encryption) key isn't hidden below the metaphorical doormat. If the key is stored on the same server (or same drive) as the shared-secrets database then you aren't likely to gain much.

In a normal password hashing authentication system the server can store the hash of a password instead of the password itself because

  • For any hash function, identical inputs result in identical outputs. (If P1 == P2, then H(P1) == H(P2). This is true because H is deterministic.)
  • For a cryptographic hash, hashes of two non-identical inputs will not match with near certainty.
    • Blindly guessing passwords fails because any one of the many possible outputs is equally probable. (A 256-bit hash has 2256 possible outputs. The probability that a blind guess produces a different output is 1 - 2-256.
    • By definition cryptographic secure hashes are preimage resistant. There is no feasible way to compute, for any chosen y, an input x such H(x) == y. (256-bit preimage resistance implies that there is no method more efficient than brute forcing around 2256 inputs.)

That means that in practice whether P1 equals P2 is the same as whether H(P1) equals H(P2).

If there is no other reason that the server needs to store plaintext, then we should store the hash instead. The advantage to storing (salted) hashes instead is that cryptographic hashes are one way. With a sufficiently strong password that leaves even the server unable to determine the user's original password once all copies of the plaintext password have been cleared.

This is why a hacker with read access to the system, a discarded hard drive, or a database dump cannot immediately determine a persons password. (But a person's password (not chosen by a secure RNG) can get cracked in the real world because humans make relatively predictable choices.)

TOTP and HOTP do not work this way. Instead one time passwords are derived from a shared secret. With that secret, both the client and the server can generate as many one time passwords as they want. However, someone who doesn't have access to that secret cannot generate new authentication codes.

The only method the server could use (without using something different from HOTP and TOTP) to give up their knowledge of the shared secret is to pre-generate, hash individual OTPs, and store the list of them somewhere. And that still doesn't prevent offline cracking because if the database is leaked then it is possible to bruteforce any of the OTPs from that list in less than 1010 hash function evaluations.

Fortunately there is no other value to a HOTP/TOTP shared secret except for defeating the authentication system itself. Generate a random shared secret for every new user, then the secret has no relation to any other information. Humans might reuse passwords across multiple services (which would make those services easier to hack if your security was compromised) and humans might do something like put their Social Security number in their password. For randomly generated secrets these would never be a concern.

So all you can do is use the same types of methods you might use to restrict access to things like credit card numbers, personal information, and other information you must have access to but must also prevent other people from accessing. (Examples are in the first and second paragraph. The only thing I can add is using HSMs for encryption, but everything else needs to be secure as well. Including server software, the computer itself, and the network it communicates over.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.