EncryptHelper

class privex.helpers.crypto.EncryptHelper.EncryptHelper(encrypt_key: str, **kwargs)[source]

Symmetric AES-128 encryption/decryption made painless - wrapper class for cryptography.fernet.Fernet

A wrapper class for the cryptography.fernet.Fernet encryption system, designed to make usage of Fernet as painless as possible.

The class EncryptHelper contains various methods for simplifying the use of the Python library Cryptography ‘s cryptography.fernet.Fernet system.

encrypt_str() / decrypt_str() facilitate painless encryption and decryption of data using AES-128 CBC. They can either be passed a 32-byte Fernet key (base64 encoded) as an argument, or leave the key as None and they’ll try to use the key defined on the EncryptHelper instance at EncryptHelper.encrypt_key

Basic usage:

>>> from privex.helpers import EncryptHelper
>>> key = EncryptHelper.generate_key()     # Generates a 32-byte symmetric key, returned as a base64 encoded string
>>> crypt = EncryptHelper(key)             # Create an instance of EncryptHelper, en/decrypting using ``key`` by default
# Encrypts the string 'hello world' with AES-128 CBC using the instance's key, returned as a base64 string
>>> enc = crypt.encrypt_str('hello world')
>>> print(enc)
gAAAAABc7ERTpu2D_uven3l-KtU_ewUC8YWKqXEbLEKrPKrKWT138MNq-I9RRtCD8UZLdQrcdM_IhUU6r8T16lQkoJZ-I7N39g==
>>> crypt.is_encrypted(enc)       # Check if a string/bytes is encrypted (only works with data matching the key)
True
>>> data = crypt.decrypt_str(enc) # Decrypt the encrypted data using the same key, outputs as a string
>>> print(data)
hello world
__init__(encrypt_key: str, **kwargs)[source]

Create an instance of EncryptHelper using the cryptography.fernet.Fernet key encrypt_key as the default key for encrypting/decrypting data.

Parameters

encrypt_key (str) – Base64 encoded Fernet key, used by default for encrypting/decrypting data

Methods

__init__(encrypt_key, **kwargs)

Create an instance of EncryptHelper using the cryptography.fernet.Fernet key encrypt_key as the default key for encrypting/decrypting data.

decrypt_str(data[, key])

Decrypts data previously encrypted using encrypt_str() with the same Fernet compatible key, and returns the decrypted version as a string.

encrypt_str(data[, key])

Encrypts a piece of data data passed as a string or bytes using Fernet with the passed 32-bit symmetric encryption key key.

from_file(obj, **settings)

Create an instance of EncryptHelper (or inheriting class) using a Fernet key loaded from a file, or stream object.

from_password(password, salt, **settings)

Create an instance of EncryptHelper (or inheriting class) from a password derived Fernet key, instead of a pre-generated Fernet key.

generate_key([output, mode])

Generate a compatible encryption key for use with cryptography.fernet.Fernet

get_fernet([key])

Used internally for getting Fernet instance with auto-fallback to encrypt_key and exception handling.

is_encrypted(data[, key])

Returns True if the passed data appears to be encrypted.

password_key(password[, salt, kdf])

Generate a cryptography.fernet.Fernet key based on a password and salt.

decrypt_str(data: Union[str, bytes], key: Union[str, bytes] = None)str[source]

Decrypts data previously encrypted using encrypt_str() with the same Fernet compatible key, and returns the decrypted version as a string.

The key cannot just be a random “password”, it must be a 32-byte key encoded with URL Safe base64. Use the method generate_key() to create a Fernet compatible encryption key.

Under the hood, Fernet uses AES-128 CBC to encrypt the data, with PKCS7 padding and HMAC_SHA256 authentication.

If the key parameter isn’t passed, or is empty (None / “”), then it will attempt to fall back to encrypt_key - if that’s also empty, EncryptKeyMissing will be raised.

Parameters
  • data (str) – The base64 encoded data to be decrypted, in the form of either a str or bytes.

  • key (str) – A Fernet encryption key (base64) for decryption, if blank, will fall back to encrypt_key

Raises
  • EncryptKeyMissing – Either no key was passed, or something is wrong with the key.

  • EncryptionError – Something went wrong while attempting to decrypt the data

Return str decrypted_data

The decrypted data as a string

encrypt_key: str

A base64 encoded Fernet key, used by default for functions such as encrypt_str()

encrypt_str(data: Union[str, bytes], key: Union[str, bytes] = None)str[source]

