1

I am looking for the database-free way how to validate a secret code that was sent to user's mobile phone by RESTful web app, in a 2-factor authentication process.

At step 1, the user provides login name and password that is validated with the data from the database. Now the system generates a code that is sent to user's mobile phone. For convenience, the code should be relatively simple, let's say 5 numeric digits. User must provide this code in order to complete the authentication process.

I'd like to avoid the trips to the database as much as possible, so I use JWT to keep the state. But it would be absurd to store the secret auth code in the JWT, because it can be easily extracted at the client side. Therefore, it should be stored in hashed or encrypted form. I am considering salting the secret code with strong salt (known only by the server) and then store the SHA256 hash of the result (bcrypt hash is no good here because it contains the salt). That would make the secret code hidden from any prying eyes, but still useful for validating the code when provided by someone who knows it. And the server can happily forget the code meanwhile.

Can this approach be considered secure for the given purpose?

| improve this question | | | | |
  • Are there any reasons you are not using RFC 6238 TOTP or RFC 4226 HOTP? – Lie Ryan Mar 13 '17 at 1:24
  • @LieRyan wouldn't that require to maintain a separate, persistent counter for each session? – Passiday Mar 13 '17 at 8:08
  • HOTP would need a counter, but TOTP uses current time as the "counter" so you only the OTP shared secret to validate TOTP. – Lie Ryan Mar 13 '17 at 13:23
1

You're looking for message authentication codes, not pure hashing. You could probably do something like the following:

The server generates a code. It also keeps a secret (that's basically what you meant by your secret salt). Then it calculates

mac = hmac-sha256(code, secret)

mac is stored in your jwt. When the user enters his code, the server uses it and the secret only the server knows to calculate mac2, and if mac2 matches mac, the user entered the right code.

Edit: You've correctly pointed out that this can be exploited because the same code always yields the same mac. So you can correct for this problem like so:

mac = hmac-sha256(code || salt, secret)

Now you have to store salt along with mac in your jwt. Choose a new random salt every time you generate a new mac. I think the above construction is safe, but you might want to google a bit to make certain.

HMAC is a protocol to use to generate message authentication codes (macs), see Wikipedia for more information. Most programming languages offer a library to work with HMACs.

| improve this answer | | | | |
  • The only loophole left then would be the fact that the same code always would give the same mac. If a user A has received macA, and knows his code is codeA, then in case this user sees later macA value that is not meant for him, he nevertheless knows the matching code. This could be solved by changing the secret with time, then. – Passiday Mar 12 '17 at 18:04
  • Good point. I'm updating the answer accordingly. – Out of Band Mar 12 '17 at 18:38
  • Adding salt doesn't really solve all problems with replay attack here. You'll want to also add the context that the data is valid on to the data being hmac-ed. This context can be the hmac of a previous message that the current message should be interpreted against. Or it could be a challenge code issues by the server. Or it could be a timestamp of the code validity period. – Lie Ryan Mar 13 '17 at 1:21
  • Right - I didn't even think to mention it, because I thought the standard exp field (expiry) was required with JWTs, along with a few others (such as the subject and the issuer). But it turns out I don't really know whether that's true. If you added those fields and signed the JWT itself, that should be enough. – Out of Band Mar 13 '17 at 9:41
0

I'm having trouble seeing how this works in any way at all, please bear with me.

We're dealing with a second factor of authentication, so let's work with the assumption that it is actually necessary; in other words, the first factor has been compromised.

Here is what I do as a hacker.

  1. I set up my own account
  2. I log into your app
  3. I receive a 2FA code and JWT that is salted and signed as suggested. I now have a JWT and the code that goes with it.
  4. I now attempt to sign into YOUR account using your compromised credentials
  5. The system sends the 2FA code to YOU, so I don't actually receive it, and I have no idea what the code is.
  6. I still have the JWT and code from step 3, so I use that.

How is this in any way secure? There has to be something that ties the code to the user.

| improve this answer | | | | |
  • Of course, the user id has to be stored in the state. If we use JWT for storing the state, then there's no problem of keeping the user id in its payload. The JWT you received in step 3 contains your user id, so it can not be used in step 6 to get into my account. – Passiday Mar 13 '17 at 7:58

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.