12

Current token format, creation, verification:

vls_k3uGjFsDfA49Ygt8mqNHAtkBuUqRTU6K1KfUCwEiX9Z

I am creating session token as follows:

  1. Create an array of 32 bytes.

  2. Fill the first 28 bytes via PRNG.

  3. Calculate checksum (CRC-32) from the 28 bytes and add the result to end of the array.

  4. Use Base-58 to encode the array and append with the prefix (vls_)

How the token stored in the db:

  1. Hash the created token via (SHA-512)

  2. Store it in a table with the associated user's id

I am verifying the token as follows:

  1. Check if it has prefix.

  2. Slice it to get token without prefix

  3. Decode it to get the bytes.

  4. Slice it again to the random bytes (byte array length - 4) and calculate checksum

  5. Compare the token checksum with the calculated checksum

  6. Hash the token (SHA-512)

  7. Check the db...

Skipped some checks for clarity.

My question:

Do I gain anything using SHA-512 instead of SHA-256?

  • I think not because the token is already high entropy.

  • If the attacker somehow got the table and probably going to know the format so he can generate tokens and hashes so it is not going to matter what hash method is used.

I might be completely wrong about this, so I'm open to any feedback.

1 Answer 1

27

This approach is over-engineered. You don't need a checksum.

You have a database in which you can store session IDs associated with users. Use a cryptographically secure random number generator (CSPRNG) to generate a random session ID. A length of 16 bytes (128 bits) is fine. Format it as hex or base64 if you need to represent it as a string. Give the user the session ID, e.g. as a cookie. Hash the session ID, then store it in the database along with an expiry date (timestamp) for the session.

When the user sends a request, take the session ID they sent, hash it, and look that hash up in the database. If the hashed session ID is found, and the current timestamp doesn't exceed the expiry date in the database, the session is valid and they're logged in. Otherwise they're not logged in. You can update the expiry timestamp on every successful request so that it only expires after a certain period of inactivity.

You don't strictly need format and length validation on the provided token, but for good practice you should at least check that it is the correct length and only contains the expected characters (e.g. regex /^[0-9a-f]{32}$/ if it's a 128-bit value encoded as a hex string). You don't need any prefix or checksum. If the user changes the value it will not match a session ID.

You may also choose to store information such as the user's IP address and browser user agent, and reject access if an attempt is made to use a session ID with a different IP/agent. This helps prevent session ID theft from the browser, e.g. if the user has malware installed.

The reason for hashing the session ID in the database is so that an attacker cannot trivially take over active user sessions if they gain read access to the database, e.g. through SQL injection. Because the session ID is a long random string, you do not need to use anything more than a cryptographic hash (e.g. SHA256) here. The hash does not need to be salted, and you don't need a computationally hard KDF like Argon2, bcrypt, or PBKDF2. A sufficiently long (i.e. 128-bit or greater) value generated by a CSPRNG is not guessable by an attacker, and the keyspace is far too large for a brute-force attack on the hash.

Another reason for hashing the session ID is timing attacks on indexed lookups. An index lookup does not take a constant-time path when searching for a string, so this timing side-channel may allow an attacker to progressively reduce the search space and discover active session IDs. This attack is usually impractical outside of lab scenarios, but the solution is to hash the token so that the attacker cannot easily perform database index lookups with a chosen prefix. We're already hashing the session ID for other reasons, so the question of practicality is moot.

The output length of the hash function is not critical here. A 128-bit session ID is more than sufficient, so as long as you're using a cryptographic hash function with an output size larger than the minimum security bound (128 bits) it will be fine. Using SHA512 instead of SHA256 doesn't offer any benefits, and just increases the storage size and computational cost of the hash.

You must use a CSPRNG to generate the session IDs. Standard library random functions like rand() or mt_rand() are not suitable for generating security-sensitive secrets. Your language's standard library may offer a cryptographic random number generator API, e.g. random_bytes in PHP, RandomNumberGenerator in .NET, or SecureRandom in Java. You may also read from /dev/urandom (but not /dev/random - that is a legacy interface) on Linux/BSD environments.

9
  • The prefix is becoming generally preferred as a way to help disambiguate and/or scope what are otherwise opaque binary blocks. Commented Oct 4, 2021 at 0:30
  • 2
    @chrylis-cautiouslyoptimistic- In this context there's no need to disambiguate at all; the cookie name tells you exactly what the token is for.
    – Polynomial
    Commented Oct 4, 2021 at 0:40
  • The CRC is valuable because it lets you avoid the expensive database lookups for most invalid tokens
    – Ben Voigt
    Commented Oct 4, 2021 at 20:06
  • @BenVoigt An indexed database lookup would be O(log N) in the worst case, and unless you're using replication and the initial lookup query contains a modified field (e.g. expiry time) a request with a valid session ID should result in a cache hit 100% of the time. If you're scaling to the point where an indexed session lookup across the user table is problematic, even with caching, you're probably already load balancing users across nodes anyway, so you can shard the session table. The risk of dodgy parsing logic causing problems isn't worth the tiny performance saving, imo.
    – Polynomial
    Commented Oct 4, 2021 at 20:16
  • Also, if an attacker was purposefully trying to cause a DoS via session ID lookups, they'd just calculate the CRC. You can't assume they wouldn't know to do that - see Kerckhoff's principle.
    – Polynomial
    Commented Oct 4, 2021 at 20:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .