How to build a "tamperproof cookie"
All right! It’s been a minute! Tamperproof cookies, I needed one, it’s pretty simple after thinking it through.
I feel like I always say it’s been a while since my last post… but this time it has in fact been a while.
Not so fast there past me prior to thinking it through! Cookies can easily be modified by the client, because they’re stored on the client local storage and aren’t signed with a digital signature!
Here’s some context into the situation:
Basically, I needed a way to store local to the user a secure (tamper proof) means of indicating the user has been dual factor authenticated for 30 days. The simplest way to do this would seemingly be to store a cookie with a value of the expiration date and user name. However, this first idea I had falls apart quickly, since a user could just change the cookie value (either expiration date and/or user name) and be able to get past the second factor. The second factor one time password (OTP) entry should only be presented to the user if the cookie is valid, for the specific user, and is not yet expired (30 days from the cookie creation).
There are a few things that are needed for a client side solution:
- Something stored client side that can be securely used as a means of indicating the user does not need to enter a OTP
- Should have some sort of unique attribution to the user - either through a user ID, email address, or something similar
- Should have an expiration date that is taken into account when checking the validity of the thing
- Should not validate successfully in instances where the user information or the expiration date is changed
Once documenting the above requirements coupled with the above screenshot, it became pretty clear to me that some sort of cryptography could be used to ensure the tamperproof part of the thing being stored on the client side - in this case a cookie.
HMAC - Hash-base Message Authentication Code - is a cryptographic operation that utilizes a “key” and “message” to produce a “mac” which is (more or less) the combination of the two pieces. The general hmac signature looks something like this:
key portion is secret between one or more parties - in my case one party, and the
message is data that is to be validated as genuine. In my case, the
key will be kept secret from the user, as that
key is what verifies the
message is what we put into the cookie.
What’s a good
key to use you may be asking yourself? Well, it’s not the user name, though it’s something similar. The user already knows their own username, so it’s not a good candidate for a
key. There is however, their password, specifically their password hash. The user would not know the hash of their password that is stored in the remote system, so it is a good potential candidate for a
The user’s password is computed basically like this:
password_digest is the output of running
salt concatenated with
plaintext_password through a one way function
work_factor times. The
password_digest is not something the user would know, as their salt changes with each password change, nor do they know the underlying one way function applied to their
So, now we have a
key candidate, what about the actual message? The message is thankfully very straightforward, and we can use the datetime of the OTP cookie expiration. The full HMAC call will end up being:
cookie_hash_content = hmac(H(password_hash), expiration_date_string).
cookie_hash_content can be stored in the body of the cookie, though we will also need to stored the
expiration_date_string within the cookie as well, so that the server side can reconstruct the inputs into the
hmac call, and verify they match.
Thankfully, this is easy enough to do with a pipe (or other) delimited set of fields within the cookie. I will be using this format:
Validating the above cookie at authentication time is as simple as:
- Try to split the cookie value into two pieces, if less than two pieces exist, return false
- Try to parse the first element in the split as a date, if fails, return false
- Ensure the parsed date is after the current datetime, if it is not, return false
- Validate the
cookie_hash_contentby comparing the cookie’s second part of the split, to a newly generated
- if they don’t match, return false
- if they match, return true
The above pseudocode also has the added benefit if the user changes their password, they will immediately fail the OTP cookie check, as the mac using their old password hash will no longer match the newly generated/checked password hash upon login. This in a few ways makes things just a bit more secure.
Here is a quick run through of the HMAC (and a few unit tests) using fake
- This works because the
keyis not known (or knowable) to the end user
- The salts, while not necessarily secret, are not known to the end user, so they would have no chance of being able to recreate the same conditions to come up with the
- We verify the
cookie_hash_contentby re-computing the value based on the values stored in the cookie itself, along with the additional data of
keythat isn’t in the cookie, and can’t be known by the user, so there’s no chance the user would be able to come up with “the right information” that could pass the verify step.
How to build a "tamperproof cookie"