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!
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!
very smart idea with clear instructions – I have bookmarked this for a future reread + full tryout
thanks very much
Very clever – solves all the problems with securely resetting a hashed password in one fell swoop.
Thanks for posting this!
Can you elaborate on how an attacker would change user context? Adding user id to the hash doesn’t seem to add additional security to me.
Hi Bill – Adding the user ID to the hash protects against OTP hijacking – without it an attacker can request and OTP for his user, then change the user ID to someone else and replace their password… adding the ID to the hash prevents that.
thanks amir, perfects
Amir,
Nice solution for OTP….how does this ensure that the OTP is not repeated…is there a way to ensure that?
Thanks,
Chet
The OTP is a hash of the user old pass together with a timestamp and a salt… really hard to repeat.
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.
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
thanks for this pretty clear and simple post on this topic, which is mostly known if you look at ‘attempts’ (i like yours a lot) by openid, twitter, fb auth etc…
But yeah, nice i love the numbered list description and outline of it thank you!