Index: django/contrib/signedcookies/middleware.py
===================================================================
--- django/contrib/signedcookies/middleware.py	(revision 0)
+++ django/contrib/signedcookies/middleware.py	(revision 0)
@@ -0,0 +1,23 @@
+import utils
+
+class SignedCookiesMiddleware(object):
+
+    def process_request(self, request):
+        for (key, signed_value) in request.COOKIES.items():
+            try:
+                request.COOKIES[key] = utils.unsign(key, signed_value)
+            except:
+                del request.COOKIES[key]
+
+    def process_response(self, request, response):
+        for (key, morsel) in response.cookies.items():
+            if morsel['expires'] == 0 and morsel['max-age'] == 0:
+                continue
+            response.set_cookie(key, utils.sign(key, morsel.value),
+                max_age=morsel['max-age'],
+                expires=morsel['expires'],
+                path=morsel['path'],
+                domain=morsel['domain'],
+                secure=morsel['secure']
+            )
+        return response
\ No newline at end of file
Index: django/contrib/signedcookies/__init__.py
===================================================================
Index: django/contrib/signedcookies/utils.py
===================================================================
--- django/contrib/signedcookies/utils.py	(revision 0)
+++ django/contrib/signedcookies/utils.py	(revision 0)
@@ -0,0 +1,24 @@
+import re
+
+from django.conf import settings
+
+try:
+    from hashlib import md5 as hash
+except ImportError:
+    from md5 import new as hash
+
+regex = re.compile(r'([0-9a-f]+):(.*)$')
+
+def get_digest(key, value):
+    string = ':'.join([settings.SECRET_KEY, key, value])
+    return hash(string).hexdigest()
+
+def unsign(key, signed_value):
+    "Given a key and signed value, return the unsigned value"
+    (signature, unsigned_value) = regex.match(signed_value).groups()
+    assert get_digest(key, unsigned_value) == signature
+    return unsigned_value
+
+def sign(key, unsigned_value):
+    "Given a key and unsigned value, return the signed value"
+    return '%s:%s' % (get_digest(key, unsigned_value), unsigned_value)
\ No newline at end of file
Index: docs/add_ons.txt
===================================================================
--- docs/add_ons.txt	(revision 5174)
+++ docs/add_ons.txt	(working copy)
@@ -182,6 +182,15 @@
 
 .. _redirects documentation: ../redirects/
 
+signedcookies
+=============
+
+An easy way to add extra security to cookie-based applications.
+
+See the `signed cookies documentation`_.
+
+.. _signed cookies documentation: ../signed_cookies/
+
 sites
 =====
 
