How to build a "tamperproof cookie"
Tags: programming, cookie, cryptography, mfa
Published: 2021-08-17 20:19:30

---
title: How to build a "tamperproof cookie"
published: true
date: 2021-08-17 00:00:00 UTC
tags: programming,cookie,cryptography,mfa
canonical_url: https://blog.kritner.com/2021/07/27/how-to-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.

# The problem

So… working on a multi factor authentication setup for a freelance client. Have the multi factor one time password all set up, but now I need a way to implement a “remember me for 30 days” feature. That should be _relatively_ simple, right? Can’t we just use cookies?

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](https://searchsecurity.techtarget.com/definition/digital-signature)!

Here’s some context into the situation:

![Context from the Coding Blocks Slack](https://blog.kritner.com/2021/07/27/how-to-tamperproof-cookie/context.PNG)

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).

# Outline of Requirements

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

# The solution

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](https://en.wikipedia.org/wiki/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:

```py
digest = hmac(key, message)
```

The `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 `key`.

The user’s password is computed basically like this:

```py
password_digest = H(salt||plaintext_password, work_factor)
```

Where `password_digest` is the output of running `salt` concatenated with `plaintext_password` through a [one way function](https://en.wikipedia.org/wiki/One-way_function) `H`, `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 `plaintext_password`.

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)`.

This entire `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:

```
{expiration_date_string}|{cookie_hash_content}
```

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_content` by comparing the cookie’s second part of the split, to a newly generated `hmac(H(password_hash), expiration_date_string)`
  - 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 `expireDates` and `passwordHashes`: [https://replit.com/@Kritner/TamperProofCookies#main.py](https://replit.com/@Kritner/TamperProofCookies#main.py)

# Final thoughts

- This works because the `key` is 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 `key` being utilized
- We verify the `cookie_hash_content` by re-computing the value based on the values stored in the cookie itself, along with the additional data of `key` that 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.

Resources:

- [Digital Signatures](https://searchsecurity.techtarget.com/definition/digital-signature)
- [HMAC](https://en.wikipedia.org/wiki/HMAC)
- [One Way Function](https://en.wikipedia.org/wiki/One-way_function)
- [replit](https://replit.com/@Kritner/TamperProofCookies#main.py)
- [Coding blocks slack](https://www.codingblocks.net/slack/)
- Photo by [nate\_dumlao](https://unsplash.com/photos/_PGlV_zMXgw) on [Unsplash](https://unsplash.com/).