blob: cdb9bdca1801e3270e8a8d91fbc34b5e20b734f5 [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
Paul Kehrerafc1ccd2014-03-19 11:49:32 -040026from cryptography.hazmat.primitives import hashes, padding
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(
Ayrxe7d19962014-05-18 14:35:23 +080046 "Fernet key must be 32 url-safe base64-encoded bytes."
Alex Gaynora8f0b632013-12-16 15:44:06 -080047 )
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 Gaynor3b39ae42014-05-17 11:56:12 -070063 if not isinstance(data, bytes):
Ayrxe7d19962014-05-18 14:35:23 +080064 raise TypeError("data must be bytes.")
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):
Alex Gaynor3b39ae42014-05-17 11:56:12 -070083 if not isinstance(token, bytes):
Ayrxe7d19962014-05-18 14:35:23 +080084 raise TypeError("token must be bytes.")
Alex Gaynor38f34552013-10-31 14:50:00 -070085
Alex Gaynor1d2901c2013-11-22 10:12:05 -080086 current_time = int(time.time())
Alex Gaynor38f34552013-10-31 14:50:00 -070087
88 try:
Alex Gaynor0d089632013-12-17 20:23:43 -080089 data = base64.urlsafe_b64decode(token)
Alex Gaynor69ab59e2013-10-31 15:26:43 -070090 except (TypeError, binascii.Error):
Alex Gaynor38f34552013-10-31 14:50:00 -070091 raise InvalidToken
92
Alex Gaynora8f0b632013-12-16 15:44:06 -080093 if six.indexbytes(data, 0) != 0x80:
94 raise InvalidToken
95
Alex Gaynore78960f2013-12-20 11:02:33 -080096 try:
97 timestamp, = struct.unpack(">Q", data[1:9])
98 except struct.error:
99 raise InvalidToken
Alex Gaynorbbeba712013-10-30 14:29:58 -0700100 if ttl is not None:
Alex Gaynorc3f7c372013-12-14 08:58:35 -0800101 if timestamp + ttl < current_time:
Alex Gaynor38f34552013-10-31 14:50:00 -0700102 raise InvalidToken
Alex Gaynorbadee1b2013-12-14 08:43:51 -0800103 if current_time + _MAX_CLOCK_SKEW < timestamp:
Alex Gaynor34176562013-11-23 07:47:23 -0800104 raise InvalidToken
Alex Gaynorfae20712013-12-16 15:29:30 -0800105 h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
Alex Gaynorbbeba712013-10-30 14:29:58 -0700106 h.update(data[:-32])
Alex Gaynorb9bc6c32013-12-27 08:23:14 -0800107 try:
108 h.verify(data[-32:])
109 except InvalidSignature:
Alex Gaynor38f34552013-10-31 14:50:00 -0700110 raise InvalidToken
111
Alex Gaynord66f3722013-12-20 11:05:13 -0800112 iv = data[9:25]
113 ciphertext = data[25:-32]
Alex Gaynordcc3f662013-11-07 14:28:16 -0800114 decryptor = Cipher(
Alex Gaynorfae20712013-12-16 15:29:30 -0800115 algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
Alex Gaynorde36e902013-10-31 10:10:44 -0700116 ).decryptor()
Alex Gaynor09bff862013-11-22 17:27:08 -0800117 plaintext_padded = decryptor.update(ciphertext)
118 try:
119 plaintext_padded += decryptor.finalize()
120 except ValueError:
121 raise InvalidToken
Alex Gaynordcc3f662013-11-07 14:28:16 -0800122 unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
Alex Gaynor38f34552013-10-31 14:50:00 -0700123
124 unpadded = unpadder.update(plaintext_padded)
125 try:
126 unpadded += unpadder.finalize()
127 except ValueError:
128 raise InvalidToken
129 return unpadded