Account Options

  1. Sign in
The old Google Groups will be going away soon.
Switch to the new Google Groups.
Google Groups Home
« Groups Home
Message from discussion Adding signing (and signed cookies) to Django core
The group you are posting to is a Usenet group. Messages posted to this group will make your email address visible to anyone on the Internet.
Your reply message has not been sent.
Your post was successful
 
From:
To:
Cc:
Followup To:
Add Cc | Add Followup-to | Edit Subject
Subject:
Validation:
For verification purposes please type the characters you see in the picture below or the numbers you hear by clicking the accessibility icon. Listen and type the numbers you hear
 
Tobias McNulty  
View profile  
 More options Sep 24 2009, 1:43 pm
From: Tobias McNulty <tob...@caktusgroup.com>
Date: Thu, 24 Sep 2009 13:43:48 -0400
Local: Thurs, Sep 24 2009 1:43 pm
Subject: Re: Adding signing (and signed cookies) to Django core

+1 for signed cookies.  Your API looks reasonable and I'd agree that
set_cookie(..., signed=True) fits better with the rest of the API as well.
What about some sanity checking to make sure that, if SECRET_KEY is used, it
is, at the very least, a non-empty string?

On Thu, Sep 24, 2009 at 1:18 PM, Simon Willison <si...@simonwillison.net>wrote:

> As mentioned in the thread about cookie-based notifications, at the
> DjangoCon Sprints I raised the subject of adding signing (and signed
> cookies) to Django core.

> I've found myself using signing more and more over time, and I think
> it's a concept which is common enough to deserve inclusion in Django -
> if anything, its use should be actively encouraged by the framework.

> It's also something that's hard to do correctly. At the sprints Armin
> pointed out that I should be using hmac, not straight sha1, for
> generating signatures (something Django itself gets wrong in the few
> places that implement signing already). Having a cryptographer-
> approved implementation will save a lot of people from making the same
> mistakes.

> Signed cookies
> ==============

> On top of signing (which I imagine would live in django.utils) I'd
> like to add a signed cookie implementation. Signed cookies are useful
> for all sorts of things - most importantly, they can be used in place
> of sessions in many places, which improves performance (and overall
> scalability) by removing the need to access a persistent session
> backend on every hit. Set the user's username in a signed cookie and
> you can display "Logged in as X" messages on every page without any
> persistence layer calls at all.

> I think signed cookies should either be a separate API from
> response.set_cookie or should be provided as an additional argument to
> that method. I'm not a fan of signing using middleware (as seen in
> http://code.google.com/p/django-signedcookies/ ) since that approach
> signs everything - some cookies, such as those used by Google
> Analytics, need to remain unsigned.

> So the API could either be:

>    response.set_signed_cookie(key, value)

> Or...

>    response.set_cookie(key, value, signed=True)

> (I prefer the latter option)

> Proposed signing implementation
> ===============================

> I'd be happy to donate my signing code from django-openid to the
> cause, which was written to be usable entirely separately from the
> rest of the django-openid codebase:

> http://github.com/simonw/django-openid/blob/master/django_openid/sign...

> http://github.com/simonw/django-openid/blob/master/django_openid/test...

> This offers two APIs: sign/unsign and dumps/loads. sign and unsign
> generate and append signatures to bytestrings and confirm that they
> have not been tampered with. dumps and loads can be used to create
> signed pickles of arbitrary Python objects.

> Here's what the API would look like with this library:

> >>> from django.utils import signed
> >>> signed.sign('hello')
> 'hello.9asVJn9dfv6qLJ_BYObzF7mmH8c'

> The signature is a URL-safe base64 encoded digest of the hmac/sha1. I
> used base64 rather than .hexdigest() for space reasons - base64
> digests are 27 characters, hexadecimal digests are 40. When you're
> including signatures in cookies and URLs (especially account recovery
> URLs sent out in plain text, 80 character wide e-mails) every byte
> counts.

> >>> signed.unsign('hello.9asVJn9dfv6qLJ_BYObzF7mmH8c')
> 'hello'
> >>> signed.unsign('hello.badsignature')
> Traceback (most recent call last):
> ...
> BadSignature: Signature failed: badsignature

> BadSignature is a subclass of ValueError, meaning lazy developers
> (like myself) can do the following rather than importing the exception
> itself:

