blob: 28d9c9280925f28b2ec4a117009f584858022eff [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 Gaynorc37feed2014-03-08 08:32:56 -080014from __future__ import absolute_import, division, print_function
15
Alex Gaynor02fad002013-10-30 14:16:13 -070016import base64
Alex Gaynor69ab59e2013-10-31 15:26:43 -070017import binascii
Alex Gaynor02fad002013-10-30 14:16:13 -070018import os
19import struct
20import time
21
Alex Gaynorbbeba712013-10-30 14:29:58 -070022import six
23
Alex Gaynorb9bc6c32013-12-27 08:23:14 -080024from cryptography.exceptions import InvalidSignature
Alex Gaynorf272c142013-12-15 22:18:49 -080025from cryptography.hazmat.backends import default_backend
Alex Gaynorb9bc6c32013-12-27 08:23:14 -080026from cryptography.hazmat.primitives import padding, hashes
Alex Gaynordcc3f662013-11-07 14:28:16 -080027from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
Alex Gaynore2d45452014-02-12 09:58:41 -080028from cryptography.hazmat.primitives.hmac import HMAC
Alex Gaynor02fad002013-10-30 14:16:13 -070029
30
Alex Gaynor38f34552013-10-31 14:50:00 -070031class InvalidToken(Exception):
32 pass
33
34
Alex Gaynor34176562013-11-23 07:47:23 -080035_MAX_CLOCK_SKEW = 60
36
Alex Gaynor2c58bbe2013-10-31 16:31:38 -070037
Alex Gaynor02fad002013-10-30 14:16:13 -070038class Fernet(object):
Alex Gaynorfae20712013-12-16 15:29:30 -080039 def __init__(self, key, backend=None):
40 if backend is None:
41 backend = default_backend()
42
Alex Gaynor898fe0f2013-11-20 16:38:32 -080043 key = base64.urlsafe_b64decode(key)
Alex Gaynora8f0b632013-12-16 15:44:06 -080044 if len(key) != 32:
45 raise ValueError(
46 "Fernet key must be 32 url-safe base64-encoded bytes"
47 )
Alex Gaynorfae20712013-12-16 15:29:30 -080048
49 self._signing_key = key[:16]
50 self._encryption_key = key[16:]
51 self._backend = backend
Alex Gaynor02fad002013-10-30 14:16:13 -070052
Alex Gaynor36597b42013-11-22 10:25:13 -080053 @classmethod
54 def generate_key(cls):
55 return base64.urlsafe_b64encode(os.urandom(32))
56
Alex Gaynor02fad002013-10-30 14:16:13 -070057 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):
Alex Gaynor38f34552013-10-31 14:50:00 -070063 if isinstance(data, six.text_type):
Alex Gaynorc1ea0a02013-10-31 15:03:53 -070064 raise TypeError(
65 "Unicode-objects must be encoded before encryption"
66 )
Alex Gaynor38f34552013-10-31 14:50:00 -070067
Alex Gaynordcc3f662013-11-07 14:28:16 -080068 padder = padding.PKCS7(algorithms.AES.block_size).padder()
Alex Gaynor02fad002013-10-30 14:16:13 -070069 padded_data = padder.update(data) + padder.finalize()
Alex Gaynordcc3f662013-11-07 14:28:16 -080070 encryptor = Cipher(
Alex Gaynorfae20712013-12-16 15:29:30 -080071 algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
Alex Gaynorde36e902013-10-31 10:10:44 -070072 ).encryptor()
Alex Gaynor02fad002013-10-30 14:16:13 -070073 ciphertext = encryptor.update(padded_data) + encryptor.finalize()
74
Alex Gaynor1d2901c2013-11-22 10:12:05 -080075 basic_parts = (
76 b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext
Alex Gaynor02fad002013-10-30 14:16:13 -070077 )
Alex Gaynorbbeba712013-10-30 14:29:58 -070078
Alex Gaynorfae20712013-12-16 15:29:30 -080079 h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
Alex Gaynor1d2901c2013-11-22 10:12:05 -080080 h.update(basic_parts)
81 hmac = h.finalize()
82 return base64.urlsafe_b64encode(basic_parts + hmac)
83
Alex Gaynor0d089632013-12-17 20:23:43 -080084 def decrypt(self, token, ttl=None):
85 if isinstance(token, six.text_type):
Alex Gaynorc1ea0a02013-10-31 15:03:53 -070086 raise TypeError(
87 "Unicode-objects must be encoded before decryption"
88 )
Alex Gaynor38f34552013-10-31 14:50:00 -070089
Alex Gaynor1d2901c2013-11-22 10:12:05 -080090 current_time = int(time.time())
Alex Gaynor38f34552013-10-31 14:50:00 -070091
92 try:
Alex Gaynor0d089632013-12-17 20:23:43 -080093 data = base64.urlsafe_b64decode(token)
Alex Gaynor69ab59e2013-10-31 15:26:43 -070094 except (TypeError, binascii.Error):
Alex Gaynor38f34552013-10-31 14:50:00 -070095 raise InvalidToken
96
Alex Gaynora8f0b632013-12-16 15:44:06 -080097 if six.indexbytes(data, 0) != 0x80:
98 raise InvalidToken
99
Alex Gaynore78960f2013-12-20 11:02:33 -0800100 try:
101 timestamp, = struct.unpack(">Q", data[1:9])
102 except struct.error:
103 raise InvalidToken
Alex Gaynorbbeba712013-10-30 14:29:58 -0700104 if ttl is not None:
Alex Gaynorc3f7c372013-12-14 08:58:35 -0800105 if timestamp + ttl < current_time:
Alex Gaynor38f34552013-10-31 14:50:00 -0700106 raise InvalidToken
Alex Gaynorbadee1b2013-12-14 08:43:51 -0800107 if current_time + _MAX_CLOCK_SKEW < timestamp:
Alex Gaynor34176562013-11-23 07:47:23 -0800108 raise InvalidToken
Alex Gaynorfae20712013-12-16 15:29:30 -0800109 h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
Alex Gaynorbbeba712013-10-30 14:29:58 -0700110 h.update(data[:-32])
Alex Gaynorb9bc6c32013-12-27 08:23:14 -0800111 try:
112 h.verify(data[-32:])
113 except InvalidSignature:
Alex Gaynor38f34552013-10-31 14:50:00 -0700114 raise InvalidToken
115
Alex Gaynord66f3722013-12-20 11:05:13 -0800116 iv = data[9:25]
117 ciphertext = data[25:-32]
Alex Gaynordcc3f662013-11-07 14:28:16 -0800118 decryptor = Cipher(
Alex Gaynorfae20712013-12-16 15:29:30 -0800119 algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
Alex Gaynorde36e902013-10-31 10:10:44 -0700120 ).decryptor()
Alex Gaynor09bff862013-11-22 17:27:08 -0800121 plaintext_padded = decryptor.update(ciphertext)
122 try:
123 plaintext_padded += decryptor.finalize()
124 except ValueError:
125 raise InvalidToken
Alex Gaynordcc3f662013-11-07 14:28:16 -0800126 unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
Alex Gaynor38f34552013-10-31 14:50:00 -0700127
128 unpadded = unpadder.update(plaintext_padded)
129 try:
130 unpadded += unpadder.finalize()
131 except ValueError:
132 raise InvalidToken
133 return unpadded