Search code examples
pythonencryptioncompression

Why is Python encryption failing?


I am writing a chatting program. I want to save the chat history and reload it. For security, I will be encrypting the data and then compressing it. But no matter what I've been trying, I can't figure out why it's failing. The issue has to do with my two methods export_history and import_history.

def export_history(self, filename='chat_history.json.enc'):
    cipher = Fernet(self.key) # First, I generate a cipher (the key is generated using Fernet.generate_key() when the class is initialized).
    encrypted_data = base64.b64encode(cipher.encrypt(json.dumps([msg.to_dict() for msg in self.messages]).encode())) # I then encrypt the chat history using the key. I first write it as a json format though.
    file_data = self.key + encrypted_data # Here, I add my key to the beginning to my encrypted data. I know this is unsafe if I wanted to publish this program, but it's as a temporary placeholder, until I build the entire framework.
    compressed_data = lzma.compress(file_data) # Then I try to compress the data.
    encoded_data = base64.b64encode(compressed_data) # I encode the data.

    # Save encoded data to file using lzma
    with lzma.open(filename, 'wb') as file: # I open my chat history.
        file.write(encoded_data) # I save the compressed and encrypted data to the file.
def import_history(self, filename='chat_history.json.enc'):
    try:
        # Use lzma.open to read the file
        with lzma.open(filename, 'rb') as file: # I open the chat history file.
            # Read and decode base64 data
            file_data = file.read() # I read the contents of the file.
            compressed_data = base64.b64decode(file_data) # I decode the data so the decompression can use it.

            print(f"Read {len(compressed_data)} bytes of base64-decoded data.") # This is just for visual feedback.

            # Decompress the data using lzma
            decompressed_data = lzma.decompress(compressed_data) # I decompress the data.

            print(f"Decompressed to {len(decompressed_data)} bytes.") # Again, visual feedback.

            # Extract key and encrypted data
            key = decompressed_data[:32] # I get the encryption key.
            encrypted_data = decompressed_data[32:] # I get the encrypted data.

            print(f"The key was: {base64.urlsafe_b64decode(key)}") # I display the key, only for a visual.
            print(f"The raw data was: {base64.urlsafe_b64decode(encrypted_data)}") # I display the encrypted data.

            try:
                # Decrypt and load chat history
                cipher = Fernet(base64.urlsafe_b64encode(key))  # Throws "Fernet key must be 32 url-safe base64-encoded bytes." is not decoded using base64.urlsafe_b64encode(key) first.
                decrypted_data = cipher.decrypt(encrypted_data) # Throws "InvalidToken" when I do use base64.urlsafe_b64encode(key) on the pervious statement, but it doesn't even get to this point when I don't.
                data = json.loads(decrypted_data.decode())

                self.messages = [ChatRoomMessage.ChatRoomMessage(msg['sender'], msg['content'], datetime.datetime.fromisoformat(msg['timestamp'])) for msg in data]
                self.key = key  # Remove base64 encoding here

            except InvalidToken as e:
                print(f"Error during decryption: {e}")
                # Handle the error (e.g., notify the user, log the error, etc.)

    except FileNotFoundError:
        pass

I have tried so many ways of fixing. I tried encoding and decoding the data using different methods in different parts of the program, I tried asking for help from AIs they didn't seem to even understand the issue, let alone trying to help me fix it.