> try:
>    value = signed.unsign(signed_value)
> except ValueError:
>    return tamper_error_view(request)

> >>> signed.dumps({"a": "foo"})
> 'KGRwMApTJ2EnCnAxClMnZm9vJwpwMgpzLg.mYepoYkzWwXRmsCTVJm3Mb0HHz4'
> >>> signed.loads(_)
> {'a': 'foo'}

> Again, the pickle is URL-safe base64 encoded to take up less valuable
> cookie space and generally make it easier to pass around on the Web. A
> nice thing about URL-safe base64 is that it uses 64 out of the 65 URL-
> safe characters (by URL-safe I mean characters that are left unchanged
> by Python's urllib.urlencode function) - the remaining character is
> the period, which I use to separate the pickle from the signature.

> signed.dumps takes a couple of extra optional arguments. The first is
> compress=True (default is False) which zlib compresses the pickle if
> doing so will save any space:

> >>> import this # to get an object worth compressing
> ...
> >>> len(signed.dumps(this.s))
> 1207
> >>> len(signed.dumps(this.s, compress=True))
> 637

> By default, all signatures use Django's SECRET_KEY. If you want to
> sign with a different key, you can pass it as an argument to the
> various functions:

> >>> signed.sign('hello', key='sekrit')
> 'hello.o6MKehoOfZ2b2FU84wzibW6IWxI'
> >>> signed.unsign(_, key='sekrit')
> 'hello'

> The dumps and loads methods also take a key argument, as well as an
> additional optional extra_key argument for if you want to generate
> different signatures for different parts of your application (useful
> for the extra paranoid):

> >>> signed.dumps('hello', extra_key='ultra')
> 'UydoZWxsbycKcDAKLg.1XYDpILo5xqSwImfa3WuJJT4RPo'
> >>> signed.loads(_, extra_key='ultra')
> 'hello'

> We'd want to get a proper cryptographer to give this the once-over
> before adding it to core, but I'm generally happy with the API. It
> could be argued that it's over kill and just sticking signed.sign and
> signed.unsign in would be enough, but I'm pretty keen on the
> convenience of dumps and loads.

> Thinking about it further, an additional API that just gives you the
> signature without including the original value would mean it could be
> used for hashing passwords as well.

> Potential uses
> ==============

> Lots of stuff:

> - Signed cookies (obviously)
> - Generating CSRF tokens
> - Secure /logout/ and /change-language/ links
> - Securing /login/?next=/some/path/
> - Securing hidden fields in form wizards
> - Recover-your-account links in e-mails

> We already use signing in a few places in Django core (mainly sessions
> and form wizards), currently using md5 without hmac - sha1/hmac would
> be an instant improvement.

> SECRET_KEY considerations
> =========================

> One thing that worries me slightly about increasing the amount of
> signing going on in Django is that it elevates the importance of the
> SECRET_KEY. I'm currently ignorant of best practices regarding
> protecting this kind of shared secret, but the steps we take (***ing
> it out from the debug pages and otherwise ignoring it) could almost
> certainly be improved.

> One thing that's particularly interesting to me is what happens when
> you change your secret. If you're changing your secret because it's
> leaked then obviously you want stuff signed with the old secret to
> become invalid immediately, but I can imagine some users wanting to
> rotate their secret keys on a continual basis for added security
> against brute force attacks.

> If you're rotating your secret, invalidating all of your users signed
> cookies etc is a bit of an annoyance. It might be worth supporting two
> secrets - the current SECRET_KEY and an optional OLD_SECRET_KEY - with
> unsigning operations falling back on the old key if the current key
> fails. This would allow users to deploy a new secret while keeping the
> old one valid for a week or so, upgrading any tokens that use the old
> key in the process. This suggestion is inspired by Amazon's recent
> announcement of a similar feature for handling web service access
> credentials:

> http://aws.typepad.com/aws/2009/09/aws-access-credential-rotation.html

> This is probably all too much complication, but it's something that's
> been nagging at me since I started increasing my dependence on the
> SECRET_KEY setting.

> So... what do people think? Is this a feature suitable for Django
> (obviously I think so)? Is this as simple as getting a cryptographer's
> input and dropping signed.py in to django.utils or are there other
> design factors we should consider?

> Cheers,

> Simon


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.