aboutsummaryrefslogtreecommitdiffstats
path: root/python/itsdangerous/signer.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/itsdangerous/signer.py')
-rw-r--r--python/itsdangerous/signer.py179
1 files changed, 179 insertions, 0 deletions
diff --git a/python/itsdangerous/signer.py b/python/itsdangerous/signer.py
new file mode 100644
index 0000000..6bddc03
--- /dev/null
+++ b/python/itsdangerous/signer.py
@@ -0,0 +1,179 @@
+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