Encrypts a piece of data data passed as a string or bytes using Fernet with the passed 32-bit symmetric encryption key key. Outputs the encrypted data as a Base64 string for easy storage.

The key cannot just be a random “password”, it must be a 32-byte key encoded with URL Safe base64. Use the method generate_key() to create a Fernet compatible encryption key.

Under the hood, Fernet uses AES-128 CBC to encrypt the data, with PKCS7 padding and HMAC_SHA256 authentication.

If the key parameter isn’t passed, or is empty (None / “”), then it will attempt to fall back to self.encrypt_key - if that’s also empty, EncryptKeyMissing will be raised.

Parameters
  • data (str) – The data to be encrypted, in the form of either a str or bytes.

  • key (str) – A Fernet encryption key (base64) to be used, if left blank will fall back to encrypt_key

Raises
  • EncryptKeyMissing – Either no key was passed, or something is wrong with the key.

  • EncryptionError – Something went wrong while attempting to encrypt the data

Return str encrypted_data

The encrypted version of the passed data as a base64 encoded string.

classmethod from_file(obj: Union[str, _io.TextIOWrapper], **settings)[source]

Create an instance of EncryptHelper (or inheriting class) using a Fernet key loaded from a file, or stream object.

>>> enc = EncryptHelper.from_file('/home/john/fernet.key')
>>> d = enc.encrypt('hello')
>>> enc.decrypt(d)
'hello'
Parameters
  • obj (TextIOWrapper) – Load the key from the filename obj

  • obj – Load the key from the file/stream object obj using .read()

classmethod from_password(password: Union[str, bytes], salt: Union[str, bytes], **settings)[source]

Create an instance of EncryptHelper (or inheriting class) from a password derived Fernet key, instead of a pre-generated Fernet key.

See password_key() for more detailed usage information.

Example

>>> enc = EncryptHelper.from_password('MySecurePass', salt=b'Sup3rseCr3tsalt')
>>> d = enc.encrypt('hello')
>>> enc.decrypt(d)
'hello'
Parameters
  • password – A password to generate the key from, as str or bytes

  • salt – The salt to use when generating the key, as str or bytes If salt is a string, it can also be passed in base64 format.

static generate_key(output: Optional[Union[str, _io.TextIOWrapper]] = None, mode='w')str[source]

Generate a compatible encryption key for use with cryptography.fernet.Fernet

NOTE: Regardless of whether or not the method is outputting the key to a filename / stream, this method will always return the encryption key as a string after completion. The key returning was redacted from the outputting examples to help readability.

Examples

With no arguments, it will simply return the key as a string.

>>> EncryptHelper.generate_key()
'6vJ_o8XQRmX_TgUFTWWV_U2vm71ThnpWsCIvgXFWg9s='

If output is a str - it’s assumed to be a filename, and the Fernet key will be outputted to the file output using open(output, mode) (where mode defaults to 'w').

Below, we call generate_key with the string test.key.txt - and we can then see the file was created and contains the Fernet key encoded with Base64.

>>> EncryptHelper.generate_key('test.key.txt')
>>> open('test.key.txt').read()
'aRDR-gCrmrPrMr9hQnL4epIPl2Szbzfid_vSTO-rl20='

If output is a file/stream object, the method output.write(key) will be called, where key is the Fernet key as a string.

Below, we open test2.key.txt in write mode manually, then pass the file stream object to generate_key, which writes the key to the file.

>>> with open('test2.key.txt', 'w') as fp:
...     EncryptHelper.generate_key(fp)
>>> open('test2.key.txt').read()
'DAFEvRkwG7ws0ccjIv2QL_s5cpeWktqpbc7eSjL-V74='
Parameters
  • output (TextIOWrapper) – Simply return the generated key

  • output – Output the generated key to the filename output using the open mode mode

  • output – Output the generated key to the file/stream object output using .write(key: str)

  • mode (str) – If you’re passing a string filename as output - then this controls the open() mode, e.g. ‘w’, ‘a’, ‘w+’

Return str key

The generated Fernet key, encoded with Base64.

get_fernet(key: Union[str, bytes] = None)cryptography.fernet.Fernet[source]

Used internally for getting Fernet instance with auto-fallback to encrypt_key and exception handling.

Parameters

key (str) – Base64 Fernet symmetric key for en/decrypting data. If empty, will fallback to encrypt_key

Raises

