import base64
import binascii
import os
import struct
import time

import six

from cryptography.hazmat.primitives import padding, hashes
from cryptography.hazmat.primitives.hmac import HMAC
from cryptography.hazmat.primitives.block import BlockCipher, ciphers, modes


class InvalidToken(Exception):
    pass


class Fernet(object):
    def __init__(self, key):
        super(Fernet, self).__init__()
        assert len(key) == 32
        self.signing_key = key[:16]
        self.encryption_key = key[16:]

    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(ciphers.AES.block_size).padder()
        padded_data = padder.update(data) + padder.finalize()
        encryptor = BlockCipher(
            ciphers.AES(self.encryption_key), modes.CBC(iv)
        ).encryptor()
        ciphertext = encryptor.update(padded_data) + encryptor.finalize()

        h = HMAC(self.signing_key, digestmod=hashes.SHA256)
        h.update(b"\x80")
        h.update(struct.pack(">Q", current_time))
        h.update(iv)
        h.update(ciphertext)
        hmac = h.digest()
        return base64.urlsafe_b64encode(
            b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext + hmac
        )

    def decrypt(self, data, ttl=None, current_time=None):
        if isinstance(data, six.text_type):
            raise TypeError(
                "Unicode-objects must be encoded before decryption"
            )

        if current_time is None:
            current_time = int(time.time())

        try:
            data = base64.urlsafe_b64decode(data)
        except (TypeError, binascii.Error):
            raise InvalidToken

        assert six.indexbytes(data, 0) == 0x80
        timestamp = data[1:9]
        iv = data[9:25]
        ciphertext = data[25:-32]
        if ttl is not None:
            if struct.unpack(">Q", timestamp)[0] + ttl < current_time:
                raise InvalidToken
        h = HMAC(self.signing_key, digestmod=hashes.SHA256)
        h.update(data[:-32])
        hmac = h.digest()

        if not constant_time_compare(hmac, data[-32:]):
            raise InvalidToken

        decryptor = BlockCipher(
            ciphers.AES(self.encryption_key), modes.CBC(iv)
        ).decryptor()
        plaintext_padded = decryptor.update(ciphertext) + decryptor.finalize()
        unpadder = padding.PKCS7(ciphers.AES.block_size).unpadder()

        unpadded = unpadder.update(plaintext_padded)
        try:
            unpadded += unpadder.finalize()
        except ValueError:
            raise InvalidToken
        return unpadded


def constant_time_compare(a, b):
    # TOOD: replace with a cffi function
    assert isinstance(a, bytes) and isinstance(b, bytes)
    if len(a) != len(b):
        return False
    result = 0
    for i in range(len(a)):
        result |= six.indexbytes(a, i) ^ six.indexbytes(b, i)
    return result == 0

@@ -60,16 +60,29 @@
 t time%0A%0A
+import cffi%0A%0A
 import s
@@ -314,24 +314,433 @@
 %0A    pass%0A%0A%0A
+ffi = cffi.FFI()%0Affi.cdef(%22%22%22%0Abool constant_time_compare(uint8_t *, size_t, uint8_t *, size_t);%0A%22%22%22)%0Alib = ffi.verify(%22%22%22%0A#include %3Cstdbool.h%3E%0A%0Abool constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b, size_t len_b) %7B%0A    if (len_a != len_b) %7B%0A        return false;%0A    %7D%0A    int result = 0;%0A    for (size_t i = 0; i %3C len_a; i++) %7B%0A        result %7C= a%5Bi%5D %5E b%5Bi%5D;%0A    %7D%0A    return result == 0;%0A%7D%0A%22%22%22)%0A%0A
 class Fernet
@@ -2790,16 +2790,20 @@
  if not 
+lib.
 constant
@@ -2821,16 +2821,27 @@
 re(hmac,
+ len(hmac),
  data%5B-3
@@ -2843,16 +2843,20 @@
 ta%5B-32:%5D
+, 32
 ):%0A     
@@ -3341,311 +3341,4 @@
 ded%0A
-%0A%0Adef constant_time_compare(a, b):%0A    # TOOD: replace with a cffi function%0A    assert isinstance(a, bytes) and isinstance(b, bytes)%0A    if len(a) != len(b):%0A        return False%0A    result = 0%0A    for i in range(len(a)):%0A        result %7C= six.indexbytes(a, i) %5E six.indexbytes(b, i)%0A    return result == 0%0A

Moved constant_time_compare to be C