| import hashlib |
| import hmac |
| |
| from ._compat import constant_time_compare |
| from .encoding import _base64_alphabet |
| from .encoding import base64_decode |
| from .encoding import base64_encode |
| from .encoding import want_bytes |
| from .exc import BadSignature |
| |
| |
| class SigningAlgorithm(object): |
| """Subclasses must implement :meth:`get_signature` to provide |
| signature generation functionality. |
| """ |
| |
| def get_signature(self, key, value): |
| """Returns the signature for the given key and value.""" |
| raise NotImplementedError() |
| |
| def verify_signature(self, key, value, sig): |
| """Verifies the given signature matches the expected |
| signature. |
| """ |
| return constant_time_compare(sig, self.get_signature(key, value)) |
| |
| |
| class NoneAlgorithm(SigningAlgorithm): |
| """Provides an algorithm that does not perform any signing and |
| returns an empty signature. |
| """ |
| |
| def get_signature(self, key, value): |
| return b"" |
| |
| |
| class HMACAlgorithm(SigningAlgorithm): |
| """Provides signature generation using HMACs.""" |
| |
| #: The digest method to use with the MAC algorithm. This defaults to |
| #: SHA1, but can be changed to any other function in the hashlib |
| #: module. |
| default_digest_method = staticmethod(hashlib.sha1) |
| |
| def __init__(self, digest_method=None): |
| if digest_method is None: |
| digest_method = self.default_digest_method |
| self.digest_method = digest_method |
| |
| def get_signature(self, key, value): |
| mac = hmac.new(key, msg=value, digestmod=self.digest_method) |
| return mac.digest() |
| |
| |
| class Signer(object): |
| """This class can sign and unsign bytes, validating the signature |
| provided. |
| |
| 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 |
| where the same signed value in one part can mean something different |
| in another part is a security risk. |
| |
| See :ref:`the-salt` for an example of what the salt is doing and how |
| you can utilize it. |
| |
| .. versionadded:: 0.14 |
| ``key_derivation`` and ``digest_method`` were added as arguments |
| to the class constructor. |
| |
| .. versionadded:: 0.18 |
| ``algorithm`` was added as an argument to the class constructor. |
| """ |
| |
| #: The digest method to use for the signer. This defaults to |
| #: SHA1 but can be changed to any other function in the hashlib |
| #: module. |
| #: |
| #: .. versionadded:: 0.14 |
| default_digest_method = staticmethod(hashlib.sha1) |
| |
| #: Controls how the key is derived. The default is Django-style |
| #: concatenation. Possible values are ``concat``, ``django-concat`` |
| #: and ``hmac``. This is used for deriving a key from the secret key |
| #: with an added salt. |
| #: |
| #: .. versionadded:: 0.14 |
| default_key_derivation = "django-concat" |
| |
| def __init__( |
| self, |
| secret_key, |
| salt=None, |
| sep=".", |
| key_derivation=None, |
| digest_method=None, |
| algorithm=None, |
| ): |
| self.secret_key = want_bytes(secret_key) |
| self.sep = want_bytes(sep) |
| if self.sep in _base64_alphabet: |
| raise ValueError( |
| "The given separator cannot be used because it may be" |
| " contained in the signature itself. Alphanumeric" |
| " characters and `-_=` must not be used." |
| ) |
| self.salt = "itsdangerous.Signer" if salt is None else salt |
| if key_derivation is None: |
| key_derivation = self.default_key_derivation |
| self.key_derivation = key_derivation |
| if digest_method is None: |
| digest_method = self.default_digest_method |
| self.digest_method = digest_method |
| if algorithm is None: |
| algorithm = HMACAlgorithm(self.digest_method) |
| self.algorithm = algorithm |
| |
| def derive_key(self): |
| """This method is called to derive the key. The default key |
| derivation choices can be overridden here. Key derivation is not |
| intended to be used as a security method to make a complex key |
| out of a short password. Instead you should use large random |
| secret keys. |
| """ |
| salt = want_bytes(self.salt) |
| if self.key_derivation == "concat": |
| return self.digest_method(salt + self.secret_key).digest() |
| elif self.key_derivation == "django-concat": |
| return self.digest_method(salt + b"signer" + self.secret_key).digest() |
| elif self.key_derivation == "hmac": |
| mac = hmac.new(self.secret_key, digestmod=self.digest_method) |
| mac.update(salt) |
| return mac.digest() |
| elif self.key_derivation == "none": |
| return self.secret_key |
| else: |
| raise TypeError("Unknown key derivation method") |
| |
| def get_signature(self, value): |
| """Returns the signature for the given value.""" |
| value = want_bytes(value) |
| key = self.derive_key() |
| sig = self.algorithm.get_signature(key, value) |
| return base64_encode(sig) |
| |
| def sign(self, value): |
| """Signs the given string.""" |
| return want_bytes(value) + want_bytes(self.sep) + self.get_signature(value) |
| |
| def verify_signature(self, value, sig): |
| """Verifies the signature for the given value.""" |
| key = self.derive_key() |
| try: |
| sig = base64_decode(sig) |
| except Exception: |
| return False |
| return self.algorithm.verify_signature(key, value, sig) |
| |
| def unsign(self, signed_value): |
| """Unsigns the given string.""" |
| signed_value = want_bytes(signed_value) |
| sep = want_bytes(self.sep) |
| if sep not in signed_value: |
| raise BadSignature("No %r found in value" % self.sep) |
| value, sig = signed_value.rsplit(sep, 1) |
| if self.verify_signature(value, sig): |
| return value |
| raise BadSignature("Signature %r does not match" % sig, payload=value) |
| |
| def validate(self, signed_value): |
| """Only validates the given signed value. Returns ``True`` if |
| the signature exists and is valid. |
| """ |
| try: |
| self.unsign(signed_value) |
| return True |
| except BadSignature: |
| return False |