0

How do I encrypt with my own key?

I've searched over the net to do this. How do I encrypt with my own key? In python I prefer cryptography though. It keeps popping out the error Fernet key must be 32 url-safe base64-encoded bytes and TypeError: a bytes-like object is required, not 'str'. I am trying to create a private variable function. I'm python new-comer.

Here is my uncompleted code. Thank you for your help.

from cryptography.fernet import Fernet
import inspect
import hashlib
import base64     #Fernet key must be 32 url-safe base64-encoded bytes

def encode(key, string):
    encoded_chars = []
    for i in xrange(len(string)):
        key_c = key[i % len(key)]
        encoded_c = chr(ord(string[i]) + ord(key_c) % 256)
        encoded_chars.append(encoded_c)
    encoded_string = "".join(encoded_chars)
    return base64.urlsafe_b64encode(encoded_string)

class private:
    class sec_storage:
        data = dict()
        hashed_data = dict()
    class var:
        def create(var,value):
            # creates key based on caller
            key = hashlib.sha224(str(inspect.stack()).encode()).hexdigest()
            cipher_suite = Fernet(base64.b64encode(key))   #Fernet key must be 32 url-safe base64-encoded bytes
            # encrypts using key
            encoded_text = cipher_suite.encrypt(value)
            # prepares storage
            hashed_var = hashlib.sha224("plus".join(list(var.encode(),key[:12])).hexdigest())
            hashed_value = hashlib.sha224(value.encode()).hexdigest()[12:30]
            private.sec_storage.data[hashed_var] = encoded_text
            private.sec_storage.hashed_data[hashed_var] = hashed_value

        def read(var):
            # creates key based on caller
            key = hashlib.sha224(str(inspect.stack()).encode()).hexdigest()
            cipher_suite = Fernet(base64.b64encode(key))     #Fernet key must be 32 url-safe base64-encoded bytes
            # retrieve var
            hashed_varname = hashlib.sha224("plus".join(list(var.encode(),key[:12])).hexdigest())
            try:
                hashed_var = private.sec_storage.data[hashed_varname]
            except NameError:
                raise NameError("Requested variable not found")
            # decrypts using key
            decoded_text = cipher_suite.decrypt(hashed_var)
            hashed_value = hashlib.sha224(decoded_text.encode()).hexdigest()[12:30]
            # checks if password is correct
            if private.sec_storage.hashed_data[hashed_varname] != hashed_value:
                raise ValueError("Value not as requested")
            return decoded_text
private.var.create("myvar","Hello World!")
print(private.var.read("myvar"))
print(sec_storage.data)

As you can see cipher_suite = Fernet(base64.b64encode(key)) #Fernet key must be 32 url-safe base64-encoded

How do I fix it?

  • 2
    _Fernet key must be 32 url-safe base64-encoded bytes_: SHA 224 creates a 28 bytes hash which is returned as hex string because of `hexdigest()` and is thus 56 bytes in size and not 32 bytes as required. _a bytes-like object is required, not 'str'_: `b64encode()` expects a bytes-like object and not a string. Suggestions: (1) A random byte sequence can easily be created with `os.urandom()` which returns a bytes-like object. (2) For a url safe string you have to use `urlsafe_b64encode()`. Remark: Use `digest()` instead of `hexdigest()` if the hash should be returned as bytes-like object. – Topaco Aug 15 '20 at 10:08
  • 2
    If you want to keep your key generation approach, the hex string must be encoded and only 32 bytes of the result may be used, e.g. `key = hashlib.sha224(...).hexdigest().encode('utf8')[:32]`. – Topaco Aug 15 '20 at 10:25
  • 1
    These things combined would make for a pretty good answer Topaco. I'd happily upvote answers such as those, even if they don't include a (complete) code fix. – Maarten Bodewes Aug 15 '20 at 21:11
  • Calling `hexdigest` to generate a key of sufficient length doubles the length of the string but hugely drops the entropy per byte. Instead of 2\*\*8 (256) values there are now only 2\*\*4 (16). Since Fernet uses the first 128-bits for the AES key and the second 128 as an HMAC key you've reduced the effective key space from 2\*\*128 to 2\*\*64, which is very, very bad. Coupled with it being the hash of the current stack the resulting key would be extremely dangerous. – Paul Kehrer Aug 16 '20 at 23:40

1 Answers1

2

The primary issue here is that Fernet expects a url-safe base64 encoded string that decodes to 32 bytes. The security of this construction relies on those 32 bytes containing sufficient entropy such that an attacker can't feasibly guess it.

In your example you're using the SHA224 hash of a stack trace. There are a few issues at play:

  • 224 bits/8 bits per byte=28 bytes, which is not a sufficiently long output.
  • Hashing a stack trace is not a safe method of deriving a key.
  • You've encoded via vanilla base64 when it should use urlsafe_b64encode.

In general it's best to generate your Fernet key with Fernet.generate_key(), but that requires you to store the key somewhere for later use. If you want to generate a key from something like a password the documentation has an example (https://cryptography.io/en/latest/fernet/#using-passwords-with-fernet).

Paul Kehrer
  • 13,466
  • 4
  • 40
  • 57