Search code examples
pythondjangoencryption

unable to decode encrypted bytes data stored in db


I have written 2 functions in python, one for encrypting data and other for decrypting data which are as follows.

from app.settings import DATA_KEY
import logging
from cryptography.hazmat.primitives.ciphers.aead import AESSIV
import base64

cipher_suite = AESSIV(DATA_KEY)

error_logger = logging.getLogger("error_logger")


def encrypt_field(input_text):
    try:
        if isinstance(input_text, str):
            input_text = input_text.encode()
        enc_text = cipher_suite.encrypt(input_text, associated_data = None)
        print(f"enc_text is {enc_text}")
        enc_text_base_64 = base64.b64encode(enc_text)
        print(f"encrypted_text is {enc_text_base_64}")
        return enc_text_base_64
    
    except Exception as e:
        error_logger.error(f"exception in encrypt field {str(e)} on field {input_text}")
        return input_text


def decrypt_field(input_text):
    try:
        print(f"input in decrypt field is {input_text} its type is {type(input_text)}")
        enc_text = base64.b64decode(input_text)
        print(f"enc text is {enc_text}")
        decrypted_field = cipher_suite.decrypt(enc_text, associated_data = None)
        print(f"decrypted_field is {decrypted_field}")
        return decrypted_field.decode('utf-8')
    
    except Exception as e:
        print(e)
        error_logger.error(f"exception in decrypt_field {e} on field {input_text}")
        return input_text
    
    

I am able to encrypt and store the data in db, however upon reading the data from db and trying to decrypt it, I am getting a utf-8 bytestring instead of original plaintext string back. What am I doing wrong here? My Django version is 5.0, Python version is 3.10.11 and Database is PostgreSQL.

This is how I am trying to decrypt the values after reading the data using queryset

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = get_user_model()
        
        fields = ['username', 'email']

    def get_username(self, obj):
        return decrypt_field(obj.username)
    
    def get_email(self, obj):
        return decrypt_field(obj.email)

and this is my model

class User(AbstractUser):
    username = models.CharField(max_length=300, unique=True)
    first_name = models.CharField(null=True, blank=True)
    middle_name = models.CharField(max_length=100, null=True, blank=True)
    last_name = models.CharField(max_length=100, null=True, blank=True)
    phone = models.CharField(max_length=50, null=True, blank=True)
    password = models.CharField(max_length=150, null=True, blank=True)
    password_reset_required = models.SmallIntegerField(null=True, blank=True)
    username_field = "email"

on calling the decrypt function instead of original email and username that I've supplied

[email protected]

I instead get

"b'CSPex4eChMQousC2Sn5JBCvge6j2X73iiQIZ5O5l9d2AliVBWiPGoGe5pKVTzEVAlIYcBxbe+1gufGXhnIQKCQ=='"

I have tried storing the data in BinaryField as well TextField of Django models, by adding additional fields to model wit binary and TextField as datatype. but to no avail the result is same. I have tried storing data as encoded byte data without base64encde as well but it cannot be decoded later and results in undefined character error.


Solution

  • I think you can simply simplify your implementation:

    import base64
    
    from cryptography.hazmat.primitives.ciphers.aead import AESSIV
    
    DATA_KEY = b"ABCDEFGH" * 4  # VERY SECRET!
    cipher_suite = AESSIV(DATA_KEY)
    
    
    def encrypt_text_to_base64(input_text: str) -> str:
        if not isinstance(input_text, str):
            raise TypeError("Input must be text")
        enc_text = cipher_suite.encrypt(input_text.encode("utf-8"), associated_data=None)
        return base64.b64encode(enc_text).decode("ascii")
    
    
    def decode_base64_to_text(input_text: str | bytes) -> str:
        return cipher_suite.decrypt(
            base64.b64decode(input_text), associated_data=None
        ).decode("utf-8")
    
    
    plain = "Hello, world!"
    encrypted = encrypt_text_to_base64(plain)
    encrypted_bytes = encrypted.encode("utf-8")
    decrypted = decode_base64_to_text(encrypted)
    decrypted_from_bytes = decode_base64_to_text(encrypted_bytes)
    
    print(f"{plain=}")
    print(f"{encrypted=}")
    print(f"{encrypted_bytes=}")
    print(f"{decrypted=}")
    print(f"{decrypted_from_bytes=}")
    
    
    try:
        print(decode_base64_to_text("f" + encrypted[1:]))
    except Exception as e:
        print("Failed to decrypt tampered data:", repr(e))
    
    

    This prints out

    plain='Hello, world!'
    encrypted='1mtatTQy3/GRFv0tUd3Mp66V8uDbeKDqn8rKytU='
    encrypted_bytes=b'1mtatTQy3/GRFv0tUd3Mp66V8uDbeKDqn8rKytU='
    decrypted='Hello, world!'
    decrypted_from_bytes='Hello, world!'
    Failed to decrypt tampered data: InvalidTag()
    

    so you can see the decryption function works for both base64-in-bytes and base64-as-a-string.