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.