functions for creating and restoring urlsafe signed json objects the format used looks like this signing dumpshello imhlbgxvig 1qauzc yiyeze3ttx7gtsv422nza4sgmk there are two components here separated by a the first component is a urlsafe base64 encoded json of the object passed to dumps the second component is a base64 encoded hmacsha256 hash of firstcomponent secret signing loadss checks the signature and returns the deserialized object if the signature fails a badsignature exception is raised signing loadsimhlbgxvig 1qauzc yiyeze3ttx7gtsv422nza4sgmk hello signing loadsimhlbgxvig 1qauzc yiyeze3ttx7gtsv42modified badsignature signature imhlbgxvig 1qauzc yiyeze3ttx7gtsv42modified does not match you can optionally compress the json prior to base64 encoding it to save space using the compresstrue argument this checks if compression actually helps and only applies compression if the result is a shorter string signing dumpslistrange1 20 compresstrue ejwfwceracaiwlcfrciiln47rgyzvjsngkxafxodgxcohgxmkdt7vhaml 1qaual ba0thezrp4fqvxixuovyjtljsrq the fact that the string is compressed is signalled by the prefixed at the start of the base64 json there are 65 urlsafe characters the 64 used by urlsafe base64 and the these functions make use of all of them signature does not match pass class signatureexpiredbadsignature secretkeys items may be str or bytes simple wrapper around json to be used in signing dumps and signing loads return urlsafe hmac signed base64 compressed json string if key is none use settings secretkey instead the hmac algorithm is the default signer algorithm 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 reusing a salt value across different parts of your application without good cause is a security risk the serializer is expected to return a bytestring reverse of dumps raise badsignature if signature fails the serializer is expected to accept a bytestring return urlsafe hmac signed base64 compressed json string 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 the serializer is expected to return a bytestring flag for if it s been compressed or not avoid zlib dependency unless compress is being used signer unsign returns str but base64 and zlib compression operate on bytes it s compressed uncompress it first retrieve original value and check it wasn t signed more than maxage seconds ago check timestamp is not older than maxage signature does not match signature timestamp is older than required max_age secret_keys items may be str or bytes simple wrapper around json to be used in signing dumps and signing loads return url safe hmac signed base64 compressed json string if key is none use settings secret_key instead the hmac algorithm is the default signer algorithm 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 reverse of dumps raise badsignature if signature fails the serializer is expected to accept a bytestring return url safe hmac signed base64 compressed json string 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 the serializer is expected to return a bytestring flag for if it s been compressed or not avoid zlib dependency unless compress is being used signer unsign returns str but base64 and zlib compression operate on bytes it s compressed uncompress it first retrieve original value and check it wasn t signed more than max_age seconds ago check timestamp is not older than max_age
import base64
import datetime
import json
import time
import zlib

from django.conf import settings
from django.utils.crypto import constant_time_compare, salted_hmac
from django.utils.encoding import force_bytes
from django.utils.module_loading import import_string
from django.utils.regex_helper import _lazy_re_compile

_SEP_UNSAFE = _lazy_re_compile(r"^[A-z0-9-_=]*$")
BASE62_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"


class BadSignature(Exception):
    

    pass


class SignatureExpired(BadSignature):
    

    pass


def b62_encode(s):
    if s == 0:
        return "0"
    sign = "-" if s < 0 else ""
    s = abs(s)
    encoded = ""
    while s > 0:
        s, remainder = divmod(s, 62)
        encoded = BASE62_ALPHABET[remainder] + encoded
    return sign + encoded


def b62_decode(s):
    if s == "0":
        return 0
    sign = 1
    if s[0] == "-":
        s = s[1:]
        sign = -1
    decoded = 0
    for digit in s:
        decoded = decoded * 62 + BASE62_ALPHABET.index(digit)
    return sign * decoded


def b64_encode(s):
    return base64.urlsafe_b64encode(s).strip(b"=")


def b64_decode(s):
    pad = b"=" * (-len(s) % 4)
    return base64.urlsafe_b64decode(s + pad)


def base64_hmac(salt, value, key, algorithm="sha1"):
    return b64_encode(
        salted_hmac(salt, value, key, algorithm=algorithm).digest()
    ).decode()


