When setting up an application to use two-factor authentication for example with Google Authenticator, each user will have a unique seed value for the authenticator. The identity server will require knowledge of the seed to verify the token – meaning you will have to store it and retrieve it somehow. This means that if an attacker gets access to the storage solution that links OTP secret seeds to user ID’s (e.g. usernames), the protocol is broken. So, trying to think up some options for securing the secrets – we cannot hash and salt it because it breaks the OTP authentication flow. We are hence left with encrypting the seed before storing it.
The most practical seems to be a symmetric crypto approach, the question is what to use as the crypto key. Here are some approaches I’ve seen people discuss that all seem bad:
- User password: if you can phish the password, then you can also generate the OTP provided you know which algorithm/library is used
- A static application secret: should be safe provided that secret is never leaked but using a static secret means that if it is compromised, all users are compromised. Still better than the user password, though.
- Using non-static user level meta data to create a unique key for each user that is not vulnerable to phishing or guessing. Typically visible to admins.
Get username/password
Verify username/password
Get OTP seed (encrypted)
Get metadata and reconstruct encryption key
Verify OTP
Authenticate user and store timestamp and other auth metadata
Construct new encryption key
Encrypt seed
Store in database
The question is what metadata to use. We need the following properties to be true:
- Not possible to guess for a third party even if we tell what metadata it is
- Not possible to reconstruct for an administrator with access to the account
- Not possible to phish or obtain through social engineering or client side attacks
There are many possibilities but here is one possible solution that would satisfy all the above requirements:
Key = Password (Not available to admins) + Timestamp for last login (not guessable/phishable)