Storing data in cookies in a secure manner

By Jérémi Joslin · January 12, 2010 · Tags: , ,

Having an hour to kill at starbucks without internet, I'm giving a look at the code of Tornado (framework made by the guys of friendfeed). I discovered something quite interesting: the secure cookie.

The concept of the secure cookie is instead of storing data in the session (usually stored in a database as by default in Django) storing the data in a cookie. But, you will tell me it's not secure, anyone can change the value and, for example, see the website with the id of another user. So to make sure the user can't change (or if he does, we can detect it), they sign the request with a secret key. Every time we want to get the value, they check if the signature is valid. The value is stored in clear, so don't store anything a user should not view.

The cookie will look like this:

uid=1234|1234567890|d32b9e9c67274fa062e2599fd659cc14

Parts:

  1. uid is the name of the key
  2. 1234 is your value in clear
  3. 1234567890 is the timestamp
  4. d32b9e9c67274fa062e2599fd659cc14 is the signature made from the value and the timestamp

The interesting code (from Tornado's web.py):

def set_secure_cookie(self, name, value, expires_days=30, **kwargs):
    """Signs and timestamps a cookie so it cannot be forged.

    You must specify the 'cookie_secret' setting in your Application
    to use this method. It should be a long, random sequence of bytes
    to be used as the HMAC secret for the signature.

    To read a cookie set with this method, use get_secure_cookie().
    """
    timestamp = str(int(time.time()))
    value = base64.b64encode(value)
    signature = self._cookie_signature(value, timestamp)
    value = "|".join([value, timestamp, signature])
    self.set_cookie(name, value, expires_days=expires_days, **kwargs)

def get_secure_cookie(self, name):
    """Returns the given signed cookie if it validates, or None."""
    value = self.get_cookie(name)
    if not value: return None
    parts = value.split("|")
    if len(parts) != 3: return None
    if self._cookie_signature(parts[0], parts[1]) != parts[2]:
        logging.warning("Invalid cookie signature %r", value)
        return None
    timestamp = int(parts[1])
    if timestamp < time.time() - 31 * 86400:
        logging.warning("Expired cookie %r", value)
        return None
    try:
        return base64.b64decode(parts[0])
    except:
        return None

def _cookie_signature(self, *parts):
    self.require_setting("cookie_secret", "secure cookies")
    hash = hmac.new(self.application.settings["cookie_secret"],
                    digestmod=hashlib.sha1)
    for part in parts: hash.update(part)
    return hash.hexdigest()

The main use I see for this is to scale. if you don't need to store a lot of things in your session, you can store it in a cookie, so you have one request less to the session store (database or server). Also, you can add easily new server without problem of session replication.

What do you think about this?