Tableless and Secure One-Time Password (OTP)

Submitted by Amir Shevat on Fri, 01/15/2010 - 00:31

A one-time password (OTP) is a password that is only valid for a single login session or transaction. It is commonly used in the internet for registration and password reminder process in which OTPs are provides to the user in a form of a link that the user uses to access in order to create/reset his password.

Common requirements of One Time passwords are:

  1. Statistically unique – using the same password for all requests is probably not the right security choice.
  2. Hard to ‘guess’ – using sequential number is again, probably not the right security choice.
  3. Can be authenticated by the server – the server needs to distinguish between real OTP and bogus OTP.
  4. Good for one time – after the process is done the OTP should no longer be valid.
  5. Time limited – the OTP usually expires after a configurable amount of time.
  6. Secure – hackers should have a hard time changing the expiry date, username context and so forth.

Most OTP implementations use a Database table to persist the OTP and to manage their expiry date, a DB table might look like this:

id User Id OTP Expire date
1 Amir Asfsd3434bgddh 1/1/2010
2 Someone Ddfsd3345ssfsss 7/1/2010


While this is a valid solution, it is not the most efficient and elegant one, the truth is that you do not need an additional table enable and manage OTPs.

The answer is simple - the seed for this OTP is already persisted in the Database in the form of the old password (or more exactly the old password hash)

Here is how it is done:

We will use a password reminder process as an example so the base URL would be http://somedomain.com/resetpass/ and use a restful API for the rest of the parameters

The major challenged is how to generate a new OTP for each user that is unique and can be authenticated without persisting it to the database.

We start by using old password hash as the seed from which we can generate the new One Time Password, let’s call it OTP and let OTP = hash(old_password). We also add the user id, and call it userID as a context for the user who wants to reset his password.

The URL will now be http://somedomain.com/resetpassword/userID/OTP

Now, that of itself does not satisfy the all the requirements, it does not provide a time limitation, so let’s add the time of expiry in the URL. So let TIME = expiry_time_in_milliseconds and the URL should look like this:

http://somedomain.com/resetpassword/userID/TIME/OTP

So now the URL meets the “Can be authenticated by the server” and the “Time limited” requirements, now let’s make it a little bit more secure so that we satisfy the rest of the requirements.

We start by adding the expiry date as a part of the OTP so that hackers would have a hard time changing the time and breaking the expiry date. So let OTP = hash(hash(old_password)+TIME)

Next let’s add the User Id to the mix, so that hackers would have a hard time changing the user context. So let OTP = hash(hash(old_password)+TIME+userid)

To add additional layer of security we would add a unique application key (we will call it SALT) so that we have a key only the application know. So let OTP = hash(hash(old_password)+TIME+userid+salt)

When a server receives a request for reset password, it will get the following parameters:

  • userID = the ID of the user that wants to reset the password.
  • Time = the expiry time of the request (after which time the request is not valid)
  • OTP = hash(hash(old_password)+TIME+userid+salt)

All the server needs to do in order to authenticate the request is to perform its own hash(hash(old_password)+TIME+userid+salt) where hash(old_password) , time, user id and salt are all already known and to verify that the result is identical to the received OTP from the request. If a hacker changes the user id or the time then their OTP would not be the same as the OTP generated on the server and the request will be invalidated.

As for the “good for one time” requirement - Ones the user changes his password then the OTP will be invalidated because the hash(old_password) would not be the same on the server, so in fact this is a one time password.

I created a reference implementation using CakePHP and released it as open source - check it out.

Interesting, but..

Amir - thanks for the heads up. This looks like a very clever solution!

I was initially concerned with the security, as;

I know the user I want to "hack"
I know today's date.
I might guess the old password

SO it is possible I know or guess most of the puzzle, and could generate my own hash to pass in along with my spoofed time. Though I would think that the remaining salt and hash algorithm used should add enough variability to prevent such intrusions.

I am very interested to see some feedback from a security professional, because this looks like a great solution to a common need.

Re: Interesting, but..

Thanks for your feedback!

The puzzle has 5 parts:
1) User (semi public)
2) Time (public)
3) User old password (super private)
4) Application salt (private)
5) hash function (public)

The Key is the old password - if you know the old password as a hacker then it is 'game over' - You can just login and do whatever you want (like change the password and email in the account area). If you do not know the password then a combination of the password and the application salt should be a strong seed for the one time password. Right?

Cheers
Amir Shevat

Post new comment

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

Powered by Drupal, an open source content management system