We're starting a project to add an additional authentication factor to our web application. This involves the creation of a TOTP (Time-Based One-Time Password) token that conforms to RFC 6238 (https://www.rfc-editor.org/rfc/rfc6238). This token will be delivered to the user via SMS or email.
I'm aware this approach is not ideal from a security standpoint (eg. Eurograbber, Sandroid, Emmental). I'm hoping to steer our organization to evaluate other options, but in the meantime I'm researching best practices for this approach.
- a user authenticates to our web application using their username and password
- user initiates some action which triggers a risk assessment
- if risky:
- pull user's encrypted secret key from database (or generate new random key if it's not there)
- use secret key and current timestamp to generate TOTP
- TOTP is delivered to user's cell number via SMS (or delivered via email)
- Display TOTP input screen
- User receives and inputs TOTP
- Pull secret key from database and generate TOTP(s)
- validate provided TOTP against generated TOTP
- if valid, carry on with original action
Does there need to be an expiration policy for the user's secret key?
Logically the secret key is even more secure than the user's password (which never expires). It never leaves our systems (the application server and database).
I could make it expire (and generate a new one) say once a month, or once every 10 uses. But this really only seems to give value if our database (and the encryption key) has been compromised (in which case we've got bigger problems).
Are there any best practices here?
Notes / Assumptions
- The secret key will be generated using a Java's SecureRandom class (with reseeding)
- The secret key will be encrypted when stored on the database
- We will ensure the one-time only use of a valid token.
A second attempt of a valid token (within the same time window) will not be accepted.
- The time window the token is valid for will be kept as small as possible.
But it needs to be big enough for the token to be delivered (sms/email) and for the user to receive and enter token.
We'll probably start with 90 seconds and adjust as needed.
- The small time window should prevent brute force attacks?
But adding a user lockout after say 10 invalid token attempts is probably a good idea.
- Once the user has authenticated with a valid token, it's valid for the duration of their session. There will be no need for the user to re-authenticate again later.
EDIT Re: Sebastion
I considered your "KISS" approach a few weeks ago, but I was planning on going the TOTP route because it would provide greater flexibility for the future. If we ever want to use Google Authenticator (or partner with RSA, or Authy, etc.) then we're halfway there. It also simplifies our development a little since the expiration is built into the algorithm.
But ... now that I take a step back, I think your approach is probably the correct one. Over-designing and over-complicating now for a future that may never happen is a code-smell.
One thing I hadn't mentioned above was that I'd probably use something like 3 thirty second windows. So on your last point, the time frame would be 90 seconds max, or a minimum of 60 seconds, if the token was generated at the end of a window.