Index: docs/signed_cookies.txt
===================================================================
--- docs/signed_cookies.txt	(revision 0)
+++ docs/signed_cookies.txt	(revision 0)
@@ -0,0 +1,137 @@
+==============
+Signed cookies
+==============
+
+While cookies are a convenient way to store basic persistent data in client
+browsers, users are free to edit them at will, making them inherently insecure.
+By attaching signatures to cookies, you can confidently trust that their values
+are safe from editing.
+
+How it works
+============
+
+Cookies are signed by attaching a digest to the cookie value that can be
+verified when the cookie is sent back to the server. This digest is created from
+the cookie's name and value, along with the site's secret_.
+
+Essentially, this creates a set of four values that must all remain intact in
+order for the cookie to validate properly. If any one of these is altered on a
+client computer, the cookie will be invalid.
+
+ * ``settings.SECRET_KEY``
+ * Name
+ * Value
+ * Signature
+
+This set of values ensures that users are unable to rename the cookie or edit
+its value without generating a new signature. Since the secret is known
+only to site administrators, users will be unable to generate signatures that
+will validate with altered values.
+
+.. _secret: ../settings/#secret-key
+
+Automatic signing
+=================
+
+The easiest and most reliable way to sign your cookies is to use the provided
+middleware, which will automatically handle everything behind the scenes. In
+fact, aside from a single line in your settings, using the middleware requires
+no changes in your code.
+
+Activating the middleware
+-------------------------
+
+Simply add ``'SignedCookiesMiddleware'`` to your ``MIDDLEWARE_CLASSES`` setting
+to enjoy signature protection for all your views. One thing to keep in mind is
+that, while all views are automatically handled, middleware will only be
+protected if they are positioned *after* signed cookies.
+
+Invalid cookies
+---------------
+
+Signing and validation are both handled transparently to views, with invalid
+cookies being silently removed from ``request.COOKIES`` prior to executing the
+view. This means that views will see no difference between an unsigned cookie
+and no cookie at all.
+
+As with a missing cookie, a proper cookie can simply be set within the view,
+which will then be signed properly and will validate on the next request.
+
+A note about sessions
+---------------------
+
+Though the `sessions framework`_ uses cookies, it only stores the ID, which is
+considered safe. This means that it can safely be placed before signed cookies
+in your ``MIDDLEWARE_CLASSES``.
+
+.. _sessions framework: ../sessions/
+
+Manual signing
+==============
+
+If only a portion of the project needs signed cookies, there is a manual option
+that doesn't rely on middleware. There are two functions provided, which can be
+used to manually sign and validate cookie values within views.
+
+These utilities live in ``django.contrib.signedcookies.utils``.
+
+sign(key, unsigned_value)
+-------------------------
+
+When setting cookies, a signature can be attached manually by using this simple
+function. It takes the cookie's name and value and returns a signed value that
+is suitable for being set to a cookie.
+
+This function will not set the cookie to the response; this still has to be done
+according to HttpResponse_.
+
+.. _HttpResponse: ../request_response/
+
+unsign(key, signed_value)
+-------------------------
+
+When receiving a signed cookie from an incoming request, the signature will
+still be attached, and must be manually validated and removed using this
+function. This single function handles both tasks, raising an error for invalid
+or unsigned cookies, and returning the plain, unsigned value when successful.
+
+Invalid cookies
+---------------
+
+If the cookie is not signed or invalid, ``unsign`` will raise an exception,
+which must be handled by the view to prevent server error responses. There are
+multiple exception types that could be thrown, so a simple ``except`` would be
+appropriate.
+
+As shown in the example below, a single try/except block is suitable for
+handling errors due to a missing, unsigned or invalid cookie.
+
+Example
+-------
+
+The following example assumes you have some shopping cart data that can be
+stored in a cookie, and will be used to populate some ``ShoppingCart`` object.
+
+    from django.contrib.signedcookies import utils
+    from myapp.utils import ShoppingCart
+    
+    def view_cart(request):
+        try:
+            cart = ShoppingCart(utils.unsign('cart', request.COOKIES['cart']))
+        except:
+            # The cookie was no good, so a new one should be set
+            cart = ShoppingCart()
+            response.set_cookie('cart', utils.sign('cart', cart.cookie_data))
+        
+        # Continue processing the shopping cart
+
+A note about stolen cookies
+===========================
+
+While cookie signing is a reasonable way to ensure that a cookie has not been
+edited, there is no guarantee that it is being requested by the same computer
+where it was first set. If a computer is compromised and its cookies stolen,
+the thief would be able to use the signed cookie with no interference.
+
+Therefore, any applications that implement mission-critical functionality with
+signed cookies should take extra precautions to anticipate cookie theft.
\ No newline at end of file
Index: tests/regressiontests/signedcookies/__init__.py
===================================================================
Index: tests/regressiontests/signedcookies/tests.py
===================================================================
--- tests/regressiontests/signedcookies/tests.py	(revision 0)
+++ tests/regressiontests/signedcookies/tests.py	(revision 0)
@@ -0,0 +1,58 @@
+import unittest
+
+from django.http import HttpRequest, HttpResponse
+
+from django.contrib.signedcookies import middleware, utils
+
+class SignedCookiesTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.middleware = middleware.SignedCookiesMiddleware()
+        (self.key, self.unsigned_value) = ('key', 'value')
+        self.signature = utils.get_digest(self.key, self.unsigned_value)
+        self.signed_value = '%s:%s' % (self.signature, self.unsigned_value)
+
+    def test_sign(self):
+        "Make sure cookie values can be signed directly"
+        signed_value = utils.sign(self.key, self.unsigned_value)
+        self.assertEqual(signed_value, self.signed_value)
+
+    def test_unsign(self):
+        "Make sure cookie values can be unsigned directly"
+        unsigned_value = utils.unsign(self.key, self.signed_value)
+        self.assertEqual(unsigned_value, self.unsigned_value)
+
+    def test_unsigned_cookie(self):
+        "Make sure process_request removes unsigned cookies"
+        request = HttpRequest()
+        request.COOKIES[self.key] = self.unsigned_value
+        self.middleware.process_request(request)
+        self.assert_(self.key not in request.COOKIES)
+
+    def test_invalid_cookie(self):
+        "Make sure process_request removes invalid cookies"
+        request = HttpRequest()
+        request.COOKIES[self.key] = self.signed_value + 'test'
+        self.middleware.process_request(request)
+        self.assert_(self.key not in request.COOKIES)
+
+    def test_valid_cookie(self):
+        "Make sure process_request removes signatures from valid cookies"
+        request = HttpRequest()
+        request.COOKIES[self.key] = self.signed_value
+        self.middleware.process_request(request)
+        self.assertEqual(request.COOKIES[self.key], self.unsigned_value)
+
+    def test_set_cookie(self):
+        "Make sure process_response signs cookies appropriately"
+        response = HttpResponse()
+        response.set_cookie(self.key, self.unsigned_value)
+        self.middleware.process_response(HttpRequest(), response)
+        self.assertEqual(response.cookies[self.key].value, self.signed_value)
+
+    def test_delete_cookie(self):
+        "Make sure process_response leaves deleted cookies alone"
+        response = HttpResponse()
+        response.delete_cookie(self.key)
+        self.middleware.process_response(HttpRequest(), response)
+        self.assertEqual(response.cookies[self.key].value, '')
\ No newline at end of file
Index: tests/regressiontests/signedcookies/models.py
===================================================================
