The answer below pertains primarily to Signed Cookies, an implementation of the concept of sessions (as used in web applications). Flask offers both, normal (unsigned) cookies (via request.cookies
and response.set_cookie()
) and signed cookies (via flask.session
). The answer has two parts, the first describes how a Signed Cookie is generated, and the second is presented in the form of a QA that addresses different aspects of the scheme. The syntax used for the examples is Python3, but the concepts apply also to previous versions.
What is SECRET_KEY
(or how to create a Signed Cookie)?
Signing cookies is a preventive measure against cookie tampering. During the process of signing a cookie, the SECRET_KEY
is used in a way similar to how a "salt" would be used to muddle a password before hashing it. Here's a (wildly) simplified description of the concept. The code in the examples is meant to be illustrative. Many of the steps have been omitted and not all of the functions actually exist. The goal here is to provide an understanding of the general idea, actual implementations will be a bit more involved. Also, keep in mind that Flask does most of this for you in the background. So, besides setting values to your cookie (via the session API) and providing a SECRET_KEY
, it's not only ill-advised to reimplement this yourself, but there's no need to do so:
A poor man's cookie signature
Before sending a Response to the browser:
( 1 ) First a SECRET_KEY
is established. It should only be known to the application and should be kept relatively constant during the application's life cycle, including through application restarts.
# choose a salt, a secret string of bytes
>>> SECRET_KEY = 'my super secret key'.encode('utf8')
( 2 ) create a cookie
>>> cookie = make_cookie(
... name='_profile',
... content='uid=382|membership=regular',
... ...
... expires='July 1 2030...'
... )
>>> print(cookie)
name: _profile
content: uid=382|membership=regular...
...
...
expires: July 1 2030, 1:20:40 AM UTC
( 3 ) to create a signature, append (or prepend) the SECRET_KEY
to the cookie byte string, then generate a hash from that combination.
# encode and salt the cookie, then hash the result
>>> cookie_bytes = str(cookie).encode('utf8')
>>> signature = sha1(cookie_bytes+SECRET_KEY).hexdigest()
>>> print(signature)
7ae0e9e033b5fa53aa....
( 4 ) Now affix the signature at one end of the content
field of the original cookie.
# include signature as part of the cookie
>>> cookie.content = cookie.content + '|' + signature
>>> print(cookie)
name: _profile
content: uid=382|membership=regular|7ae0e9... <--- signature
domain: .example.com
path: /
send for: Encrypted connections only
expires: July 1 2030, 1:20:40 AM UTC
and that's what's sent to the client.
# add cookie to response
>>> response.set_cookie(cookie)
# send to browser -->
Upon receiving the cookie from the browser:
( 5 ) When the browser returns this cookie back to the server, strip the signature from the cookie's content
field to get back the original cookie.
# Upon receiving the cookie from browser
>>> cookie = request.get_cookie()
# pop the signature out of the cookie
>>> (cookie.content, popped_signature) = cookie.content.rsplit('|', 1)
( 6 ) Use the original cookie with the application's SECRET_KEY
to recalculate the signature using the same method as in step 3.
# recalculate signature using SECRET_KEY and original cookie
>>> cookie_bytes = str(cookie).encode('utf8')
>>> calculated_signature = sha1(cookie_bytes+SECRET_KEY).hexdigest()
( 7 ) Compare the calculated result with the signature previously popped out of the just received cookie. If they match, we know that the cookie has not been messed with. But if even just a space has been added to the cookie, the signatures won't match.
# if both signatures match, your cookie has not been modified
>>> good_cookie = popped_signature==calculated_signature
( 8 ) If they don't match then you may respond with any number of actions, log the event, discard the cookie, issue a fresh one, redirect to a login page, etc.
>>> if not good_cookie:
... security_log(cookie)
Hash-based Message Authentication Code (HMAC)
The type of signature generated above that requires a secret key to ensure the integrity of some contents is called in cryptography a Message Authentication Code or MAC.
I specified earlier that the example above is an oversimplification of that concept and that it wasn't a good idea to implement your own signing. That's because the algorithm used to sign cookies in Flask is called HMAC and is a bit more involved than the above simple step-by-step. The general idea is the same, but due to reasons beyond the scope of this discussion, the series of computations are a tad bit more complex.
If you're still interested in crafting a DIY, as it's usually the case, Python has some modules to help you get started :) here's a starting block:
import hmac
import hashlib
def create_signature(secret_key, msg, digestmod=None):
if digestmod is None:
digestmod = hashlib.sha1
mac = hmac.new(secret_key, msg=msg, digestmod=digestmod)
return mac.digest()
The documentaton for hmac and hashlib.
The "Demystification" of SECRET_KEY
:)
What's a "signature" in this context?
It's a method to ensure that some content has not been modified by anyone other than a person or an entity authorized to do so.
One of the simplest forms of signature is the "checksum", which simply verifies that two pieces of data are the same. For example, when installing software from source it's important to first confirm that your copy of the source code is identical to the author's. A common approach to do this is to run the source through a cryptographic hash function and compare the output with the checksum published on the project's home page.
Let's say for instance that you're about to download a project's source in a gzipped file from a web mirror. The SHA1 checksum published on the project's web page is 'eb84e8da7ca23e9f83....'
# so you get the code from the mirror
download https://mirror.example-codedump.com/source_code.tar.gz
# you calculate the hash as instructed
sha1(source_code.tar.gz)
> eb84e8da7c....
Both hashes are the same, you know that you have an identical copy.
What's a cookie?
An extensive discussion on cookies would go beyond the scope of this question. I provide an overview here since a minimal understanding can be useful to have a better understanding of how and why SECRET_KEY
is useful. I highly encourage you to follow up with some personal readings on HTTP Cookies.
A common practice in web applications is to use the client (web browser) as a lightweight cache. Cookies are one implementation of this practice. A cookie is typically some data added by the server to an HTTP response by way of its headers. It's kept by the browser which subsequently sends it back to the server when issuing requests, also by way of HTTP headers. The data contained in a cookie can be used to emulate what's called statefulness, the illusion that the server is maintaining an ongoing connection with the client. Only, in this case, instead of a wire to keep the connection "alive", you simply have snapshots of the state of the application after it has handled a client's request. These snapshots are carried back and forth between client and server. Upon receiving a request, the server first reads the content of the cookie to reestablish the context of its conversation with the client. It then handles the request within that context and before returning the response to the client, updates the cookie. The illusion of an ongoing session is thus maintained.
What does a cookie look like?
A typical cookie would look like this:
name: _profile
content: uid=382|status=genie
domain: .example.com
path: /
send for: Encrypted connections only
expires: July 1 2030, 1:20:40 AM UTC
Cookies are trivial to peruse from any modern browser. On Firefox for example go to Preferences > Privacy > History > remove individual cookies.
The content
field is the most relevant to the application. Other fields carry mostly meta instructions to specify various scopes of influence.
Why use cookies at all?
The short answer is performance. Using cookies, minimizes the need to look things up in various data stores (memory caches, files, databases, etc), thus speeding things up on the server application's side. Keep in mind that the bigger the cookie the heavier the payload over the network, so what you save in database lookup on the server you might lose over the network. Consider carefully what to include in your cookies.
Why would cookies need to be signed?
Cookies are used to keep all sorts of information, some of which can be very sensitive. They're also by nature not safe and require that a number of auxiliary precautions be taken to be considered secure in any way for both parties, client and server. Signing cookies specifically addresses the problem that they can be tinkered with in attempts to fool server applications. There are other measures to mitigate other types of vulnerabilities, I encourage you to read up more on cookies.
How can a cookie be tampered with?
Cookies reside on the client in text form and can be edited with no effort. A cookie received by your server application could have been modified for a number of reasons, some of which may not be innocent. Imagine a web application that keeps permission information about its users on cookies and grants privileges based on that information. If the cookie is not tinker-proof, anyone could modify theirs to elevate their status from "role=visitor" to "role=admin" and the application would be none the wiser.
Why is a SECRET_KEY
necessary to sign cookies?
Verifying cookies is a tad bit different than verifying source code the way it's described earlier. In the case of the source code, the original author is the trustee and owner of the reference fingerprint (the checksum), which will be kept public. What you don't trust is the source code, but you trust the public signature. So to verify your copy of the source you simply want your calculated hash to match the public hash.
In the case of a cookie however the application doesn't keep tra