signer.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import hashlib
  2. import hmac
  3. from ._compat import constant_time_compare
  4. from .encoding import _base64_alphabet
  5. from .encoding import base64_decode
  6. from .encoding import base64_encode
  7. from .encoding import want_bytes
  8. from .exc import BadSignature
  9. class SigningAlgorithm(object):
  10. """Subclasses must implement :meth:`get_signature` to provide
  11. signature generation functionality.
  12. """
  13. def get_signature(self, key, value):
  14. """Returns the signature for the given key and value."""
  15. raise NotImplementedError()
  16. def verify_signature(self, key, value, sig):
  17. """Verifies the given signature matches the expected
  18. signature.
  19. """
  20. return constant_time_compare(sig, self.get_signature(key, value))
  21. class NoneAlgorithm(SigningAlgorithm):
  22. """Provides an algorithm that does not perform any signing and
  23. returns an empty signature.
  24. """
  25. def get_signature(self, key, value):
  26. return b""
  27. class HMACAlgorithm(SigningAlgorithm):
  28. """Provides signature generation using HMACs."""
  29. #: The digest method to use with the MAC algorithm. This defaults to
  30. #: SHA1, but can be changed to any other function in the hashlib
  31. #: module.
  32. default_digest_method = staticmethod(hashlib.sha1)
  33. def __init__(self, digest_method=None):
  34. if digest_method is None:
  35. digest_method = self.default_digest_method
  36. self.digest_method = digest_method
  37. def get_signature(self, key, value):
  38. mac = hmac.new(key, msg=value, digestmod=self.digest_method)
  39. return mac.digest()
  40. class Signer(object):
  41. """This class can sign and unsign bytes, validating the signature
  42. provided.
  43. Salt can be used to namespace the hash, so that a signed string is
  44. only valid for a given namespace. Leaving this at the default value
  45. or re-using a salt value across different parts of your application
  46. where the same signed value in one part can mean something different
  47. in another part is a security risk.
  48. See :ref:`the-salt` for an example of what the salt is doing and how
  49. you can utilize it.
  50. .. versionadded:: 0.14
  51. ``key_derivation`` and ``digest_method`` were added as arguments
  52. to the class constructor.
  53. .. versionadded:: 0.18
  54. ``algorithm`` was added as an argument to the class constructor.
  55. """
  56. #: The digest method to use for the signer. This defaults to
  57. #: SHA1 but can be changed to any other function in the hashlib
  58. #: module.
  59. #:
  60. #: .. versionadded:: 0.14
  61. default_digest_method = staticmethod(hashlib.sha1)
  62. #: Controls how the key is derived. The default is Django-style
  63. #: concatenation. Possible values are ``concat``, ``django-concat``
  64. #: and ``hmac``. This is used for deriving a key from the secret key
  65. #: with an added salt.
  66. #:
  67. #: .. versionadded:: 0.14
  68. default_key_derivation = "django-concat"
  69. def __init__(
  70. self,
  71. secret_key,
  72. salt=None,
  73. sep=".",
  74. key_derivation=None,
  75. digest_method=None,
  76. algorithm=None,
  77. ):
  78. self.secret_key = want_bytes(secret_key)
  79. self.sep = want_bytes(sep)
  80. if self.sep in _base64_alphabet:
  81. raise ValueError(
  82. "The given separator cannot be used because it may be"
  83. " contained in the signature itself. Alphanumeric"
  84. " characters and `-_=` must not be used."
  85. )
  86. self.salt = "itsdangerous.Signer" if salt is None else salt
  87. if key_derivation is None:
  88. key_derivation = self.default_key_derivation
  89. self.key_derivation = key_derivation
  90. if digest_method is None:
  91. digest_method = self.default_digest_method
  92. self.digest_method = digest_method
  93. if algorithm is None:
  94. algorithm = HMACAlgorithm(self.digest_method)
  95. self.algorithm = algorithm
  96. def derive_key(self):
  97. """This method is called to derive the key. The default key
  98. derivation choices can be overridden here. Key derivation is not
  99. intended to be used as a security method to make a complex key
  100. out of a short password. Instead you should use large random
  101. secret keys.
  102. """
  103. salt = want_bytes(self.salt)
  104. if self.key_derivation == "concat":
  105. return self.digest_method(salt + self.secret_key).digest()
  106. elif self.key_derivation == "django-concat":
  107. return self.digest_method(salt + b"signer" + self.secret_key).digest()
  108. elif self.key_derivation == "hmac":
  109. mac = hmac.new(self.secret_key, digestmod=self.digest_method)
  110. mac.update(salt)
  111. return mac.digest()
  112. elif self.key_derivation == "none":
  113. return self.secret_key
  114. else:
  115. raise TypeError("Unknown key derivation method")
  116. def get_signature(self, value):
  117. """Returns the signature for the given value."""
  118. value = want_bytes(value)
  119. key = self.derive_key()
  120. sig = self.algorithm.get_signature(key, value)
  121. return base64_encode(sig)
  122. def sign(self, value):
  123. """Signs the given string."""
  124. return want_bytes(value) + want_bytes(self.sep) + self.get_signature(value)
  125. def verify_signature(self, value, sig):
  126. """Verifies the signature for the given value."""
  127. key = self.derive_key()
  128. try:
  129. sig = base64_decode(sig)
  130. except Exception:
  131. return False
  132. return self.algorithm.verify_signature(key, value, sig)
  133. def unsign(self, signed_value):
  134. """Unsigns the given string."""
  135. signed_value = want_bytes(signed_value)
  136. sep = want_bytes(self.sep)
  137. if sep not in signed_value:
  138. raise BadSignature("No %r found in value" % self.sep)
  139. value, sig = signed_value.rsplit(sep, 1)
  140. if self.verify_signature(value, sig):
  141. return value
  142. raise BadSignature("Signature %r does not match" % sig, payload=value)
  143. def validate(self, signed_value):
  144. """Only validates the given signed value. Returns ``True`` if
  145. the signature exists and is valid.
  146. """
  147. try:
  148. self.unsign(signed_value)
  149. return True
  150. except BadSignature:
  151. return False