2

After reading about how to ensure that "remember me" tokens are kept secure and reading the source code for psecio's Gatekeeper PHP library, I've come up with the following strategy for keeping things secure, and I wanted to find out if this is going to go horribly wrong. I'm basically doing the following things:

  1. When a user logs in, generate a cryptographically-secure string using the system's random number generator. (random.SystemRandom() in Python) This is generated by picking random characters from the selection of all lower and uppercase ASCII letters and digits. (''.join(_random_gen.choice(_random_chars) for i in range(length)), as per how Django does the same. _random_gen is the secure random number generator)
  2. The generated token is inserted into a RethinkDB database along with the userid it goes along with and an expiration time 1 minute into the future. A cookie value is then created by using the unique ID that RethinkDB generates to identify that entry and the sha256-hashed token from before. Basically: ':'.join(unique_id, sha256_crypt.encrypt(token)). sha256_crypt is from Python's passlib library.
  3. When a user accesses a page that would require them to be logged in, the actual cookie value is retrieved from the database using the ID that was stored. The hashed cookie is then verified against the actual cookie using sha256_crypt.verify.
  4. If the verification passes and the time value previously stored is less than the current time, then the previous entry in the database is removed and a new ID/token pair is generated to be stored as a cookie.

Is this a good strategy, or is there an obvious flaw that I'm not seeing?

EDIT: After re-reading some Stack Overflow posts that I linked in a comment, I have changed the process above so that the database stores the hashed token, and the actual token is sent back as a cookie. (which will only happen over https, of course)

Freezerburn
  • 1,013
  • 3
  • 11
  • 29
  • Why step 2 (and half of 3)? If you have a random token, that's enough. It's not going to become any more random or secure by hashing it in arbitrary ways. – deceze Jul 17 '15 at 15:07
  • 1
    I'm basing this off of the section "Proactively Secure Long-Term Authentication" at https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence . See the "Problem 2: Timing Leaks" section just above it for context. And the "Part II" section of http://stackoverflow.com/questions/549/the-definitive-guide-to-form-based-website-authentication – Freezerburn Jul 17 '15 at 15:17

1 Answers1

0

You should make sure you generate enough characters in your secure string. I would aim for 64 bits of entropy, which means you need at least 11 characters in your string to prevent any type of practical brute force.

This is as per OWASP's recommendation for Session Identifiers:

With a very large web site, an attacker might try 10,000 guesses per second with 100,000 valid session identifiers available to be guessed. Given these assumptions, the expected time for an attacker to successfully guess a valid session identifier is greater than 292 years.

Given 292 years, generating a new one every minute seems a little excessive. Maybe you could change this to refresh it once per day.

I would also add a system wide salt to your hashed, stored value (known as a pepper). This will prevent any precomputed rainbow tables from extracting the original session value if an attacker manages to gain access to your session table. Create a 16 bit cryptographically secure random value to use as your pepper.

Apart from this, I don't see any inherent problems with what you've described. The usual advice applies though: Also use HSTS, TLS/SSL and Secure cookie flags.

SilverlightFox
  • 32,436
  • 11
  • 76
  • 145
  • My current setting is to generate 24 characters. And I'll add the pepper to my code using the function I already have, and just store that value in a module. Thanks for letting me know that I'm actually going about this reasonably! – Freezerburn Jul 19 '15 at 00:07