How to decrypt a value on a different service that it was originally encrypted with Fernet?

Viewed 649

I'm playing around with a project that is python backend-based. I'll have Django for the "core" stuff and FastAPI for some crawlers. I'm encrpting some data to the DB with Django using the Fernet module and a custom Field.

class EncryptedField(models.CharField):
    description = "Save encrypted data to DB an read as string on application level."

    def __init__(self, *args, **kwargs):
        kwargs["max_length"] = 1000
        super().__init__(*args, **kwargs)

    @cached_property
    def fernet(self) -> Fernet:
        return Fernet(key=settings.FERNET_KEY)

    def get_internal_type(self) -> str:
        return "BinaryField"

    def get_db_prep_save(
        self, value: Any, connection: BaseDatabaseWrapper
    ) -> Union[memoryview, None]:
        value = super().get_db_prep_save(value, connection)
        if value is not None:
            encrypted_value = self.fernet.encrypt(data=force_bytes(s=value))
            return connection.Database.Binary(encrypted_value)

    def from_db_value(self, value: bytes, *args) -> Union[str, None]:
        if value is not None:
            decrypted_value = self.fernet.decrypt(token=force_bytes(s=value))
            return self.to_python(value=force_str(s=decrypted_value))

Everything work as expected, the problem is when I try to decrypt the value on FastAPI side:

def decrypt(value: bytes):
    return Fernet(FERNET_KEY).decrypt(token=value)

Some important information:

  • I double check and settings.FERNET_KEY == FERNET_KEY, ie, I'm using the same key on both sides.
  • Both services share the same DB and the function are receiving different values when reading for it.
    • Django -> from_db_value -> value -> b"gAAAAABhSm94ADjyQES3JL-EiEX4pH2odwJnJe2qsuGk_K685vseoVNN6kuoF9CRdf2GxiIViOgiKVcZMk5olg7FrJL2cmMFvg=="
    • FastAPI -> user.encrypted_field -> value -> b"pbkdf2_sha256$260000$RzIJ5Vg3Yx8JTz4y5ZHttZ$0z9CuQiPCJrBZqc/5DvxiEcbNHZpu8hAZgmibAe7nrQ=". I actually enter inside the DB and checked that this is the value stored there. user comes from here:
      • from sqlmodel import Session, select
        
        from .models import User
        
        
        async def get_user(db: Session, username: str) -> str:
           statement = select(User).where(User.username == username)
           return db.exec(statement).first()
        

So I'm wondering there's something before from_db_value that's converting the value somehow?!

One final alternative would be to decrypt the value on Django and send it directly to FastAPI, but I'd prefer not to do so.

How can I decrypt the value on FastAPI?

2 Answers

It is enough to decode the value:

def decrypt(value: bytes):
    return Fernet(FERNET_KEY).decrypt(token=value)

Are you sure you are trying to decrypt the same data? To me, this looks like an encrypted password field and some other field.

mysql> select first_name, password from users_user where login='test'
+------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------+
| first_name                                                                                           | password                                                                                 |
| gAAAAABhSm94ADjyQES3JL-EiEX4pH2odwJnJe2qsuGk_K685vseoVNN6kuoF9CRdf2GxiIViOgiKVcZMk5olg7FrJL2cmMFvg== | pbkdf2_sha256$260000$RzIJ5Vg3Yx8JTz4y5ZHttZ$0z9CuQiPCJrBZqc/5DvxiEcbNHZpu8hAZgmibAe7nrQ= |

In FastAPI you don't force bytes.

def decrypt(value: bytes):
    return Fernet(FERNET_KEY).decrypt(token=value)

#instead do
def decrypt(value: bytes):
    return Fernet(FERNET_KEY).decrypt(token=force_bytes(s=value))

Then the decryption function on Django and FastAPI will look the same. force_bytes might be the place where data is getting changed.

I would recommend you check the force bytes function. Show us the code in force bytes function if possible.

Related