aboutsummaryrefslogtreecommitdiffstats
path: root/cryptography
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2014-01-07 12:26:33 -0800
committerAlex Gaynor <alex.gaynor@gmail.com>2014-01-07 12:26:33 -0800
commitd7e04ae2f45c99034d564fd6a7747bf216d00ea9 (patch)
tree3aa52344b3c0a9c4f7457a281acba849a31655dc /cryptography
parentffa8b4d5fedad8f9c1fdbd05a2da8fb2679168af (diff)
parent168c29d6d74060b0d9f592b740f8913cc5d07c5e (diff)
downloadcryptography-d7e04ae2f45c99034d564fd6a7747bf216d00ea9.tar.gz
cryptography-d7e04ae2f45c99034d564fd6a7747bf216d00ea9.tar.bz2
cryptography-d7e04ae2f45c99034d564fd6a7747bf216d00ea9.zip
Merge branch 'master' into setup-install-extension
Diffstat (limited to 'cryptography')
-rw-r--r--cryptography/fernet.py131
-rw-r--r--cryptography/hazmat/bindings/openssl/binding.py8
-rw-r--r--cryptography/hazmat/bindings/utils.py8
3 files changed, 146 insertions, 1 deletions
diff --git a/cryptography/fernet.py b/cryptography/fernet.py
new file mode 100644
index 00000000..c19309d5
--- /dev/null
+++ b/cryptography/fernet.py
@@ -0,0 +1,131 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import base64
+import binascii
+import os
+import struct
+import time
+
+import six
+
+from cryptography.exceptions import InvalidSignature
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import padding, hashes
+from cryptography.hazmat.primitives.hmac import HMAC
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+
+
+class InvalidToken(Exception):
+ pass
+
+
+_MAX_CLOCK_SKEW = 60
+
+
+class Fernet(object):
+ def __init__(self, key, backend=None):
+ if backend is None:
+ backend = default_backend()
+
+ key = base64.urlsafe_b64decode(key)
+ if len(key) != 32:
+ raise ValueError(
+ "Fernet key must be 32 url-safe base64-encoded bytes"
+ )
+
+ self._signing_key = key[:16]
+ self._encryption_key = key[16:]
+ self._backend = backend
+
+ @classmethod
+ def generate_key(cls):
+ return base64.urlsafe_b64encode(os.urandom(32))
+
+ def encrypt(self, data):
+ current_time = int(time.time())
+ iv = os.urandom(16)
+ return self._encrypt_from_parts(data, current_time, iv)
+
+ def _encrypt_from_parts(self, data, current_time, iv):
+ if isinstance(data, six.text_type):
+ raise TypeError(
+ "Unicode-objects must be encoded before encryption"
+ )
+
+ padder = padding.PKCS7(algorithms.AES.block_size).padder()
+ padded_data = padder.update(data) + padder.finalize()
+ encryptor = Cipher(
+ algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
+ ).encryptor()
+ ciphertext = encryptor.update(padded_data) + encryptor.finalize()
+
+ basic_parts = (
+ b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext
+ )
+
+ h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
+ h.update(basic_parts)
+ hmac = h.finalize()
+ return base64.urlsafe_b64encode(basic_parts + hmac)
+
+ def decrypt(self, token, ttl=None):
+ if isinstance(token, six.text_type):
+ raise TypeError(
+ "Unicode-objects must be encoded before decryption"
+ )
+
+ current_time = int(time.time())
+
+ try:
+ data = base64.urlsafe_b64decode(token)
+ except (TypeError, binascii.Error):
+ raise InvalidToken
+
+ if six.indexbytes(data, 0) != 0x80:
+ raise InvalidToken
+
+ try:
+ timestamp, = struct.unpack(">Q", data[1:9])
+ except struct.error:
+ raise InvalidToken
+ if ttl is not None:
+ if timestamp + ttl < current_time:
+ raise InvalidToken
+ if current_time + _MAX_CLOCK_SKEW < timestamp:
+ raise InvalidToken
+ h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
+ h.update(data[:-32])
+ try:
+ h.verify(data[-32:])
+ except InvalidSignature:
+ raise InvalidToken
+
+ iv = data[9:25]
+ ciphertext = data[25:-32]
+ decryptor = Cipher(
+ algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
+ ).decryptor()
+ plaintext_padded = decryptor.update(ciphertext)
+ try:
+ plaintext_padded += decryptor.finalize()
+ except ValueError:
+ raise InvalidToken
+ unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
+
+ unpadded = unpadder.update(plaintext_padded)
+ try:
+ unpadded += unpadder.finalize()
+ except ValueError:
+ raise InvalidToken
+ return unpadded
diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py
index 4f6b99ae..2a1e1184 100644
--- a/cryptography/hazmat/bindings/openssl/binding.py
+++ b/cryptography/hazmat/bindings/openssl/binding.py
@@ -13,7 +13,9 @@
from __future__ import absolute_import, division, print_function
-from cryptography.hazmat.bindings.utils import build_ffi
+from cryptography.hazmat.bindings.utils import (
+ build_ffi, binding_available
+)
_OSX_PRE_INCLUDE = """
#ifdef __APPLE__
@@ -79,3 +81,7 @@ class Binding(object):
cls.ffi, cls.lib = build_ffi(cls._module_prefix, cls._modules,
_OSX_PRE_INCLUDE, _OSX_POST_INCLUDE,
["crypto", "ssl"])
+
+ @classmethod
+ def is_available(cls):
+ return binding_available(cls._ensure_ffi_initialized)
diff --git a/cryptography/hazmat/bindings/utils.py b/cryptography/hazmat/bindings/utils.py
index 69290eb3..40fd07f8 100644
--- a/cryptography/hazmat/bindings/utils.py
+++ b/cryptography/hazmat/bindings/utils.py
@@ -87,3 +87,11 @@ def build_ffi(module_prefix, modules, pre_include, post_include, libraries):
delattr(lib, name)
return ffi, lib
+
+
+def binding_available(initializer):
+ try:
+ initializer()
+ return True
+ except cffi.VerificationError:
+ return False