EncryptKeyMissing – Either no key was passed, or something is wrong with the key.

Return Fernet f

Instance of Fernet using passed key or self.encrypt_key for encryption.

is_encrypted(data: Union[str, bytes], key: Union[str, bytes] = None)bool[source]

Returns True if the passed data appears to be encrypted. Can only verify encryption if the same key that was used to encrypt the data is passed.

Parameters
  • data (str) – The data to check for encryption, either as a string or bytes

  • key (str) – Base64 encoded Fernet symmetric key for decrypting data. If empty, fallback to encrypt_key

Raises

EncryptKeyMissing – Either no key was passed, or something is wrong with the key.

Return bool is_encrypted

True if the data is encrypted, False if it’s not encrypted or wrong key used.

static password_key(password, salt=None, kdf: Type[cryptography.hazmat.primitives.kdf.KeyDerivationFunction] = <class 'cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC'>, **kwargs) → Tuple[str, dict][source]

Generate a cryptography.fernet.Fernet key based on a password and salt. Key derivation is customisable.

Parameters
  • password – A password to generate the key from, as str or bytes

  • salt – The salt to use when generating the key, as str or bytes If salt is a string, it can also be passed in base64 format.

Standard Usage with manual salt:

Call password_key with a password of your choice, and a salt of your choice (ideally at least 16 chars), and a tuple containing the Fernet key (base64 encoded), and key derivative configuration will be returned.

>>> ek = EncryptHelper
>>> key, kd = ek.password_key('MySecurePass', salt=b'Sup3rseCr3tsalt')
>>> key
'rJ_g-lBT7pxeu4MVrhfi5rAv9yLbX5pTm6vkJj_Mezc='
>>> kd
{'length': 32, 'salt': 'U3VwM3JzZUNyM3RzYWx0', 'backend': <...Backend object at 0x7fd1c0220eb8>,
'algorithm': <...SHA256 object at 0x7fd1b0232278>, 'iterations': 100000, 'kdf': <class '...PBKDF2HMAC'>}

You can see when we call the method a second time with the same password and salt, we get the same Fernet key.

>>> key, kd = ek.password_key('MySecurePass', salt=b'Sup3rseCr3tsalt')
>>> key
'rJ_g-lBT7pxeu4MVrhfi5rAv9yLbX5pTm6vkJj_Mezc='

Now we can simply initialise the class with this key, and start encrypting/decrypting data:

>>> enc = EncryptHelper(key)
>>> mydata = enc.encrypt_str('hello')
>>> mydata
'gAAAAABdsJrpZvQAhAEwAGk2GPeJMUjUdp1FHAg42ncArvvQjqGztLslgexF7dKWbJ8bhYNt9MBzzT0WR_XEvl1j5Q95UOVTsQ=='
>>> enc.decrypt_str(mydata)
'hello'

Automatic salt generation:

While it’s strongly recommend that you pass your own salt (at least 16 bytes recommended), for convenience this method will automatically generate a 16 byte salt and return it as part of the dict (second tuple item) returned.

First, we generate a key from the password helloworld

>>> ek = EncryptHelper
>>> key, kd = ek.password_key('helloworld')
>>> key
'6asAQ0qTQtmjw54RBR_RVmwsyv6EgTY_lcnVgJAVKCQ='
>>> kd
{'length': 32, 'salt': 'bDU5MzJaaEhnZ1htSmlQeg==', 'backend': <...Backend object at 0x7ff968053860>,
 'algorithm': <...SHA256 object at 0x7ff9685f6160>, 'iterations': 100000, 'kdf': <class '...PBKDF2HMAC'>}

If we call password_key again with helloworld, you’ll notice it outputs a completely different key. This is because no salt was specified, so it simply generated yet another salt.

>>> ek.password_key('helloworld')[0]
'BfesIzfEPodtHSyPrpnkK0iDipHikaE7T1uuFFPnqmc='

To actually get the same Fernet key back, we have to either:

  • Pass the entire kd dictionary as kwargs (safest option, contains all params used for generation)

    >>> ek.password_key('helloworld', **kd)[0]
    '6asAQ0qTQtmjw54RBR_RVmwsyv6EgTY_lcnVgJAVKCQ='
    
  • Pass the generated salt from the kd object, alongside our password.

    >>> ek.password_key('helloworld', salt=kd['salt'])[0]
    '6asAQ0qTQtmjw54RBR_RVmwsyv6EgTY_lcnVgJAVKCQ='