Source code for django.core.signing

"""Functions for creating and restoring url-safe signed JSON objects.The format used looks like this:>>> signing.dumps("hello")'ImhlbGxvIg:1QaUZC:YIye-ze3TTx7gtSv422nZA4sgmk'There are two components here, separated by a ':'. The first component is aURLsafe base64 encoded JSON of the object passed to dumps(). The secondcomponent is a base64 encoded hmac/SHA1 hash of "$first_component:$secret"signing.loads(s) checks the signature and returns the deserialized object.If the signature fails, a BadSignature exception is raised.>>> signing.loads("ImhlbGxvIg:1QaUZC:YIye-ze3TTx7gtSv422nZA4sgmk")'hello'>>> signing.loads("ImhlbGxvIg:1QaUZC:YIye-ze3TTx7gtSv422nZA4sgmk-modified")...BadSignature: Signature failed: ImhlbGxvIg:1QaUZC:YIye-ze3TTx7gtSv422nZA4sgmk-modifiedYou can optionally compress the JSON prior to base64 encoding it to savespace, using the compress=True argument. This checks if compression actuallyhelps and only applies compression if the result is a shorter string:>>> signing.dumps(list(range(1, 20)), compress=True)'.eJwFwcERACAIwLCF-rCiILN47r-GyZVJsNgkxaFxoDgxcOHGxMKD_T7vhAml:1QaUaL:BA0thEZrp4FQVXIXuOvYJtLJSrQ'The fact that the string is compressed is signalled by the prefixed '.' at thestart of the base64 JSON.There are 65 url-safe characters: the 64 used by url-safe base64 and the ':'.These functions make use of all of them."""importbase64importdatetimeimportjsonimportreimporttimeimportzlibfromdjango.confimportsettingsfromdjango.utilsimportbaseconvfromdjango.utils.cryptoimportconstant_time_compare,salted_hmacfromdjango.utils.encodingimportforce_bytesfromdjango.utils.module_loadingimportimport_string_SEP_UNSAFE=re.compile(r'^[A-z0-9-_=]*$')classBadSignature(Exception):"""Signature does not match."""passclassSignatureExpired(BadSignature):"""Signature timestamp is older than required max_age."""passdefb64_encode(s):returnbase64.urlsafe_b64encode(s).strip(b'=')defb64_decode(s):pad=b'='*(-len(s)%4)returnbase64.urlsafe_b64decode(s+pad)defbase64_hmac(salt,value,key):returnb64_encode(salted_hmac(salt,value,key).digest()).decode()defget_cookie_signer(salt='django.core.signing.get_cookie_signer'):Signer=import_string(settings.SIGNING_BACKEND)key=force_bytes(settings.SECRET_KEY)# SECRET_KEY may be str or bytes.returnSigner(b'django.http.cookies'+key,salt=salt)classJSONSerializer:""" Simple wrapper around json to be used in signing.dumps and signing.loads. """defdumps(self,obj):returnjson.dumps(obj,separators=(',',':')).encode('latin-1')defloads(self,data):returnjson.loads(data.decode('latin-1'))

[docs]defdumps(obj,key=None,salt='django.core.signing',serializer=JSONSerializer,compress=False):""" Return URL-safe, hmac/SHA1 signed base64 compressed JSON string. If key is None, use settings.SECRET_KEY instead. If compress is True (not the default), check if compressing using zlib can save some space. Prepend a '.' to signify compression. This is included in the signature, to protect against zip bombs. Salt can be used to namespace the hash, so that a signed string is only valid for a given namespace. Leaving this at the default value or re-using a salt value across different parts of your application without good cause is a security risk. The serializer is expected to return a bytestring. """data=serializer().dumps(obj)# Flag for if it's been compressed or notis_compressed=Falseifcompress:# Avoid zlib dependency unless compress is being usedcompressed=zlib.compress(data)iflen(compressed)<(len(data)-1):data=compressedis_compressed=Truebase64d=b64_encode(data).decode()ifis_compressed:base64d='.'+base64dreturnTimestampSigner(key,salt=salt).sign(base64d)

[docs]classSigner:def__init__(self,key=None,sep=':',salt=None):# Use of native strings in all versions of Pythonself.key=keyorsettings.SECRET_KEYself.sep=sepif_SEP_UNSAFE.match(self.sep):raiseValueError('Unsafe Signer separator: %r (cannot be empty or consist of ''only A-z0-9-_=)'%sep,)self.salt=saltor'%s.%s'%(self.__class__.__module__,self.__class__.__name__)defsignature(self,value):returnbase64_hmac(self.salt+'signer',value,self.key)defsign(self,value):return'%s%s%s'%(value,self.sep,self.signature(value))defunsign(self,signed_value):ifself.sepnotinsigned_value:raiseBadSignature('No "%s" found in value'%self.sep)value,sig=signed_value.rsplit(self.sep,1)ifconstant_time_compare(sig,self.signature(value)):returnvalueraiseBadSignature('Signature "%s" does not match'%sig)

[docs]defunsign(self,value,max_age=None):""" Retrieve original value and check it wasn't signed more than max_age seconds ago. """result=super().unsign(value)value,timestamp=result.rsplit(self.sep,1)timestamp=baseconv.base62.decode(timestamp)ifmax_ageisnotNone:ifisinstance(max_age,datetime.timedelta):max_age=max_age.total_seconds()# Check timestamp is not older than max_ageage=time.time()-timestampifage>max_age:raiseSignatureExpired('Signature age %s > %s seconds'%(age,max_age))returnvalue