blob: 8bcaa40ad1db6f9b007a6a0daac452a2c5a39fad [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 Gaynorf272c142013-12-15 22:18:49 -080022from cryptography.hazmat.backends import default_backend
Alex Gaynor022bc3a2013-12-14 08:35:36 -080023from cryptography.hazmat.primitives import padding, hashes, constant_time
Alex Gaynor02fad002013-10-30 14:16:13 -070024from cryptography.hazmat.primitives.hmac import HMAC
Alex Gaynordcc3f662013-11-07 14:28:16 -080025from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
Alex Gaynor02fad002013-10-30 14:16:13 -070026
27
Alex Gaynor38f34552013-10-31 14:50:00 -070028class InvalidToken(Exception):
29 pass
30
31
Alex Gaynor34176562013-11-23 07:47:23 -080032_MAX_CLOCK_SKEW = 60
33
Alex Gaynor2c58bbe2013-10-31 16:31:38 -070034
Alex Gaynor02fad002013-10-30 14:16:13 -070035class Fernet(object):
Alex Gaynorebf34282013-11-27 08:46:58 -060036 def __init__(self, key):
Alex Gaynor898fe0f2013-11-20 16:38:32 -080037 key = base64.urlsafe_b64decode(key)
Alex Gaynorcd47c4a2013-10-31 09:46:27 -070038 assert len(key) == 32
Alex Gaynor02fad002013-10-30 14:16:13 -070039 self.signing_key = key[:16]
40 self.encryption_key = key[16:]
Alex Gaynorebf34282013-11-27 08:46:58 -060041 self.backend = default_backend()
Alex Gaynor02fad002013-10-30 14:16:13 -070042
Alex Gaynor36597b42013-11-22 10:25:13 -080043 @classmethod
44 def generate_key(cls):
45 return base64.urlsafe_b64encode(os.urandom(32))
46
Alex Gaynor02fad002013-10-30 14:16:13 -070047 def encrypt(self, data):
48 current_time = int(time.time())
49 iv = os.urandom(16)
50 return self._encrypt_from_parts(data, current_time, iv)
51
52 def _encrypt_from_parts(self, data, current_time, iv):
Alex Gaynor38f34552013-10-31 14:50:00 -070053 if isinstance(data, six.text_type):
Alex Gaynorc1ea0a02013-10-31 15:03:53 -070054 raise TypeError(
55 "Unicode-objects must be encoded before encryption"
56 )
Alex Gaynor38f34552013-10-31 14:50:00 -070057
Alex Gaynordcc3f662013-11-07 14:28:16 -080058 padder = padding.PKCS7(algorithms.AES.block_size).padder()
Alex Gaynor02fad002013-10-30 14:16:13 -070059 padded_data = padder.update(data) + padder.finalize()
Alex Gaynordcc3f662013-11-07 14:28:16 -080060 encryptor = Cipher(
61 algorithms.AES(self.encryption_key), modes.CBC(iv), self.backend
Alex Gaynorde36e902013-10-31 10:10:44 -070062 ).encryptor()
Alex Gaynor02fad002013-10-30 14:16:13 -070063 ciphertext = encryptor.update(padded_data) + encryptor.finalize()
64
Alex Gaynor1d2901c2013-11-22 10:12:05 -080065 basic_parts = (
66 b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext
Alex Gaynor02fad002013-10-30 14:16:13 -070067 )
Alex Gaynorbbeba712013-10-30 14:29:58 -070068
Alex Gaynor1d2901c2013-11-22 10:12:05 -080069 h = HMAC(self.signing_key, hashes.SHA256(), self.backend)
70 h.update(basic_parts)
71 hmac = h.finalize()
72 return base64.urlsafe_b64encode(basic_parts + hmac)
73
74 def decrypt(self, data, ttl=None):
Alex Gaynor38f34552013-10-31 14:50:00 -070075 if isinstance(data, six.text_type):
Alex Gaynorc1ea0a02013-10-31 15:03:53 -070076 raise TypeError(
77 "Unicode-objects must be encoded before decryption"
78 )
Alex Gaynor38f34552013-10-31 14:50:00 -070079
Alex Gaynor1d2901c2013-11-22 10:12:05 -080080 current_time = int(time.time())
Alex Gaynor38f34552013-10-31 14:50:00 -070081
82 try:
83 data = base64.urlsafe_b64decode(data)
Alex Gaynor69ab59e2013-10-31 15:26:43 -070084 except (TypeError, binascii.Error):
Alex Gaynor38f34552013-10-31 14:50:00 -070085 raise InvalidToken
86
Alex Gaynor5c5342e2013-10-31 11:25:54 -070087 assert six.indexbytes(data, 0) == 0x80
Alex Gaynorbadee1b2013-12-14 08:43:51 -080088 timestamp = struct.unpack(">Q", data[1:9])[0]
Alex Gaynorf5938482013-10-30 14:34:55 -070089 iv = data[9:25]
90 ciphertext = data[25:-32]
Alex Gaynorbbeba712013-10-30 14:29:58 -070091 if ttl is not None:
Alex Gaynorc3f7c372013-12-14 08:58:35 -080092 if timestamp + ttl < current_time:
Alex Gaynor38f34552013-10-31 14:50:00 -070093 raise InvalidToken
Alex Gaynorbadee1b2013-12-14 08:43:51 -080094 if current_time + _MAX_CLOCK_SKEW < timestamp:
Alex Gaynor34176562013-11-23 07:47:23 -080095 raise InvalidToken
Alex Gaynor105e8132013-11-07 14:25:42 -080096 h = HMAC(self.signing_key, hashes.SHA256(), self.backend)
Alex Gaynorbbeba712013-10-30 14:29:58 -070097 h.update(data[:-32])
Alex Gaynorfa7081a2013-11-01 16:38:25 -070098 hmac = h.finalize()
Alex Gaynor022bc3a2013-12-14 08:35:36 -080099 if not constant_time.bytes_eq(hmac, data[-32:]):
Alex Gaynor38f34552013-10-31 14:50:00 -0700100 raise InvalidToken
101
Alex Gaynordcc3f662013-11-07 14:28:16 -0800102 decryptor = Cipher(
103 algorithms.AES(self.encryption_key), modes.CBC(iv), self.backend
Alex Gaynorde36e902013-10-31 10:10:44 -0700104 ).decryptor()
Alex Gaynor09bff862013-11-22 17:27:08 -0800105 plaintext_padded = decryptor.update(ciphertext)
106 try:
107 plaintext_padded += decryptor.finalize()
108 except ValueError:
109 raise InvalidToken
Alex Gaynordcc3f662013-11-07 14:28:16 -0800110 unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
Alex Gaynor38f34552013-10-31 14:50:00 -0700111
112 unpadded = unpadder.update(plaintext_padded)
113 try:
114 unpadded += unpadder.finalize()
115 except ValueError:
116 raise InvalidToken
117 return unpadded