blob: c19309d55f2b3bca3a70ac3b69170b0648c9fa42 [file] [log] [blame]
Alex Gaynor8912d3a2013-11-02 14:04:19 -07001# 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
Alex Gaynor02fad002013-10-30 14:16:13 -070014import base64
Alex Gaynor69ab59e2013-10-31 15:26:43 -070015import binascii
Alex Gaynor02fad002013-10-30 14:16:13 -070016import os
17import struct
18import time
19
Alex Gaynorbbeba712013-10-30 14:29:58 -070020import six
21
Alex Gaynorb9bc6c32013-12-27 08:23:14 -080022from cryptography.exceptions import InvalidSignature
Alex Gaynorf272c142013-12-15 22:18:49 -080023from cryptography.hazmat.backends import default_backend
Alex Gaynorb9bc6c32013-12-27 08:23:14 -080024from cryptography.hazmat.primitives import padding, hashes
Alex Gaynor02fad002013-10-30 14:16:13 -070025from cryptography.hazmat.primitives.hmac import HMAC
Alex Gaynordcc3f662013-11-07 14:28:16 -080026from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
Alex Gaynor02fad002013-10-30 14:16:13 -070027
28
Alex Gaynor38f34552013-10-31 14:50:00 -070029class InvalidToken(Exception):
30 pass
31
32
Alex Gaynor34176562013-11-23 07:47:23 -080033_MAX_CLOCK_SKEW = 60
34
Alex Gaynor2c58bbe2013-10-31 16:31:38 -070035
Alex Gaynor02fad002013-10-30 14:16:13 -070036class Fernet(object):
Alex Gaynorfae20712013-12-16 15:29:30 -080037 def __init__(self, key, backend=None):
38 if backend is None:
39 backend = default_backend()
40
Alex Gaynor898fe0f2013-11-20 16:38:32 -080041 key = base64.urlsafe_b64decode(key)
Alex Gaynora8f0b632013-12-16 15:44:06 -080042 if len(key) != 32:
43 raise ValueError(
44 "Fernet key must be 32 url-safe base64-encoded bytes"
45 )
Alex Gaynorfae20712013-12-16 15:29:30 -080046
47 self._signing_key = key[:16]
48 self._encryption_key = key[16:]
49 self._backend = backend
Alex Gaynor02fad002013-10-30 14:16:13 -070050
Alex Gaynor36597b42013-11-22 10:25:13 -080051 @classmethod
52 def generate_key(cls):
53 return base64.urlsafe_b64encode(os.urandom(32))
54
Alex Gaynor02fad002013-10-30 14:16:13 -070055 def encrypt(self, data):
56 current_time = int(time.time())
57 iv = os.urandom(16)
58 return self._encrypt_from_parts(data, current_time, iv)
59
60 def _encrypt_from_parts(self, data, current_time, iv):
Alex Gaynor38f34552013-10-31 14:50:00 -070061 if isinstance(data, six.text_type):
Alex Gaynorc1ea0a02013-10-31 15:03:53 -070062 raise TypeError(
63 "Unicode-objects must be encoded before encryption"
64 )
Alex Gaynor38f34552013-10-31 14:50:00 -070065
Alex Gaynordcc3f662013-11-07 14:28:16 -080066 padder = padding.PKCS7(algorithms.AES.block_size).padder()
Alex Gaynor02fad002013-10-30 14:16:13 -070067 padded_data = padder.update(data) + padder.finalize()
Alex Gaynordcc3f662013-11-07 14:28:16 -080068 encryptor = Cipher(
Alex Gaynorfae20712013-12-16 15:29:30 -080069 algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
Alex Gaynorde36e902013-10-31 10:10:44 -070070 ).encryptor()
Alex Gaynor02fad002013-10-30 14:16:13 -070071 ciphertext = encryptor.update(padded_data) + encryptor.finalize()
72
Alex Gaynor1d2901c2013-11-22 10:12:05 -080073 basic_parts = (
74 b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext
Alex Gaynor02fad002013-10-30 14:16:13 -070075 )
Alex Gaynorbbeba712013-10-30 14:29:58 -070076
Alex Gaynorfae20712013-12-16 15:29:30 -080077 h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
Alex Gaynor1d2901c2013-11-22 10:12:05 -080078 h.update(basic_parts)
79 hmac = h.finalize()
80 return base64.urlsafe_b64encode(basic_parts + hmac)
81
Alex Gaynor0d089632013-12-17 20:23:43 -080082 def decrypt(self, token, ttl=None):
83 if isinstance(token, six.text_type):
Alex Gaynorc1ea0a02013-10-31 15:03:53 -070084 raise TypeError(
85 "Unicode-objects must be encoded before decryption"
86 )
Alex Gaynor38f34552013-10-31 14:50:00 -070087
Alex Gaynor1d2901c2013-11-22 10:12:05 -080088 current_time = int(time.time())
Alex Gaynor38f34552013-10-31 14:50:00 -070089
90 try:
Alex Gaynor0d089632013-12-17 20:23:43 -080091 data = base64.urlsafe_b64decode(token)
Alex Gaynor69ab59e2013-10-31 15:26:43 -070092 except (TypeError, binascii.Error):
Alex Gaynor38f34552013-10-31 14:50:00 -070093 raise InvalidToken
94
Alex Gaynora8f0b632013-12-16 15:44:06 -080095 if six.indexbytes(data, 0) != 0x80:
96 raise InvalidToken
97
Alex Gaynore78960f2013-12-20 11:02:33 -080098 try:
99 timestamp, = struct.unpack(">Q", data[1:9])
100 except struct.error:
101 raise InvalidToken
Alex Gaynorbbeba712013-10-30 14:29:58 -0700102 if ttl is not None:
Alex Gaynorc3f7c372013-12-14 08:58:35 -0800103 if timestamp + ttl < current_time:
Alex Gaynor38f34552013-10-31 14:50:00 -0700104 raise InvalidToken
Alex Gaynorbadee1b2013-12-14 08:43:51 -0800105 if current_time + _MAX_CLOCK_SKEW < timestamp:
Alex Gaynor34176562013-11-23 07:47:23 -0800106 raise InvalidToken
Alex Gaynorfae20712013-12-16 15:29:30 -0800107 h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
Alex Gaynorbbeba712013-10-30 14:29:58 -0700108 h.update(data[:-32])
Alex Gaynorb9bc6c32013-12-27 08:23:14 -0800109 try:
110 h.verify(data[-32:])
111 except InvalidSignature:
Alex Gaynor38f34552013-10-31 14:50:00 -0700112 raise InvalidToken
113
Alex Gaynord66f3722013-12-20 11:05:13 -0800114 iv = data[9:25]
115 ciphertext = data[25:-32]
Alex Gaynordcc3f662013-11-07 14:28:16 -0800116 decryptor = Cipher(
Alex Gaynorfae20712013-12-16 15:29:30 -0800117 algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
Alex Gaynorde36e902013-10-31 10:10:44 -0700118 ).decryptor()
Alex Gaynor09bff862013-11-22 17:27:08 -0800119 plaintext_padded = decryptor.update(ciphertext)
120 try:
121 plaintext_padded += decryptor.finalize()
122 except ValueError:
123 raise InvalidToken
Alex Gaynordcc3f662013-11-07 14:28:16 -0800124 unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
Alex Gaynor38f34552013-10-31 14:50:00 -0700125
126 unpadded = unpadder.update(plaintext_padded)
127 try:
128 unpadded += unpadder.finalize()
129 except ValueError:
130 raise InvalidToken
131 return unpadded