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:
- Statistically unique – using the same password for all requests is probably not the right security choice.
- Hard to guess – using sequential number is again, probably not the right security choice.
- Can be authenticated by the server – the server needs to distinguish between real OTP and bogus OTP.
- Good for one time – after the process is done the OTP should no longer be valid.
- Time limited – the OTP usually expires after a configurable amount of time.
- 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.
Very interesting system, however it doesn’t completely isolate the problem of ‘storing the state of the OTP’, basically the OTP lifespan is represented by the password of the user.
There are many places outside that area that needs an OTP that wouldn’t work with this logic.
This also means that even when you don’t need to store the OTP in the database during checks like in your case (which is very good), you still need the database to store the fact that the OTP is used up.
BUT (!) this means its an excellent solution for everything that is targeting a value in a database (could be something else like the email of the user). Nice article!