prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Key versioning/key rotation support (wishlist)
  It would be nice if Fernet had built-in support for key versioning.  I don't know exactly what the API would look like, but the idea would be to allow encrypting new data under a new key while still retaining the ability to decrypt old data that was encrypted under older keys.
  
  If Fernet supported this natively, then I could tell developers to "just use Fernet" when they first write their applications, and later when they get a real security engineer, that person could start doing key rotation without having to change application-level code or data structures.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `cryptography/fernet.py`
  Content:
  ```
  1 # Licensed under the Apache License, Version 2.0 (the "License");
  2 # you may not use this file except in compliance with the License.
  3 # You may obtain a copy of the License at
  4 #
  5 #    http://www.apache.org/licenses/LICENSE-2.0
  6 #
  7 # Unless required by applicable law or agreed to in writing, software
  8 # distributed under the License is distributed on an "AS IS" BASIS,
  9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  10 # implied.
  11 # See the License for the specific language governing permissions and
  12 # limitations under the License.
  13 
  14 from __future__ import absolute_import, division, print_function
  15 
  16 import base64
  17 import binascii
  18 import os
  19 import struct
  20 import time
  21 
  22 import six
  23 
  24 from cryptography.exceptions import InvalidSignature
  25 from cryptography.hazmat.backends import default_backend
  26 from cryptography.hazmat.primitives import hashes, padding
  27 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
  28 from cryptography.hazmat.primitives.hmac import HMAC
  29 
  30 
  31 class InvalidToken(Exception):
  32     pass
  33 
  34 
  35 _MAX_CLOCK_SKEW = 60
  36 
  37 
  38 class Fernet(object):
  39     def __init__(self, key, backend=None):
  40         if backend is None:
  41             backend = default_backend()
  42 
  43         key = base64.urlsafe_b64decode(key)
  44         if len(key) != 32:
  45             raise ValueError(
  46                 "Fernet key must be 32 url-safe base64-encoded bytes."
  47             )
  48 
  49         self._signing_key = key[:16]
  50         self._encryption_key = key[16:]
  51         self._backend = backend
  52 
  53     @classmethod
  54     def generate_key(cls):
  55         return base64.urlsafe_b64encode(os.urandom(32))
  56 
  57     def encrypt(self, data):
  58         current_time = int(time.time())
  59         iv = os.urandom(16)
  60         return self._encrypt_from_parts(data, current_time, iv)
  61 
  62     def _encrypt_from_parts(self, data, current_time, iv):
  63         if not isinstance(data, bytes):
  64             raise TypeError("data must be bytes.")
  65 
  66         padder = padding.PKCS7(algorithms.AES.block_size).padder()
  67         padded_data = padder.update(data) + padder.finalize()
  68         encryptor = Cipher(
  69             algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
  70         ).encryptor()
  71         ciphertext = encryptor.update(padded_data) + encryptor.finalize()
  72 
  73         basic_parts = (
  74             b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext
  75         )
  76 
  77         h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
  78         h.update(basic_parts)
  79         hmac = h.finalize()
  80         return base64.urlsafe_b64encode(basic_parts + hmac)
  81 
  82     def decrypt(self, token, ttl=None):
  83         if not isinstance(token, bytes):
  84             raise TypeError("token must be bytes.")
  85 
  86         current_time = int(time.time())
  87 
  88         try:
  89             data = base64.urlsafe_b64decode(token)
  90         except (TypeError, binascii.Error):
  91             raise InvalidToken
  92 
  93         if not data or six.indexbytes(data, 0) != 0x80:
  94             raise InvalidToken
  95 
  96         try:
  97             timestamp, = struct.unpack(">Q", data[1:9])
  98         except struct.error:
  99             raise InvalidToken
  100         if ttl is not None:
  101             if timestamp + ttl < current_time:
  102                 raise InvalidToken
  103         if current_time + _MAX_CLOCK_SKEW < timestamp:
  104             raise InvalidToken
  105         h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
  106         h.update(data[:-32])
  107         try:
  108             h.verify(data[-32:])
  109         except InvalidSignature:
  110             raise InvalidToken
  111 
  112         iv = data[9:25]
  113         ciphertext = data[25:-32]
  114         decryptor = Cipher(
  115             algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
  116         ).decryptor()
  117         plaintext_padded = decryptor.update(ciphertext)
  118         try:
  119             plaintext_padded += decryptor.finalize()
  120         except ValueError:
  121             raise InvalidToken
  122         unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
  123 
  124         unpadded = unpadder.update(plaintext_padded)
  125         try:
  126             unpadded += unpadder.finalize()
  127         except ValueError:
  128             raise InvalidToken
  129         return unpadded
  130
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/cryptography/fernet.py b/cryptography/fernet.py
  --- a/cryptography/fernet.py
  +++ b/cryptography/fernet.py
  @@ -127,3 +127,24 @@
           except ValueError:
               raise InvalidToken
           return unpadded
  +
  +
  +class MultiFernet(object):
  +    def __init__(self, fernets):
  +        fernets = list(fernets)
  +        if not fernets:
  +            raise ValueError(
  +                "MultiFernet requires at least one Fernet instance"
  +            )
  +        self._fernets = fernets
  +
  +    def encrypt(self, msg):
  +        return self._fernets[0].encrypt(msg)
  +
  +    def decrypt(self, msg, ttl=None):
  +        for f in self._fernets:
  +            try:
  +                return f.decrypt(msg, ttl)
  +            except InvalidToken:
  +                pass
  +        raise InvalidToken
  
verification_info: {"golden_diff": "diff --git a/cryptography/fernet.py b/cryptography/fernet.py\n--- a/cryptography/fernet.py\n+++ b/cryptography/fernet.py\n@@ -127,3 +127,24 @@\n         except ValueError:\n             raise InvalidToken\n         return unpadded\n+\n+\n+class MultiFernet(object):\n+    def __init__(self, fernets):\n+        fernets = list(fernets)\n+        if not fernets:\n+            raise ValueError(\n+                \"MultiFernet requires at least one Fernet instance\"\n+            )\n+        self._fernets = fernets\n+\n+    def encrypt(self, msg):\n+        return self._fernets[0].encrypt(msg)\n+\n+    def decrypt(self, msg, ttl=None):\n+        for f in self._fernets:\n+            try:\n+                return f.decrypt(msg, ttl)\n+            except InvalidToken:\n+                pass\n+        raise InvalidToken\n", "issue": "Key versioning/key rotation support (wishlist)\nIt would be nice if Fernet had built-in support for key versioning.  I don't know exactly what the API would look like, but the idea would be to allow encrypting new data under a new key while still retaining the ability to decrypt old data that was encrypted under older keys.\n\nIf Fernet supported this natively, then I could tell developers to \"just use Fernet\" when they first write their applications, and later when they get a real security engineer, that person could start doing key rotation without having to change application-level code or data structures.\n\n", "before_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom __future__ import absolute_import, division, print_function\n\nimport base64\nimport binascii\nimport os\nimport struct\nimport time\n\nimport six\n\nfrom cryptography.exceptions import InvalidSignature\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import hashes, padding\nfrom cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes\nfrom cryptography.hazmat.primitives.hmac import HMAC\n\n\nclass InvalidToken(Exception):\n    pass\n\n\n_MAX_CLOCK_SKEW = 60\n\n\nclass Fernet(object):\n    def __init__(self, key, backend=None):\n        if backend is None:\n            backend = default_backend()\n\n        key = base64.urlsafe_b64decode(key)\n        if len(key) != 32:\n            raise ValueError(\n                \"Fernet key must be 32 url-safe base64-encoded bytes.\"\n            )\n\n        self._signing_key = key[:16]\n        self._encryption_key = key[16:]\n        self._backend = backend\n\n    @classmethod\n    def generate_key(cls):\n        return base64.urlsafe_b64encode(os.urandom(32))\n\n    def encrypt(self, data):\n        current_time = int(time.time())\n        iv = os.urandom(16)\n        return self._encrypt_from_parts(data, current_time, iv)\n\n    def _encrypt_from_parts(self, data, current_time, iv):\n        if not isinstance(data, bytes):\n            raise TypeError(\"data must be bytes.\")\n\n        padder = padding.PKCS7(algorithms.AES.block_size).padder()\n        padded_data = padder.update(data) + padder.finalize()\n        encryptor = Cipher(\n            algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend\n        ).encryptor()\n        ciphertext = encryptor.update(padded_data) + encryptor.finalize()\n\n        basic_parts = (\n            b\"\\x80\" + struct.pack(\">Q\", current_time) + iv + ciphertext\n        )\n\n        h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)\n        h.update(basic_parts)\n        hmac = h.finalize()\n        return base64.urlsafe_b64encode(basic_parts + hmac)\n\n    def decrypt(self, token, ttl=None):\n        if not isinstance(token, bytes):\n            raise TypeError(\"token must be bytes.\")\n\n        current_time = int(time.time())\n\n        try:\n            data = base64.urlsafe_b64decode(token)\n        except (TypeError, binascii.Error):\n            raise InvalidToken\n\n        if not data or six.indexbytes(data, 0) != 0x80:\n            raise InvalidToken\n\n        try:\n            timestamp, = struct.unpack(\">Q\", data[1:9])\n        except struct.error:\n            raise InvalidToken\n        if ttl is not None:\n            if timestamp + ttl < current_time:\n                raise InvalidToken\n        if current_time + _MAX_CLOCK_SKEW < timestamp:\n            raise InvalidToken\n        h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)\n        h.update(data[:-32])\n        try:\n            h.verify(data[-32:])\n        except InvalidSignature:\n            raise InvalidToken\n\n        iv = data[9:25]\n        ciphertext = data[25:-32]\n        decryptor = Cipher(\n            algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend\n        ).decryptor()\n        plaintext_padded = decryptor.update(ciphertext)\n        try:\n            plaintext_padded += decryptor.finalize()\n        except ValueError:\n            raise InvalidToken\n        unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()\n\n        unpadded = unpadder.update(plaintext_padded)\n        try:\n            unpadded += unpadder.finalize()\n        except ValueError:\n            raise InvalidToken\n        return unpadded\n", "path": "cryptography/fernet.py"}], "after_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom __future__ import absolute_import, division, print_function\n\nimport base64\nimport binascii\nimport os\nimport struct\nimport time\n\nimport six\n\nfrom cryptography.exceptions import InvalidSignature\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import hashes, padding\nfrom cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes\nfrom cryptography.hazmat.primitives.hmac import HMAC\n\n\nclass InvalidToken(Exception):\n    pass\n\n\n_MAX_CLOCK_SKEW = 60\n\n\nclass Fernet(object):\n    def __init__(self, key, backend=None):\n        if backend is None:\n            backend = default_backend()\n\n        key = base64.urlsafe_b64decode(key)\n        if len(key) != 32:\n            raise ValueError(\n                \"Fernet key must be 32 url-safe base64-encoded bytes.\"\n            )\n\n        self._signing_key = key[:16]\n        self._encryption_key = key[16:]\n        self._backend = backend\n\n    @classmethod\n    def generate_key(cls):\n        return base64.urlsafe_b64encode(os.urandom(32))\n\n    def encrypt(self, data):\n        current_time = int(time.time())\n        iv = os.urandom(16)\n        return self._encrypt_from_parts(data, current_time, iv)\n\n    def _encrypt_from_parts(self, data, current_time, iv):\n        if not isinstance(data, bytes):\n            raise TypeError(\"data must be bytes.\")\n\n        padder = padding.PKCS7(algorithms.AES.block_size).padder()\n        padded_data = padder.update(data) + padder.finalize()\n        encryptor = Cipher(\n            algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend\n        ).encryptor()\n        ciphertext = encryptor.update(padded_data) + encryptor.finalize()\n\n        basic_parts = (\n            b\"\\x80\" + struct.pack(\">Q\", current_time) + iv + ciphertext\n        )\n\n        h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)\n        h.update(basic_parts)\n        hmac = h.finalize()\n        return base64.urlsafe_b64encode(basic_parts + hmac)\n\n    def decrypt(self, token, ttl=None):\n        if not isinstance(token, bytes):\n            raise TypeError(\"token must be bytes.\")\n\n        current_time = int(time.time())\n\n        try:\n            data = base64.urlsafe_b64decode(token)\n        except (TypeError, binascii.Error):\n            raise InvalidToken\n\n        if not data or six.indexbytes(data, 0) != 0x80:\n            raise InvalidToken\n\n        try:\n            timestamp, = struct.unpack(\">Q\", data[1:9])\n        except struct.error:\n            raise InvalidToken\n        if ttl is not None:\n            if timestamp + ttl < current_time:\n                raise InvalidToken\n        if current_time + _MAX_CLOCK_SKEW < timestamp:\n            raise InvalidToken\n        h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)\n        h.update(data[:-32])\n        try:\n            h.verify(data[-32:])\n        except InvalidSignature:\n            raise InvalidToken\n\n        iv = data[9:25]\n        ciphertext = data[25:-32]\n        decryptor = Cipher(\n            algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend\n        ).decryptor()\n        plaintext_padded = decryptor.update(ciphertext)\n        try:\n            plaintext_padded += decryptor.finalize()\n        except ValueError:\n            raise InvalidToken\n        unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()\n\n        unpadded = unpadder.update(plaintext_padded)\n        try:\n            unpadded += unpadder.finalize()\n        except ValueError:\n            raise InvalidToken\n        return unpadded\n\n\nclass MultiFernet(object):\n    def __init__(self, fernets):\n        fernets = list(fernets)\n        if not fernets:\n            raise ValueError(\n                \"MultiFernet requires at least one Fernet instance\"\n            )\n        self._fernets = fernets\n\n    def encrypt(self, msg):\n        return self._fernets[0].encrypt(msg)\n\n    def decrypt(self, msg, ttl=None):\n        for f in self._fernets:\n            try:\n                return f.decrypt(msg, ttl)\n            except InvalidToken:\n                pass\n        raise InvalidToken\n", "path": "cryptography/fernet.py"}]}