You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

143 lines
4.2 KiB

7 years ago
  1. # This file is dual licensed under the terms of the Apache License, Version
  2. # 2.0, and the BSD License. See the LICENSE file in the root of this repository
  3. # for complete details.
  4. from __future__ import absolute_import, division, print_function
  5. import base64
  6. import binascii
  7. import os
  8. import struct
  9. import time
  10. import six
  11. from cryptography.exceptions import InvalidSignature
  12. from cryptography.hazmat.backends import default_backend
  13. from cryptography.hazmat.primitives import hashes, padding
  14. from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
  15. from cryptography.hazmat.primitives.hmac import HMAC
  16. class InvalidToken(Exception):
  17. pass
  18. _MAX_CLOCK_SKEW = 60
  19. class Fernet(object):
  20. def __init__(self, key, backend=None):
  21. if backend is None:
  22. backend = default_backend()
  23. key = base64.urlsafe_b64decode(key)
  24. if len(key) != 32:
  25. raise ValueError(
  26. "Fernet key must be 32 url-safe base64-encoded bytes."
  27. )
  28. self._signing_key = key[:16]
  29. self._encryption_key = key[16:]
  30. self._backend = backend
  31. @classmethod
  32. def generate_key(cls):
  33. return base64.urlsafe_b64encode(os.urandom(32))
  34. def encrypt(self, data):
  35. current_time = int(time.time())
  36. iv = os.urandom(16)
  37. return self._encrypt_from_parts(data, current_time, iv)
  38. def _encrypt_from_parts(self, data, current_time, iv):
  39. if not isinstance(data, bytes):
  40. raise TypeError("data must be bytes.")
  41. padder = padding.PKCS7(algorithms.AES.block_size).padder()
  42. padded_data = padder.update(data) + padder.finalize()
  43. encryptor = Cipher(
  44. algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
  45. ).encryptor()
  46. ciphertext = encryptor.update(padded_data) + encryptor.finalize()
  47. basic_parts = (
  48. b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext
  49. )
  50. h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
  51. h.update(basic_parts)
  52. hmac = h.finalize()
  53. return base64.urlsafe_b64encode(basic_parts + hmac)
  54. def decrypt(self, token, ttl=None):
  55. if not isinstance(token, bytes):
  56. raise TypeError("token must be bytes.")
  57. current_time = int(time.time())
  58. try:
  59. data = base64.urlsafe_b64decode(token)
  60. except (TypeError, binascii.Error):
  61. raise InvalidToken
  62. if not data or six.indexbytes(data, 0) != 0x80:
  63. raise InvalidToken
  64. try:
  65. timestamp, = struct.unpack(">Q", data[1:9])
  66. except struct.error:
  67. raise InvalidToken
  68. if ttl is not None:
  69. if timestamp + ttl < current_time:
  70. raise InvalidToken
  71. if current_time + _MAX_CLOCK_SKEW < timestamp:
  72. raise InvalidToken
  73. h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
  74. h.update(data[:-32])
  75. try:
  76. h.verify(data[-32:])
  77. except InvalidSignature:
  78. raise InvalidToken
  79. iv = data[9:25]
  80. ciphertext = data[25:-32]
  81. decryptor = Cipher(
  82. algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
  83. ).decryptor()
  84. plaintext_padded = decryptor.update(ciphertext)
  85. try:
  86. plaintext_padded += decryptor.finalize()
  87. except ValueError:
  88. raise InvalidToken
  89. unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
  90. unpadded = unpadder.update(plaintext_padded)
  91. try:
  92. unpadded += unpadder.finalize()
  93. except ValueError:
  94. raise InvalidToken
  95. return unpadded
  96. class MultiFernet(object):
  97. def __init__(self, fernets):
  98. fernets = list(fernets)
  99. if not fernets:
  100. raise ValueError(
  101. "MultiFernet requires at least one Fernet instance"
  102. )
  103. self._fernets = fernets
  104. def encrypt(self, msg):
  105. return self._fernets[0].encrypt(msg)
  106. def decrypt(self, msg, ttl=None):
  107. for f in self._fernets:
  108. try:
  109. return f.decrypt(msg, ttl)
  110. except InvalidToken:
  111. pass
  112. raise InvalidToken

Powered by TurnKey Linux.