Sometimes it's useful to sign data to ensure the user does not tamper with it - for example, cookies or hidden form variables. SHA1 is cryptographically secure but weighs in at 40 characters, which is pretty long if you're going to be passing the data around in a URL or a cookie.
These functions knock an SHA1 hash down to just 27 characters, thanks to a base65 encoding that only uses URL-safe characters (defined as characters which are unmodified by Python's urllib.urlencode function). This compressed hash can then be passed around in cookies or URLs, and uncompressed again when the signature needs to be checked.
UPDATE: You probably shouldn't use this; see http://fi.am/entry/urlsafe-base64-encodingdecoding-in-two-lines/ for a smarter approach based on Python's built-in base64 module.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
""" YOU PROBABLY SHOULDN'T USE THIS See http://fi.am/entry/urlsafe-base64-encodingdecoding-in-two-lines/ You can do this using Python's built-in base64 library in just two lines of code: import base64 def uri_b64encode(s): return base64.urlsafe_b64encode(s).strip('=') def uri_b64decode(s): return base64.urlsafe_b64decode(s + '=' * (len(s) % 4)) ---------------------------------------------------- Utility functions for signing a string using SHA1, then shrinking that SHA1 hash down to as short a string as possible using lossless base65 compression. >>> data = "Hello" >>> secret = "sekrit" >>> sig = sign(data, secret) >>> sig 'F7wP0YkP663d-n3yRDQVd8p0GC' >>> verify(data, secret, sig) True """ # Characters that are NOT encoded by urllib.urlencode: URLSAFE = '-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' BASE10 = "0123456789" import hashlib def sign(s, key): return base65_sha1(s + ':' + key) def verify(s, key, sig): return sign(s, key) == sig def base65_sha1(s): return int_to_base65(int(hashlib.sha1(s).hexdigest(), 16)) def sha1_from_base65(s): i = base65_to_int(s) return hex(i).replace('0x', '') def int_to_base65(i): return baseconvert(str(i).lower().replace('l', ''), BASE10, URLSAFE) def base65_to_int(s): return baseconvert(s, URLSAFE, BASE10) def baseconvert(number_string, from_digits, to_digits): "Convert a number between two bases of arbitrary digits" # Inspired by http://code.activestate.com/recipes/111286/ # Convert number_string (in from_digits encoding) to an integer i = 0L for digit in str(number_string): i = i * len(from_digits) + from_digits.index(digit) # Convert integer to to_digits encoding res =  while i > 0: res.insert(0, to_digits[i % len(to_digits)]) i = i / len(to_digits) return ''.join(res)
More like this
- Stuff by NixonDash 1 month ago
- Add custom fields to the built-in Group model by jmoppel 3 months ago
- Month / Year SelectDateWidget based on django SelectDateWidget by pierreben 6 months, 2 weeks ago
- Python Django CRUD Example Tutorial by tuts_station 7 months ago
- Browser-native date input field by kytta 8 months, 2 weeks ago
I suppose I should do a round of upgrades on django-signedcookies anyway, and this would be a very useful addition.
This has another great use: for sites where you want to use an email address as the primary user identifier (no username), one way of generating unique usernames to satisfy Django's User object is to make a hash of the (unique) email address. The problem is that User.username is only 30 chars, not long enough for a 40-character hash. But a 27-character encoding of the hash just fits!
Nice snippet! I have one question though.
Whats the point of the replace('L','')? Isn't the string already lowercase by this point?
Might want to make another full snippet, because the approach in the comments doesn't do the
signfunctionality (sha1 digest).
Please login first before commenting.