def _cookie_signer_key(key):
    
    return b"django.http.cookies" + force_bytes(key)


def get_cookie_signer(salt="django.core.signing.get_cookie_signer"):
    Signer = import_string(settings.SIGNING_BACKEND)
    return Signer(
        key=_cookie_signer_key(settings.SECRET_KEY),
        fallback_keys=map(_cookie_signer_key, settings.SECRET_KEY_FALLBACKS),
        salt=salt,
    )


class JSONSerializer:
    

    def dumps(self, obj):
        return json.dumps(obj, separators=(",", ":")).encode("latin-1")

    def loads(self, data):
        return json.loads(data.decode("latin-1"))


def dumps(
    obj, key=None, salt="django.core.signing", serializer=JSONSerializer, compress=False
):
    
    return TimestampSigner(key=key, salt=salt).sign_object(
        obj, serializer=serializer, compress=compress
    )


def loads(
    s,
    key=None,
    salt="django.core.signing",
    serializer=JSONSerializer,
    max_age=None,
    fallback_keys=None,
):
    
    return TimestampSigner(
        key=key, salt=salt, fallback_keys=fallback_keys
    ).unsign_object(
        s,
        serializer=serializer,
        max_age=max_age,
    )


class Signer:
    def __init__(
        self, *, key=None, sep=":", salt=None, algorithm=None, fallback_keys=None
    ):
        self.key = key or settings.SECRET_KEY
        self.fallback_keys = (
            fallback_keys
            if fallback_keys is not None
            else settings.SECRET_KEY_FALLBACKS
        )
        self.sep = sep
        self.salt = salt or "%s.%s" % (
            self.__class__.__module__,
            self.__class__.__name__,
        )
        self.algorithm = algorithm or "sha256"
        if _SEP_UNSAFE.match(self.sep):
            raise ValueError(
                "Unsafe Signer separator: %r (cannot be empty or consist of "
                "only A-z0-9-_=)" % sep,
            )

    def signature(self, value, key=None):
        key = key or self.key
        return base64_hmac(self.salt + "signer", value, key, algorithm=self.algorithm)

    def sign(self, value):
        return "%s%s%s" % (value, self.sep, self.signature(value))

    def unsign(self, signed_value):
        if self.sep not in signed_value:
            raise BadSignature('No "%s" found in value' % self.sep)
        value, sig = signed_value.rsplit(self.sep, 1)
        for key in [self.key, *self.fallback_keys]:
            if constant_time_compare(sig, self.signature(value, key)):
                return value
        raise BadSignature('Signature "%s" does not match' % sig)

    def sign_object(self, obj, serializer=JSONSerializer, compress=False):
        
        data = serializer().dumps(obj)
        
        is_compressed = False

        if compress:
            
            compressed = zlib.compress(data)
            if len(compressed) < (len(data) - 1):
                data = compressed
                is_compressed = True
        base64d = b64_encode(data).decode()
        if is_compressed:
            base64d = "." + base64d
        return self.sign(base64d)

    def unsign_object(self, signed_obj, serializer=JSONSerializer, **kwargs):
        
        
        base64d = self.unsign(signed_obj, **kwargs).encode()
        decompress = base64d[:1] == b"."
        if decompress:
            
            base64d = base64d[1:]
        data = b64_decode(base64d)
        if decompress:
            data = zlib.decompress(data)
        return serializer().loads(data)


class TimestampSigner(Signer):
    def timestamp(self):
        return b62_encode(int(time.time()))

    def sign(self, value):
        value = "%s%s%s" % (value, self.sep, self.timestamp())
        return super().sign(value)

    def unsign(self, value, max_age=None):
        
        result = super().unsign(value)
        value, timestamp = result.rsplit(self.sep, 1)
        timestamp = b62_decode(timestamp)
        if max_age is not None:
            if isinstance(max_age, datetime.timedelta):
                max_age = max_age.total_seconds()
            
            age = time.time() - timestamp
            if age > max_age:
                raise SignatureExpired("Signature age %s > %s seconds" % (age, max_age))
        return value