What I expect to happen was for the code to take the chat history, format it into a json format, then I want to encrypt the data, add the encryption key at the beginning of the data (yes it's insecure, but I need to get it working before I take security into account), compress the data, and then save the data into the file. Then when I reload, I expect it to load the file, decompress the file data, extract the key, decrypt the data, take the json format and turn it back into the chat history, and then display the chat history (handled in another class and that works fine).

Before I added compression and encryption, this class worked beautifully! I was able to save and load the chat history. Once I added in the compression and encryption, it keeps throwing exceptions. The errors seem to be related to when I try to decrypt it, but it could have taken place in either before or during decryption. It's even possible that I may be missing a step (such as adding in the data validation part), which I remember that when I used AES before, I thought I had to manually manage it. I may be wrong, but either way, I'm not sure why it's not working.

This is before I save the file.

Here is some example data. Let's say I said in the chat window:

User: Hi!
User: HRU?

The raw key looks like: b'CTbY1OCUIzgQg-IxB56gjKzeHpQh8ZsQJdPWPs_NJQA='

The raw json data looks like:

b'[{"sender": "User", "content": "Hi!", "timestamp": "2023-11-27T01:54:01.430667"}, {"sender": "User", "content": "HRU?", "timestamp": "2023-11-27T01:54:03.654174"}]'

The encrypted json data looks like:

b'Z0FBQUFBQmxaRDBOYzFJTndYX3VQdDVkSTBPc0ZyQ3lrLU5lVHFSdXZCTXRsNEw0MEpnVjhtSFhPVld0ak5kRVhYekJLakk3UmQyV3R1Nzk0VHg0SERvdWxNd0lvNkZ2cWRSeGY4UTRJUHJYRTRFM3ZNaklLc0hpN0NwVkFMT0Y5akFRUFNoMU5jcHptd1dvOHZQd1l1TWlJZ0hKaXFPX3ZjdHE5czFxcTdSTmszMFZyT01sZEM4RkE5bzZwTTVHSW5tUmRCb2EzcU1DSUJOUUgxQnoteUZxbXVKQkdEd0VzeUpLdGh3enpKbmgwbmFuM1VubF8wZWdxcUdoLWxOVTB4NTdHemc1U2ZGYmNSS0Z1N01maUoxT1Z6Wng3X21BQlpXZ2lwalU5VTBtelpYYVRZZFFnaDA9'

The uncompressed file data looks like:

b'CTbY1OCUIzgQg-IxB56gjKzeHpQh8ZsQJdPWPs_NJQA=Z0FBQUFBQmxaRDBOYzFJTndYX3VQdDVkSTBPc0ZyQ3lrLU5lVHFSdXZCTXRsNEw0MEpnVjhtSFhPVld0ak5kRVhYekJLakk3UmQyV3R1Nzk0VHg0SERvdWxNd0lvNkZ2cWRSeGY4UTRJUHJYRTRFM3ZNaklLc0hpN0NwVkFMT0Y5akFRUFNoMU5jcHptd1dvOHZQd1l1TWlJZ0hKaXFPX3ZjdHE5czFxcTdSTmszMFZyT01sZEM4RkE5bzZwTTVHSW5tUmRCb2EzcU1DSUJOUUgxQnoteUZxbXVKQkdEd0VzeUpLdGh3enpKbmgwbmFuM1VubF8wZWdxcUdoLWxOVTB4NTdHemc1U2ZGYmNSS0Z1N01maUoxT1Z6Wng3X21BQlpXZ2lwalU5VTBtelpYYVRZZFFnaDA9'

The compressed file data looks like:

b"\xfd7zXZ\x00\x00\x04\xe6\xd6\xb4F\x02\x00!\x01\x16\x00\x00\x00t/\xe5\xa3\xe0\x01\xcb\x01\x91]\x00!\x95\x08\xa8\xb2\x8f\xb1(\xb5\xcc\xd9v\xca\x93\xc2\xc3B{\x96\x9f+\x83\xa3\xab\xdcsg\x08\xb8\x0e\xed\xf8\x00y\xc3\xd42\xc0\x8bg\xa7\xd1\x06\xa1\x99\xfc\xa4\xb2G4 <\x1b\xaf\x14\xb5\x96B\x1a\xce.\xa4\xa6\xd0\xf8M\xc5\x84\xc8!\xdb\x19\xa6]U\x89\xe3\x94qA\xd2\x94\x96\xcfL\x8c\x13/\xe3P\xfa\xe89Z\x99Z\x06H'\xe7k\x85\xa1\xd0UD\x90h\xa1CI{\x96\xc5\x95\x82-\x97nS\x01\xc3\xe4\x90\xd4\x05\xb3\xf8\xa3}\xc0j\x96O\x9f\xb6\x04\x07Cu\x0e\xb5\xe8\xd7\x15r8\x9b\xf9\xdc-\xf0\x9eC\x17|\xf2\xfd\xdb\xa1d\xc5O\xaa\xe6\xd5\x07=-\xe0\xa4\xb5\xda\xc2\x03\xec\xa2\x9e\xae\xc2A\xabQ ((V\x1c\xa2\xefD\x010\xb6'\xc3\xb7k\xd8\x0eS\x17\x85\x7f-\xa9\x1d:\x1f\x01V\xb6\xe1\xba\x98+\x8801f\xa9\x13\x86$f\xc3\x1c ,G\xfc\x02QeiO\\\xa9mrAT\x9f\x8d\xe4\xc8q\xdd]y\x96[\xb1\xd5N\n\xef&\x894\x13\xf8(\xd7$Au\xddw,\x14\x07j\x02\xe1\x85F\xc0\x81\xce#P\xe4\xe0=\xd6\xb5\x14\xe5\x8f\xac\xe2K\xb3Ga\x1b\x84X\x8d\xdaw\xd0L<\x1b\xfc\x1a\x12\x1c\xd7F\x1dt \xe9=\xf5\xd3VM\xd7\xd6\xfd.\x10\xc7h\xf5>O\x9f5\x95\x0f\xd1O\x13\xd0$m\x8e\xec!\xa4\xe3}\x17OTc\x85o\xc2Z\x19+X#S\x92\xf7o\x91\x91\xb4\x8cJ\x08\xbc\xf8\x86R|p6\x1e\xc1\x87\x92O,#\xfdY\x92[\xc9nc\x1e(\xbf\xae\xbb\xef\xbfV\xb8=`\x00\x00\x00\x00\x1d\rDE\xafrKn\x00\x01\xad\x03\xcc\x03\x00\x00\xdaC\xaa\x1f\xb1\xc4g\xfb\x02\x00\x00\x00\x00\x04YZ"

The compressed file data after encoding looks like:

b'/Td6WFoAAATm1rRGAgAhARYAAAB0L+Wj4AHLAZFdACGVCKiyj7EotczZdsqTwsNCe5afK4Ojq9xzZwi4Du34AHnD1DLAi2en0Qahmfykskc0IDwbrxS1lkIazi6kptD4TcWEyCHbGaZdVYnjlHFB0pSWz0yMEy/jUProOVqZWgZIJ+drhaHQVUSQaKFDSXuWxZWCLZduUwHD5JDUBbP4o33AapZPn7YEB0N1DrXo1xVyOJv53C3wnkMXfPL926FkxU+q5tUHPS3gpLXawgPsop6uwkGrUSAoKFYcou9EATC2J8O3a9gOUxeFfy2pHTofAVa24bqYK4gwMWapE4YkZsMcICxH/AJRZWlPXKltckFUn43kyHHdXXmWW7HVTgrvJok0E/go1yRBdd13LBQHagLhhUbAgc4jUOTgPda1FOWPrOJLs0dhG4RYjdp30Ew8G/waEhzXRh10IOk99dNWTdfW/S4Qx2j1Pk+fNZUP0U8T0CRtjuwhpON9F09UY4VvwloZK1gjU5L3b5GRtIxKCLz4hlJ8cDYewYeSTywj/VmSW8luYx4ov667779WuD1gAAAAAB0NREWvcktuAAGtA8wDAADaQ6ofscRn+wIAAAAABFla'

This is when I open the file

The compressed file data looks like:

b'/Td6WFoAAATm1rRGAgAhARYAAAB0L+Wj4AHLAZFdACGVCKiyj7EotczZdsqTwsNCe5afK4Ojq9xzZwi4Du34AHnD1DLAi2en0Qahmfykskc0IDwbrxS1lkIazi6kptD4TcWEyCHbGaZdVYnjlHFB0pSWz0yMEy/jUProOVqZWgZIJ+drhaHQVUSQaKFDSXuWxZWCLZduUwHD5JDUBbP4o33AapZPn7YEB0N1DrXo1xVyOJv53C3wnkMXfPL926FkxU+q5tUHPS3gpLXawgPsop6uwkGrUSAoKFYcou9EATC2J8O3a9gOUxeFfy2pHTofAVa24bqYK4gwMWapE4YkZsMcICxH/AJRZWlPXKltckFUn43kyHHdXXmWW7HVTgrvJok0E/go1yRBdd13LBQHagLhhUbAgc4jUOTgPda1FOWPrOJLs0dhG4RYjdp30Ew8G/waEhzXRh10IOk99dNWTdfW/S4Qx2j1Pk+fNZUP0U8T0CRtjuwhpON9F09UY4VvwloZK1gjU5L3b5GRtIxKCLz4hlJ8cDYewYeSTywj/VmSW8luYx4ov667779WuD1gAAAAAB0NREWvcktuAAGtA8wDAADaQ6ofscRn+wIAAAAABFla'

The decoded compressed file data looks like:

b"\xfd7zXZ\x00\x00\x04\xe6\xd6\xb4F\x02\x00!\x01\x16\x00\x00\x00t/\xe5\xa3\xe0\x01\xcb\x01\x91]\x00!\x95\x08\xa8\xb2\x8f\xb1(\xb5\xcc\xd9v\xca\x93\xc2\xc3B{\x96\x9f+\x83\xa3\xab\xdcsg\x08\xb8\x0e\xed\xf8\x00y\xc3\xd42\xc0\x8bg\xa7\xd1\x06\xa1\x99\xfc\xa4\xb2G4 <\x1b\xaf\x14\xb5\x96B\x1a\xce.\xa4\xa6\xd0\xf8M\xc5\x84\xc8!\xdb\x19\xa6]U\x89\xe3\x94qA\xd2\x94\x96\xcfL\x8c\x13/\xe3P\xfa\xe89Z\x99Z\x06H'\xe7k\x85\xa1\xd0UD\x90h\xa1CI{\x96\xc5\x95\x82-\x97nS\x01\xc3\xe4\x90\xd4\x05\xb3\xf8\xa3}\xc0j\x96O\x9f\xb6\x04\x07Cu\x0e\xb5\xe8\xd7\x15r8\x9b\xf9\xdc-\xf0\x9eC\x17|\xf2\xfd\xdb\xa1d\xc5O\xaa\xe6\xd5\x07=-\xe0\xa4\xb5\xda\xc2\x03\xec\xa2\x9e\xae\xc2A\xabQ ((V\x1c\xa2\xefD\x010\xb6'\xc3\xb7k\xd8\x0eS\x17\x85\x7f-\xa9\x1d:\x1f\x01V\xb6\xe1\xba\x98+\x8801f\xa9\x13\x86$f\xc3\x1c ,G\xfc\x02QeiO\\\xa9mrAT\x9f\x8d\xe4\xc8q\xdd]y\x96[\xb1\xd5N\n\xef&\x894\x13\xf8(\xd7$Au\xddw,\x14\x07j\x02\xe1\x85F\xc0\x81\xce#P\xe4\xe0=\xd6\xb5\x14\xe5\x8f\xac\xe2K\xb3Ga\x1b\x84X\x8d\xdaw\xd0L<\x1b\xfc\x1a\x12\x1c\xd7F\x1dt \xe9=\xf5\xd3VM\xd7\xd6\xfd.\x10\xc7h\xf5>O\x9f5\x95\x0f\xd1O\x13\xd0$m\x8e\xec!\xa4\xe3}\x17OTc\x85o\xc2Z\x19+X#S\x92\xf7o\x91\x91\xb4\x8cJ\x08\xbc\xf8\x86R|p6\x1e\xc1\x87\x92O,#\xfdY\x92[\xc9nc\x1e(\xbf\xae\xbb\xef\xbfV\xb8=`\x00\x00\x00\x00\x1d\rDE\xafrKn\x00\x01\xad\x03\xcc\x03\x00\x00\xdaC\xaa\x1f\xb1\xc4g\xfb\x02\x00\x00\x00\x00\x04YZ"

The decompressed file data looks like:

b'CTbY1OCUIzgQg-IxB56gjKzeHpQh8ZsQJdPWPs_NJQA=Z0FBQUFBQmxaRDBOYzFJTndYX3VQdDVkSTBPc0ZyQ3lrLU5lVHFSdXZCTXRsNEw0MEpnVjhtSFhPVld0ak5kRVhYekJLakk3UmQyV3R1Nzk0VHg0SERvdWxNd0lvNkZ2cWRSeGY4UTRJUHJYRTRFM3ZNaklLc0hpN0NwVkFMT0Y5akFRUFNoMU5jcHptd1dvOHZQd1l1TWlJZ0hKaXFPX3ZjdHE5czFxcTdSTmszMFZyT01sZEM4RkE5bzZwTTVHSW5tUmRCb2EzcU1DSUJOUUgxQnoteUZxbXVKQkdEd0VzeUpLdGh3enpKbmgwbmFuM1VubF8wZWdxcUdoLWxOVTB4NTdHemc1U2ZGYmNSS0Z1N01maUoxT1Z6Wng3X21BQlpXZ2lwalU5VTBtelpYYVRZZFFnaDA9'

The key looks like:

b'CTbY1OCUIzgQg-IxB56gjKzeHpQh8ZsQ'

The encrypted data looks like:

b'JdPWPs_NJQA=Z0FBQUFBQmxaRDBOYzFJTndYX3VQdDVkSTBPc0ZyQ3lrLU5lVHFSdXZCTXRsNEw0MEpnVjhtSFhPVld0ak5kRVhYekJLakk3UmQyV3R1Nzk0VHg0SERvdWxNd0lvNkZ2cWRSeGY4UTRJUHJYRTRFM3ZNaklLc0hpN0NwVkFMT0Y5akFRUFNoMU5jcHptd1dvOHZQd1l1TWlJZ0hKaXFPX3ZjdHE5czFxcTdSTmszMFZyT01sZEM4RkE5bzZwTTVHSW5tUmRCb2EzcU1DSUJOUUgxQnoteUZxbXVKQkdEd0VzeUpLdGh3enpKbmgwbmFuM1VubF8wZWdxcUdoLWxOVTB4NTdHemc1U2ZGYmNSS0Z1N01maUoxT1Z6Wng3X21BQlpXZ2lwalU5VTBtelpYYVRZZFFnaDA9'

Solution

  • import lzma
    from cryptography.fernet import Fernet
    import base64
    import json
    
    def export_history(key, filename='chat_history.json.enc'):
        cipher = Fernet(key) 
        print(f"export_history: {key=}")
        encrypted_data = base64.b64encode(cipher.encrypt(json.dumps([msg for msg in messages]).encode())) 
        print(f"export_history: {encrypted_data=}")
        file_data = key + encrypted_data
        print(f"export_history: {file_data=}")
        compressed_data = lzma.compress(file_data) 
        print(f"export_history: {compressed_data=}")
        encoded_data = base64.b64encode(compressed_data) 
        print(f"export_history: {encoded_data=}")
    
        
        with lzma.open(filename, 'wb') as file: 
            file.write(encoded_data) 
    
    
    def import_history( filename='chat_history.json.enc'):
        try:
            
            with lzma.open(filename, 'rb') as file: 
                
                file_data = file.read()
                print(f"import_history: {file_data=}")
                compressed_data = base64.b64decode(file_data) 
                print(f"import_history: {compressed_data=}")
                decompressed_data = lzma.decompress(compressed_data) 
                print(f"import_history: {decompressed_data=}")
                key = decompressed_data[:32] 
                print(f"import_history: {key=}")
                encrypted_data = decompressed_data[32:] 
                print(f"import_history: {encrypted_data=}")
                try:
                    cipher = Fernet(base64.urlsafe_b64encode(key))  
                    decrypted_data = cipher.decrypt(encrypted_data) 
                    print(f"import_history: {decrypted_data=}")
                    data = json.loads(decrypted_data.decode())
                    print(f"import_history: {data=}")
                except Exception as e:
                    print(f"Error during decryption: {e}")
                    
    
        except FileNotFoundError:
            pass
    
    
    def main():
        key = Fernet.generate_key()
        
        export_history(key)
        import_history()
    
    messages = [{"sender": "User", "content": "Hi!", "timestamp": "2023-11-27T01:54:01.430667"}, {"sender": "User", "content": "HRU?", "timestamp": "2023-11-27T01:54:03.654174"}]
    
    if __name__ == '__main__':
        main()
    

    If you print the variables values without doing any kind of transformation you'll see where the variables that should match on both functions differ.

    Here's the output of the code I posted:

    export_history: key=b'9xlL0bYJkoLlh7GpMk81ee4Wuts9TQY-wzQMszn9mdE='
    export_history: encrypted_data=b'Z0FBQUFBQmxaRVp0SXA1SmgxMldhcmxCaVRQQklxS1QxSFg4ZlVNUzZMckp0QVQ3TzFKNWRFaFhpTkdBTldYMW50RGEtYjRHRUxROEl2aklwZmt6d0JZUldLYURzLW1Ia3Y2NXNPQjdmdlVpMlVnTER6VGtsWkFIdEdXN2Z6VjZGdFBCREdEcjR4NUtWckZ4cjd4ZE40cGdBWWFPd3EzbWpXTTU0QVRhM2tUbkJTU2RsNzVleDVmaGY5Qzl3YV9pU3p6TFRVSlVJTFBidWRCWS1mZGNVVVF1X1lvblRrRXFZTUswWmdTZDhGN05MZFdzT3h3YnJ6aWJQaGpTZ216MVpXZmx5OHFwcEQxVmp5R3ZadFZadzYyaHZSZ05nOUo1bGo1NE9yN01PbTY3Q08zWXJRQWVLRms9'
    export_history: file_data=b'9xlL0bYJkoLlh7GpMk81ee4Wuts9TQY-wzQMszn9mdE=Z0FBQUFBQmxaRVp0SXA1SmgxMldhcmxCaVRQQklxS1QxSFg4ZlVNUzZMckp0QVQ3TzFKNWRFaFhpTkdBTldYMW50RGEtYjRHRUxROEl2aklwZmt6d0JZUldLYURzLW1Ia3Y2NXNPQjdmdlVpMlVnTER6VGtsWkFIdEdXN2Z6VjZGdFBCREdEcjR4NUtWckZ4cjd4ZE40cGdBWWFPd3EzbWpXTTU0QVRhM2tUbkJTU2RsNzVleDVmaGY5Qzl3YV9pU3p6TFRVSlVJTFBidWRCWS1mZGNVVVF1X1lvblRrRXFZTUswWmdTZDhGN05MZFdzT3h3YnJ6aWJQaGpTZ216MVpXZmx5OHFwcEQxVmp5R3ZadFZadzYyaHZSZ05nOUo1bGo1NE9yN01PbTY3Q08zWXJRQWVLRms9'
    export_history: compressed_data=b'\xfd7zXZ\x00\x00\x04\xe6\xd6\xb4F\x02\x00!\x01\x16\x00\x00\x00t/\xe5\xa3\xe0\x01\xcb\x01\x8f]\x00\x1c\x9e\t\x84\xc6e\xae\xe2\xd5\x9b\x01>\xa3\xc0>#\xe8k\x80\x18\xa3s\xa1(%K\x1b\xbcy\xfc\xf4\xd9#\xd8\xe6\x06!h\\\x9d\xbb\xc5p\xb4\xbf\xe3\xa2\x15|\x18\xf3\xc9j\xa7\xb4\xc7\x16k<\xd4\xe8\xf0]\x13\xf4P\x01\x7f\xf4#tG\x82\xf9\x86\xa6\xd4\x1e\xf8W\xce\xc9d`\n\xbe\x80za\x93&\xdb\xb0\x90\x95\x8c\xb50\x95\xb5\x8f\xd4\xeb\x8c$\xb6_\xb0ge\xa9m\xec\x00^\xe2\x98R{\x96"J\x8e\x97\xa7\x8ch\x15\x17\xb13s8\xfd\xb4Hd\x11J\xfb_\x81\xa1\x1a\x0b`+\xd0%\x14\xfcZ]\x92\xdcV\xa8\xcf\x12\xd4\x1d;J\x1a}\xc7\xec\xb1^b\x1e\x1b\x96\x98i\x9d\r;\xda\xc3\x9e_\x9a\x0fj\x0c9[\xfci\xbap\xe2i%\x7f\xf0k\xfdL\xd3\xc2\x1d\xf4Jh\xbfE\x85\xe7\xc5\xa7\x83\x83g/\xa4\x97\xb9\xe29\xc4D6\xa3z\xb6\r\xd3z\xb3\xa6\xb5\x86\x8f\xaa\x83\x86\x9ex\x96\x96\x17pU\xdd\x06\xae\x06\xf8\xf2\xc39f\x1bV\xfc\x07\x8fZ\xa1\x94\xd6\xf3\xdbt\te\x9dH\x8d\xb5A\xce\xf8\xde\xc4J\xac\xef\xca\xdcL\x153\nR\xfa\x9b~\xa0rD\x98\\\xa2\xbc#KA\xdb\x17\xa3=\xa9\xa7\xb3j\xcf\xef\x90\xe4\xa9K\xd6\xf5p\x84#\xf1\xfb\x91\x0b\xd3\xb5\xef-\xff\x13\xcd\xbe\xf0/`\xae\xa2\x8c\xf6CUr\xa9\x97;>\xac\xa4OQ_\n\x0e\xab\n\xecJ\x96_v\xa3\x82\x01\xae\x12C$v\xe6`3\xbe\x82\x85\x05\x87e\xceV\xc3:\xc2\xa3\x19\xd7\xf5*\xe0+\xbf\xc5\x909\xef\xf4\xfa\xed\xa7\xea@\x00\x00\xf0\xaf\xeb\x8av\xce\xb8\xe9\x00\x01\xab\x03\xcc\x03\x00\x00\xc7\xa0\xf3\xc9\xb1\xc4g\xfb\x02\x00\x00\x00\x00\x04YZ'
    export_history: encoded_data=b'/Td6WFoAAATm1rRGAgAhARYAAAB0L+Wj4AHLAY9dAByeCYTGZa7i1ZsBPqPAPiPoa4AYo3OhKCVLG7x5/PTZI9jmBiFoXJ27xXC0v+OiFXwY88lqp7THFms81OjwXRP0UAF/9CN0R4L5hqbUHvhXzslkYAq+gHphkybbsJCVjLUwlbWP1OuMJLZfsGdlqW3sAF7imFJ7liJKjpenjGgVF7Ezczj9tEhkEUr7X4GhGgtgK9AlFPxaXZLcVqjPEtQdO0oafcfssV5iHhuWmGmdDTvaw55fmg9qDDlb/Gm6cOJpJX/wa/1M08Id9Epov0WF58Wng4NnL6SXueI5xEQ2o3q2DdN6s6a1ho+qg4aeeJaWF3BV3QauBvjywzlmG1b8B49aoZTW89t0CWWdSI21Qc743sRKrO/K3EwVMwpS+pt+oHJEmFyivCNLQdsXoz2pp7Nqz++Q5KlL1vVwhCPx+5EL07XvLf8Tzb7wL2Cuooz2Q1VyqZc7PqykT1FfCg6rCuxKll92o4IBrhJDJHbmYDO+goUFh2XOVsM6wqMZ1/Uq4Cu/xZA57/T67afqQAAA8K/rinbOuOkAAasDzAMAAMeg88mxxGf7AgAAAAAEWVo='
    import_history: file_data=b'/Td6WFoAAATm1rRGAgAhARYAAAB0L+Wj4AHLAY9dAByeCYTGZa7i1ZsBPqPAPiPoa4AYo3OhKCVLG7x5/PTZI9jmBiFoXJ27xXC0v+OiFXwY88lqp7THFms81OjwXRP0UAF/9CN0R4L5hqbUHvhXzslkYAq+gHphkybbsJCVjLUwlbWP1OuMJLZfsGdlqW3sAF7imFJ7liJKjpenjGgVF7Ezczj9tEhkEUr7X4GhGgtgK9AlFPxaXZLcVqjPEtQdO0oafcfssV5iHhuWmGmdDTvaw55fmg9qDDlb/Gm6cOJpJX/wa/1M08Id9Epov0WF58Wng4NnL6SXueI5xEQ2o3q2DdN6s6a1ho+qg4aeeJaWF3BV3QauBvjywzlmG1b8B49aoZTW89t0CWWdSI21Qc743sRKrO/K3EwVMwpS+pt+oHJEmFyivCNLQdsXoz2pp7Nqz++Q5KlL1vVwhCPx+5EL07XvLf8Tzb7wL2Cuooz2Q1VyqZc7PqykT1FfCg6rCuxKll92o4IBrhJDJHbmYDO+goUFh2XOVsM6wqMZ1/Uq4Cu/xZA57/T67afqQAAA8K/rinbOuOkAAasDzAMAAMeg88mxxGf7AgAAAAAEWVo='
    import_history: compressed_data=b'\xfd7zXZ\x00\x00\x04\xe6\xd6\xb4F\x02\x00!\x01\x16\x00\x00\x00t/\xe5\xa3\xe0\x01\xcb\x01\x8f]\x00\x1c\x9e\t\x84\xc6e\xae\xe2\xd5\x9b\x01>\xa3\xc0>#\xe8k\x80\x18\xa3s\xa1(%K\x1b\xbcy\xfc\xf4\xd9#\xd8\xe6\x06!h\\\x9d\xbb\xc5p\xb4\xbf\xe3\xa2\x15|\x18\xf3\xc9j\xa7\xb4\xc7\x16k<\xd4\xe8\xf0]\x13\xf4P\x01\x7f\xf4#tG\x82\xf9\x86\xa6\xd4\x1e\xf8W\xce\xc9d`\n\xbe\x80za\x93&\xdb\xb0\x90\x95\x8c\xb50\x95\xb5\x8f\xd4\xeb\x8c$\xb6_\xb0ge\xa9m\xec\x00^\xe2\x98R{\x96"J\x8e\x97\xa7\x8ch\x15\x17\xb13s8\xfd\xb4Hd\x11J\xfb_\x81\xa1\x1a\x0b`+\xd0%\x14\xfcZ]\x92\xdcV\xa8\xcf\x12\xd4\x1d;J\x1a}\xc7\xec\xb1^b\x1e\x1b\x96\x98i\x9d\r;\xda\xc3\x9e_\x9a\x0fj\x0c9[\xfci\xbap\xe2i%\x7f\xf0k\xfdL\xd3\xc2\x1d\xf4Jh\xbfE\x85\xe7\xc5\xa7\x83\x83g/\xa4\x97\xb9\xe29\xc4D6\xa3z\xb6\r\xd3z\xb3\xa6\xb5\x86\x8f\xaa\x83\x86\x9ex\x96\x96\x17pU\xdd\x06\xae\x06\xf8\xf2\xc39f\x1bV\xfc\x07\x8fZ\xa1\x94\xd6\xf3\xdbt\te\x9dH\x8d\xb5A\xce\xf8\xde\xc4J\xac\xef\xca\xdcL\x153\nR\xfa\x9b~\xa0rD\x98\\\xa2\xbc#KA\xdb\x17\xa3=\xa9\xa7\xb3j\xcf\xef\x90\xe4\xa9K\xd6\xf5p\x84#\xf1\xfb\x91\x0b\xd3\xb5\xef-\xff\x13\xcd\xbe\xf0/`\xae\xa2\x8c\xf6CUr\xa9\x97;>\xac\xa4OQ_\n\x0e\xab\n\xecJ\x96_v\xa3\x82\x01\xae\x12C$v\xe6`3\xbe\x82\x85\x05\x87e\xceV\xc3:\xc2\xa3\x19\xd7\xf5*\xe0+\xbf\xc5\x909\xef\xf4\xfa\xed\xa7\xea@\x00\x00\xf0\xaf\xeb\x8av\xce\xb8\xe9\x00\x01\xab\x03\xcc\x03\x00\x00\xc7\xa0\xf3\xc9\xb1\xc4g\xfb\x02\x00\x00\x00\x00\x04YZ'
    import_history: decompressed_data=b'9xlL0bYJkoLlh7GpMk81ee4Wuts9TQY-wzQMszn9mdE=Z0FBQUFBQmxaRVp0SXA1SmgxMldhcmxCaVRQQklxS1QxSFg4ZlVNUzZMckp0QVQ3TzFKNWRFaFhpTkdBTldYMW50RGEtYjRHRUxROEl2aklwZmt6d0JZUldLYURzLW1Ia3Y2NXNPQjdmdlVpMlVnTER6VGtsWkFIdEdXN2Z6VjZGdFBCREdEcjR4NUtWckZ4cjd4ZE40cGdBWWFPd3EzbWpXTTU0QVRhM2tUbkJTU2RsNzVleDVmaGY5Qzl3YV9pU3p6TFRVSlVJTFBidWRCWS1mZGNVVVF1X1lvblRrRXFZTUswWmdTZDhGN05MZFdzT3h3YnJ6aWJQaGpTZ216MVpXZmx5OHFwcEQxVmp5R3ZadFZadzYyaHZSZ05nOUo1bGo1NE9yN01PbTY3Q08zWXJRQWVLRms9'
    import_history: key=b'9xlL0bYJkoLlh7GpMk81ee4Wuts9TQY-'
    import_history: encrypted_data=b'wzQMszn9mdE=Z0FBQUFBQmxaRVp0SXA1SmgxMldhcmxCaVRQQklxS1QxSFg4ZlVNUzZMckp0QVQ3TzFKNWRFaFhpTkdBTldYMW50RGEtYjRHRUxROEl2aklwZmt6d0JZUldLYURzLW1Ia3Y2NXNPQjdmdlVpMlVnTER6VGtsWkFIdEdXN2Z6VjZGdFBCREdEcjR4NUtWckZ4cjd4ZE40cGdBWWFPd3EzbWpXTTU0QVRhM2tUbkJTU2RsNzVleDVmaGY5Qzl3YV9pU3p6TFRVSlVJTFBidWRCWS1mZGNVVVF1X1lvblRrRXFZTUswWmdTZDhGN05MZFdzT3h3YnJ6aWJQaGpTZ216MVpXZmx5OHFwcEQxVmp5R3ZadFZadzYyaHZSZ05nOUo1bGo1NE9yN01PbTY3Q08zWXJRQWVLRms9'
    

    In any IDE that highlights other instances of theselected text like VSCode you'll see that the key in export and import does only matches up to 9xlL0bYJkoLlh7GpMk81ee4Wuts9TQY-

    You use key = decompressed_data[:32] but 32 bytes is before the key was encoded to base64.

    When exporting you could add at the beginning of the file_data the number of bytes of key or simply just add a space as a separator like file_data = key + ' ' + encrypted_data

    Here's adding the length of the key to the beginning of the data written to the file.

    # on export
    
    key_size_in_bytes = len(key)
    print(f"export_history: {key_size_in_bytes=}")
    key_size_in_bytes_repr = key_size_in_bytes.to_bytes(2, byteorder='big')
    print(f"export_history: {key_size_in_bytes_repr=}")
    file_data = key_size_in_bytes_repr + key + encrypted_data
    
    
    # on import
    key_size_bytes = int.from_bytes(decompressed_data[:2], byteorder='big')
    print(f"import_history: {key_size_bytes=}")
    
    key_base64 = decompressed_data[2:key_size_bytes+2].decode('utf-8')
    print(f"import_history: {key_base64=}")
    
    encrypted_data = decompressed_data[key_size_bytes+2:]
    print(f"import_history: {encrypted_data=}")
    

    And since you already have the base64 key in key_base64 you have to change

    # this
    cipher = Fernet(base64.urlsafe_b64decode(key)) 
    
    # to
    cipher = Fernet(key_base64)
    

    Now with all these changes you'll still get the error:

    cryptography.fernet.InvalidToken
    

    This might be hard to locate because of how you nest function calls like in

    encrypted_data = base64.b64encode(cipher.encrypt(json.dumps([msg for msg in messages]).encode())) 
    
    # Change json.dumps([msg for msg in messages]).encode() to string for easier reading
    
    encrypted_data = base64.b64encode(cipher.encrypt(string))
    

    First you encrypt the string and then base64 encode it. Then later compress it and base64 encode that then write to file, so your steps to export are:

    • string (json.dumps)
    • cipher.encrypt
    • base64.b64encode
    • lzma.compress
    • base64.b64encode

    But you steps for import are:

    • base64.b64decode
    • lzma.decompress
    • cipher.decrypt
    • string (json.loads)

    This would be easy to see if each step was separated in a line of it's own.

    So before cipher.decrypt we have to decoe base64 again:

    base64_decoded_encrypted_data = base64.b64decode(encrypted_data)
    decrypted_data = cipher.decrypt(base64_decoded_encrypted_data)
    

    And this is the final whole output of the prints:

    export_history: key=b'NlxKPLoEnHCY45LaZkb0yyyvj5oemOYlCraqWIW5Nbw='
    export_history: encrypted_data=b'Z0FBQUFBQmxaRTZVU05HZTlac3VtaGo0cFFQV0dmYURtZVJjQ25VSW9iM1Q5cFo5aGRQVm5MMEZnY1BPUVI0bmpjeXdYVWUxa1hkb1NPNnBCekszTVlNcHlIYXBaTkFvRTgtRExZWXA5MHBlVjY4dXQtR01tdTI1MXI1b1RWLTdIeFhPekN0MnhWcHFNR3E3SWZKUlJTaU8tSm13S2hCVkVPVHFqMzBHSDVVYTBZeG9KWUhTZTZoLU0zMUdaNFhNanB3aml1dDBMY3JkeG1FZ3RrVGN1NzF1dXpHWWRfQUI5Wl9PZ3Voa1hIWmdDQndZaGRDYllURmRoWGx6OGFiRUdGWVB0M0p5NmRWWGNrU19TOXYwdTQ4MnVLdHR3Vlh5RzlUZWFjTzFZUnU1cnIzcGZqZWVWUW89'
    export_history: key_size_in_bytes=44
    export_history: key_size_in_bytes_repr=b'\x00,'
    export_history: file_data=b'\x00,NlxKPLoEnHCY45LaZkb0yyyvj5oemOYlCraqWIW5Nbw=Z0FBQUFBQmxaRTZVU05HZTlac3VtaGo0cFFQV0dmYURtZVJjQ25VSW9iM1Q5cFo5aGRQVm5MMEZnY1BPUVI0bmpjeXdYVWUxa1hkb1NPNnBCekszTVlNcHlIYXBaTkFvRTgtRExZWXA5MHBlVjY4dXQtR01tdTI1MXI1b1RWLTdIeFhPekN0MnhWcHFNR3E3SWZKUlJTaU8tSm13S2hCVkVPVHFqMzBHSDVVYTBZeG9KWUhTZTZoLU0zMUdaNFhNanB3aml1dDBMY3JkeG1FZ3RrVGN1NzF1dXpHWWRfQUI5Wl9PZ3Voa1hIWmdDQndZaGRDYllURmRoWGx6OGFiRUdGWVB0M0p5NmRWWGNrU19TOXYwdTQ4MnVLdHR3Vlh5RzlUZWFjTzFZUnU1cnIzcGZqZWVWUW89'
    export_history: compressed_data=b'\xfd7zXZ\x00\x00\x04\xe6\xd6\xb4F\x02\x00!\x01\x16\x00\x00\x00t/\xe5\xa3\xe0\x01\xcd\x01\x8f]\x00\x00\x0b\xe7%\x1a\x17 \x92\xb9\xa6Dtd\x11E\xf5U\xd1\xf2\xbf\xe5`.m\xe2\xf05%\xffN\x8d \xd5\xd6\xddM\x1c \x06\x8dFg\xa2\x1a\xa7\x1e\x85~hF\x84\x99\xf1\xbf\x048\x08\xb8\xde\xe3\x96\x169\x83\x17\x03\xe2\xca\x10\xbc\x8cI\xb552\x98\x0e4@\xb9]\xc2\xe9,\xbf\xae\xdb6|\xfd\x1b1\xa6\xd6\x1fv8\xa7\xacQ\xbb\x8a\xcd\xdc\xad\x07\x8c\x86\xc2U\x7f\x8b\xf1\x89\xa1\xee\x0f\xb4\x1c8%\x17HS*\x1b\x07<\xc1\xae+a\x84\xf8\x85\x00\xbe\x9e\x17\xa0\xc0\xd5E W=f\x1cY\x15\xa5E\xda\xb5^CJ\xec\xa8.\xfer\x80\xa7\xf41\xd6G\xc5w\xba\x9b\xa5WL}j\xf5l\x9c\x88K\xbbh\xf2NK\x01\xbfS\x06\xce1\xa66M\x88E\xe9i}P\xe6\x14\x8aI5Y\x9b.\x0eTz\xcb\xd6\xd1\xd9\xdf^\xe6?\x0eQ22\x1a\x15b\xa3\xb0\xdc:\xc6\x05\x91\xbdxu\x02\xab\xba%8%\xdb8\x99O\xb7\x8a\x9f+{f\xbe\xd6/9I\xea\x0f\xf3\x19H{\xe3\x8a8c\xd9\xb9\xbb\x9d\x1a\xe3Y \xbfF\x8b\x87\xd3\xc0.\x89d\xc5\xf3\x18\x03v\x9f\xa8\x1cS\xe0\xa9\x8f\x9b\\Z[\x87\xf5l\xf9\xb3g\x80\x14\x97\x1c\xf1\xaf\xf4\x7f\xf9\x10\x96>\x18\xf1Z\x1c\xf8f\x119\x1cKi\x9d\xfe\xf2\xad"\xae\x9e]\xf3\xdc\\/K\x16\x90\x1f\xa7.uWZgS*s\x9a\x82\xd1"y\xd4\x1e\xd7WA\xf2\xaey\x0e\xb3\x96\x98\xdc\xf1\xb5\xec\xea]I\xe7|\x88\x01&\x11:\x7f\xc4\x12\x8cj\x88\xa1\xe7\x1d\xa1.\xf6\x00\x00*\xc1\xf0\xe8 vb\x98\x00\x01\xab\x03\xce\x03\x00\x00Lh\xfac\xb1\xc4g\xfb\x02\x00\x00\x00\x00\x04YZ'     
    export_history: encoded_data=b'/Td6WFoAAATm1rRGAgAhARYAAAB0L+Wj4AHNAY9dAAAL5yUaFyCSuaZEdGQRRfVV0fK/5WAubeLwNSX/To0g1dbdTRwgBo1GZ6Iapx6FfmhGhJnxvwQ4CLje45YWOYMXA+LKELyMSbU1MpgONEC5XcLpLL+u2zZ8/RsxptYfdjinrFG7is3crQeMhsJVf4vxiaHuD7QcOCUXSFMqGwc8wa4rYYT4hQC+nhegwNVFIFc9ZhxZFaVF2rVeQ0rsqC7+coCn9DHWR8V3upulV0x9avVsnIhLu2jyTksBv1MGzjGmNk2IRelpfVDmFIpJNVmbLg5UesvW0dnfXuY/DlEyMhoVYqOw3DrGBZG9eHUCq7olOCXbOJlPt4qfK3tmvtYvOUnqD/MZSHvjijhj2bm7nRrjWSC/RouH08AuiWTF8xgDdp+oHFPgqY+bXFpbh/Vs+bNngBSXHPGv9H/5EJY+GPFaHPhmETkcS2md/vKtIq6eXfPcXC9LFpAfpy51V1pnUypzmoLRInnUHtdXQfKueQ6zlpjc8bXs6l1J53yIASYROn/EEoxqiKHnHaEu9gAAKsHw6CB2YpgAAasDzgMAAExo+mOxxGf7AgAAAAAEWVo='
    import_history: file_data=b'/Td6WFoAAATm1rRGAgAhARYAAAB0L+Wj4AHNAY9dAAAL5yUaFyCSuaZEdGQRRfVV0fK/5WAubeLwNSX/To0g1dbdTRwgBo1GZ6Iapx6FfmhGhJnxvwQ4CLje45YWOYMXA+LKELyMSbU1MpgONEC5XcLpLL+u2zZ8/RsxptYfdjinrFG7is3crQeMhsJVf4vxiaHuD7QcOCUXSFMqGwc8wa4rYYT4hQC+nhegwNVFIFc9ZhxZFaVF2rVeQ0rsqC7+coCn9DHWR8V3upulV0x9avVsnIhLu2jyTksBv1MGzjGmNk2IRelpfVDmFIpJNVmbLg5UesvW0dnfXuY/DlEyMhoVYqOw3DrGBZG9eHUCq7olOCXbOJlPt4qfK3tmvtYvOUnqD/MZSHvjijhj2bm7nRrjWSC/RouH08AuiWTF8xgDdp+oHFPgqY+bXFpbh/Vs+bNngBSXHPGv9H/5EJY+GPFaHPhmETkcS2md/vKtIq6eXfPcXC9LFpAfpy51V1pnUypzmoLRInnUHtdXQfKueQ6zlpjc8bXs6l1J53yIASYROn/EEoxqiKHnHaEu9gAAKsHw6CB2YpgAAasDzgMAAExo+mOxxGf7AgAAAAAEWVo='
    import_history: compressed_data=b'\xfd7zXZ\x00\x00\x04\xe6\xd6\xb4F\x02\x00!\x01\x16\x00\x00\x00t/\xe5\xa3\xe0\x01\xcd\x01\x8f]\x00\x00\x0b\xe7%\x1a\x17 \x92\xb9\xa6Dtd\x11E\xf5U\xd1\xf2\xbf\xe5`.m\xe2\xf05%\xffN\x8d \xd5\xd6\xddM\x1c \x06\x8dFg\xa2\x1a\xa7\x1e\x85~hF\x84\x99\xf1\xbf\x048\x08\xb8\xde\xe3\x96\x169\x83\x17\x03\xe2\xca\x10\xbc\x8cI\xb552\x98\x0e4@\xb9]\xc2\xe9,\xbf\xae\xdb6|\xfd\x1b1\xa6\xd6\x1fv8\xa7\xacQ\xbb\x8a\xcd\xdc\xad\x07\x8c\x86\xc2U\x7f\x8b\xf1\x89\xa1\xee\x0f\xb4\x1c8%\x17HS*\x1b\x07<\xc1\xae+a\x84\xf8\x85\x00\xbe\x9e\x17\xa0\xc0\xd5E W=f\x1cY\x15\xa5E\xda\xb5^CJ\xec\xa8.\xfer\x80\xa7\xf41\xd6G\xc5w\xba\x9b\xa5WL}j\xf5l\x9c\x88K\xbbh\xf2NK\x01\xbfS\x06\xce1\xa66M\x88E\xe9i}P\xe6\x14\x8aI5Y\x9b.\x0eTz\xcb\xd6\xd1\xd9\xdf^\xe6?\x0eQ22\x1a\x15b\xa3\xb0\xdc:\xc6\x05\x91\xbdxu\x02\xab\xba%8%\xdb8\x99O\xb7\x8a\x9f+{f\xbe\xd6/9I\xea\x0f\xf3\x19H{\xe3\x8a8c\xd9\xb9\xbb\x9d\x1a\xe3Y \xbfF\x8b\x87\xd3\xc0.\x89d\xc5\xf3\x18\x03v\x9f\xa8\x1cS\xe0\xa9\x8f\x9b\\Z[\x87\xf5l\xf9\xb3g\x80\x14\x97\x1c\xf1\xaf\xf4\x7f\xf9\x10\x96>\x18\xf1Z\x1c\xf8f\x119\x1cKi\x9d\xfe\xf2\xad"\xae\x9e]\xf3\xdc\\/K\x16\x90\x1f\xa7.uWZgS*s\x9a\x82\xd1"y\xd4\x1e\xd7WA\xf2\xaey\x0e\xb3\x96\x98\xdc\xf1\xb5\xec\xea]I\xe7|\x88\x01&\x11:\x7f\xc4\x12\x8cj\x88\xa1\xe7\x1d\xa1.\xf6\x00\x00*\xc1\xf0\xe8 vb\x98\x00\x01\xab\x03\xce\x03\x00\x00Lh\xfac\xb1\xc4g\xfb\x02\x00\x00\x00\x00\x04YZ'     
    import_history: decompressed_data=b'\x00,NlxKPLoEnHCY45LaZkb0yyyvj5oemOYlCraqWIW5Nbw=Z0FBQUFBQmxaRTZVU05HZTlac3VtaGo0cFFQV0dmYURtZVJjQ25VSW9iM1Q5cFo5aGRQVm5MMEZnY1BPUVI0bmpjeXdYVWUxa1hkb1NPNnBCekszTVlNcHlIYXBaTkFvRTgtRExZWXA5MHBlVjY4dXQtR01tdTI1MXI1b1RWLTdIeFhPekN0MnhWcHFNR3E3SWZKUlJTaU8tSm13S2hCVkVPVHFqMzBHSDVVYTBZeG9KWUhTZTZoLU0zMUdaNFhNanB3aml1dDBMY3JkeG1FZ3RrVGN1NzF1dXpHWWRfQUI5Wl9PZ3Voa1hIWmdDQndZaGRDYllURmRoWGx6OGFiRUdGWVB0M0p5NmRWWGNrU19TOXYwdTQ4MnVLdHR3Vlh5RzlUZWFjTzFZUnU1cnIzcGZqZWVWUW89'
    import_history: key_size_bytes=44
    import_history: key_base64='NlxKPLoEnHCY45LaZkb0yyyvj5oemOYlCraqWIW5Nbw='
    import_history: encrypted_data=b'Z0FBQUFBQmxaRTZVU05HZTlac3VtaGo0cFFQV0dmYURtZVJjQ25VSW9iM1Q5cFo5aGRQVm5MMEZnY1BPUVI0bmpjeXdYVWUxa1hkb1NPNnBCekszTVlNcHlIYXBaTkFvRTgtRExZWXA5MHBlVjY4dXQtR01tdTI1MXI1b1RWLTdIeFhPekN0MnhWcHFNR3E3SWZKUlJTaU8tSm13S2hCVkVPVHFqMzBHSDVVYTBZeG9KWUhTZTZoLU0zMUdaNFhNanB3aml1dDBMY3JkeG1FZ3RrVGN1NzF1dXpHWWRfQUI5Wl9PZ3Voa1hIWmdDQndZaGRDYllURmRoWGx6OGFiRUdGWVB0M0p5NmRWWGNrU19TOXYwdTQ4MnVLdHR3Vlh5RzlUZWFjTzFZUnU1cnIzcGZqZWVWUW89'
    import_history: decrypted_data=b'[{"sender": "User", "content": "Hi!", "timestamp": "2023-11-27T01:54:01.430667"}, {"sender": "User", "content": "HRU?", "timestamp": "2023-11-27T01:54:03.654174"}]'
    import_history: data=[{'sender': 'User', 'content': 'Hi!', 'timestamp': '2023-11-27T01:54:01.430667'}, {'sender': 'User', 'content': 'HRU?', 'timestamp': '2023-11-27T01:54:03.654174'}]
    
    

    and the whole code:

    import lzma
    from cryptography.fernet import Fernet
    import base64
    import json
    
    def export_history(key, filename='chat_history.json.enc'):
        cipher = Fernet(key) 
        print(f"export_history: {key=}")
        encrypted_data = base64.b64encode(cipher.encrypt(json.dumps([msg for msg in messages]).encode())) 
        print(f"export_history: {encrypted_data=}")
        
        key_size_in_bytes = len(key)
        print(f"export_history: {key_size_in_bytes=}")
        key_size_in_bytes_repr = key_size_in_bytes.to_bytes(2, byteorder='big')
        print(f"export_history: {key_size_in_bytes_repr=}")
        
        file_data = key_size_in_bytes_repr + key + encrypted_data
        print(f"export_history: {file_data=}")
        compressed_data = lzma.compress(file_data) 
        print(f"export_history: {compressed_data=}")
        encoded_data = base64.b64encode(compressed_data) 
        print(f"export_history: {encoded_data=}")
    
        
        with lzma.open(filename, 'wb') as file: 
            file.write(encoded_data) 
    
    
    def import_history( filename='chat_history.json.enc'):
        try:
            
            with lzma.open(filename, 'rb') as file: 
                
                file_data = file.read()
                print(f"import_history: {file_data=}")
                compressed_data = base64.b64decode(file_data) 
                print(f"import_history: {compressed_data=}")
                decompressed_data = lzma.decompress(compressed_data) 
                print(f"import_history: {decompressed_data=}")
                
                key_size_bytes = int.from_bytes(decompressed_data[:2], byteorder='big')
                print(f"import_history: {key_size_bytes=}")
                
                key_base64 = decompressed_data[2:key_size_bytes+2].decode('utf-8')
                print(f"import_history: {key_base64=}")
                
                encrypted_data = decompressed_data[key_size_bytes+2:]
                print(f"import_history: {encrypted_data=}")
                
                base64_decoded_encrypted_data = base64.b64decode(encrypted_data)
                
                cipher = Fernet(key_base64)  
                decrypted_data = cipher.decrypt(base64_decoded_encrypted_data) 
                print(f"import_history: {decrypted_data=}")
                data = json.loads(decrypted_data.decode())
                print(f"import_history: {data=}")
                    
    
        except FileNotFoundError:
            pass
    
    
    def main():
        key = Fernet.generate_key()
        
        export_history(key)
        import_history()
    
    messages = [{"sender": "User", "content": "Hi!", "timestamp": "2023-11-27T01:54:01.430667"}, {"sender": "User", "content": "HRU?", "timestamp": "2023-11-27T01:54:03.654174"}]
    
    if __name__ == '__main__':
        main()
    

    PS: in my code I use Fernet.generate_key() which just return base64.urlsafe_b64encode(os.urandom(32))