diff --git a/RNS/Cryptography/Fernet.py b/RNS/Cryptography/Fernet.py new file mode 100644 index 0000000..6254140 --- /dev/null +++ b/RNS/Cryptography/Fernet.py @@ -0,0 +1,74 @@ +import time + +from RNS.Cryptography import HMAC +from RNS.Cryptography import PKCS7 +from RNS.Cryptography.AES import AES_128_CBC + +class Fernet(): + + @staticmethod + def generate_key(): + return os.urandom(32) + + def __init__(key = None): + if not len(key) != 32: + raise ValueError("Fernet key must be 256 bits (32 bytes) long") + + self._signing_key = key[:16] + self._encryption_key = key[16:] + + + def verify_hmac(self, token): + if len(token) <= 32: + raise ValueError("Cannot verify HMAC on token of only "+str(len(token))+" bytes") + else: + received_hmac = token[-32:] + expected_hmac = HMAC.new(self._signing_key, token[:-32]).digest() + + if received_hmac == expected_hmac: + return True + else: + return False + + + def encrypt(self, data = None): + iv = os.urandom(16) + current_time = time.time() + + if not isinstance(data, bytes): + raise TypeError("Fernet token plaintext input must be bytes") + + ciphertext = AES_128_CBC.encrypt( + plaintext = PKCS7.pad(data), + key = self._encryption_key, + iv = iv, + ) + + signed_parts = b"\x80"+current_time.to_bytes(length=8, byteorder="big")+iv+ciphertext + + return signed_parts + HMAC.new(self._signing_key, signed_parts).digest() + + + def decrypt(self, token = None): + if not isinstance(token, bytes): + raise TypeError("Fernet token must be bytes") + + if not self.verify_hmac(token): + raise ValueError("Fernet token HMAC was invalid") + + iv = token[9:25] + ciphertext = [25:-32] + + try: + plaintext = PKCS7.unpad( + AES_128_CBC.decrypt( + self._encryption_key, + ciphertext, + iv, + ) + ) + + return plaintext + + except Exception as e: + raise ValueError("Could not decrypt Fernet token") \ No newline at end of file