First of all, I know, don't reinvent the wheel... But there's a good reason for this.

So I have been implementing a generic 2 factor authentication code generation/validation algorithm in order to re-use it in multiple applications and also make it stateless (the algorithm doesn't need to store anything in the database, and it doesn't require any user data at all.)

The application using the code is responsible to set a payload and validate it within the generic 2 fa process.

So here's how it would work...

1 - Application requests a new 2FA code passing down a payload:

1.1 - payload: arbitrary data, such as a user id and application id.

1.2 - a random 7 digits code is created (using crypto secure random libraries)

1.3 - an expiration time is selected, i.e. 3 minutes from current time, expressed as epoch time in seconds.

1.4 - data: (expiration, payload, code)

1.5 - data is encrypted using AES CTR with a randomly generated IV of 128 bits and a secret key

1.6 - both encrypted data and IV are digested through an HMAC that uses another secret key, in order to sign these two values.

1.7 - final token: 'base64 encrypted data : IV : signature'

1.8 - the token is returned to the user UI (such as a mobile app or login webiste)

1.9 - the code is sent through SMS or Email

2 - Application submits the original token, and user provided code to the backend

2.1 - token is decoded, and signature is validated

2.2 - token is decrypted, expiration checked, and user code is compared against the token code

2.3 - payload is returned to the application, which performs any additional validation such as fetching a user from the user id in the payload, etc...

Is this broken? You might ask why not just store the 7 digits code in the database, but the whole idea is to make this completely stateless and generic.

Also note: The endpoint for token generation/validation should be throttled in a way that it can't be brute forced.

  • You'd be better off building it and open sourcing it so people can poke at it – Anthony Russell Jul 5 '18 at 20:26
  • Well I wanted to validate mostly the security of this, would it be easy to break it? Is something wrong with the process? People with good security knowledge can probably quickly point out if there's a flaw in any of the steps that would render the whole code generation useless. – Cristiano Coelho Jul 5 '18 at 20:57
  • I can't tell you if there's any flaws in it without seeing the code. Someone could describe to me a TLS handshake lib they wrote that's to the letter but then botch the implementation. – Anthony Russell Jul 5 '18 at 21:00
  • @AnthonyRussell You can review the TLS specification for security issues without looking at implementations. – AndrolGenhald Jul 5 '18 at 21:02
  • Tell that to the person that implemented the Heartbleed bug @AndrolGenhald – Anthony Russell Jul 5 '18 at 21:02

After thinking about it for 15 minutes, and assuming it's implemented correctly and the applications using it use it correctly, the only minor problem I can come up with is a language one:

1.8 - the token is returned to the user UI (such as a mobile app or login website)

UI implies it is presented to the user. The user has no need to see the token, it should just be returned to the user client, which submits it without the user ever having to see it. This is of course a very minor issue as it doesn't really do any harm for the user to see it, it's just not really necessary.

Of course, you probably want someone better than me thinking about it for more than 15 minutes.

Personally I expect that once a significant enough percentage of people are using 2FA (and enough places support 2FA), phishers will start using proxies like Evilginx, which OTP solutions are vulnerable to. As far as I know the only 2FA method that prevents this right now is U2F, which requires storing a public key for each user.

I don't believe it's even possible to prevent this type of phishing without storing a public key, secret, or something similar per user. If I were implementing 2FA I would try to make it compatible with U2F, or at least try to design it so that U2F or a future method could be easily added if desired.

Also note that neither SMS nor email have strong guarantees that they won't be intercepted. At least with TOPT or HOTP you could use something like Google Authenticator that, while still vulnerable against a malicious proxy, at least doesn't have to send a code that could be intercepted.

  • You are right about the language issue, I really meant returning to the client, the token should never be seen. Interesting stuff with Evilginx, but if man in the middle attacks are possible this way, don't you think your site has much worse problems? Either way you will end up with a session token somewhere that can be stolen with this. – Cristiano Coelho Jul 5 '18 at 21:50
  • @user1777914 If by "don't you think your site has much worse problems?" you mean that the possibility of MitM means you've misconfigured something then no, it's not a traditional MitM. It's still phishing, so you have to get the user to click a link to gaagle.com, which Evilginx proxies to google.com. Both the user's connection to gaagle.com and gaagle.com's connection to google.com are secure and correctly configured. – AndrolGenhald Jul 5 '18 at 21:56
  • I see, well leaving phishing aside. I know about SMS/Email also not really being secure (it's still better than nothing though). Would a third option through a mobile app push notification make this slightly better? Just like facebook sends you the codes there. I know it won't be as good as google authenticator or a real OTP but the main goal is to still avoid saving any kind of data server side. – Cristiano Coelho Jul 5 '18 at 22:44
  • @user1777914 That might be a little better if it's unencrypted, if you can encrypt push notifications then it's much better. – AndrolGenhald Jul 9 '18 at 13:31

It looks like "I don't want to walk one kilometer, so I will walk one hundred kilometers instead". Saving seven numbers in a database, or flat file, or anything is way easier, and generic too. Your solution is not stateless, as the state is sent to the client.

I would definitely not use your system, it's full of moving parts. If you will send a token to the user by email or SMS, just save it and compare later. A small sqlite database is enough, and there are libraries for using it in almost every language. You would have a database like this:

[userid | token   | expiration         ]
|192    | 1234567 | 2018-07:15 10:11:12|
|723    | 7482912 | 2018-07:15 15:24:33|

To prevent the database from getting too big, you could run a job every hour or so to delete the expired tokens.

You can simplify the system a little and get rid of the cleanup job by using a proper TOTP. Create a database with the userid and the key. When the user wants to authenticate, you ask for the token. The user will open Google Authenticator, Authy, 1Password, Enpass or whatever TOTP client he uses. When the user submits the token, you calculate and validate the token. There are a lot of TOTP libraries available too, for almost every language.

To use the second approach, you will only add a very small sqlite database and a library to your application. I think the overhead is very small, and simplifies your process a lot.

  • The idea is to not use any database at all, such that the code can be easily deployed on AWS Lambda for example and have it re-used by multiple applications under the same company. The fact that you need to store a user - token pair is already awful and not generic enough. What if you want more metadata? Maybe I didn't explain exactly the objective of having this as generic as possible – Cristiano Coelho Jul 5 '18 at 20:55
  • You can use DynamoDB for storing the token and easily deploy on AWS. If storing a username and token is awful, I imagine how simple your application have to be. – ThoriumBR Jul 5 '18 at 21:01
  • Well it doesn't matter if it's simple or not, but the idea is to avoid this. Just having a token going back and forth isn't that much worse than storing data in a database. IMO it's better than having to use an additional database service and also implement maintenance on it to clear old data. The actual code is really very, very simple and short, probably shorter than writing the SQL queries or boto3 dynamo db calls and a maintenance job. – Cristiano Coelho Jul 5 '18 at 21:07
  • It's hard to imagine how something can be simpler than select key from tokens where username='johndoe' limit 1... Probably the throttling mechanism is way more complex than this. – ThoriumBR Jul 5 '18 at 21:19
  • It isn't if it's already part of your framework, which you should have already implemented anyways for other things. But you are still not seeing the point about avoiding a database completely. It's like arguing why use a JWT instead of just storing a token in the database. – Cristiano Coelho Jul 5 '18 at 21:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.