FossilRepo

fossilrepo / core / fields.py
Source Blame History 42 lines
c588255… ragelink 1 """Custom model fields — encrypted storage using Fernet symmetric encryption."""
c588255… ragelink 2
c588255… ragelink 3 import base64
c588255… ragelink 4 import hashlib
c588255… ragelink 5
c588255… ragelink 6 from cryptography.fernet import Fernet, InvalidToken
c588255… ragelink 7 from django.conf import settings
c588255… ragelink 8 from django.db import models
c588255… ragelink 9
c588255… ragelink 10
c588255… ragelink 11 def _get_fernet():
c588255… ragelink 12 """Derive a Fernet key from Django's SECRET_KEY."""
c588255… ragelink 13 key_bytes = hashlib.sha256(settings.SECRET_KEY.encode()).digest()
c588255… ragelink 14 return Fernet(base64.urlsafe_b64encode(key_bytes))
c588255… ragelink 15
c588255… ragelink 16
c588255… ragelink 17 class EncryptedTextField(models.TextField):
c588255… ragelink 18 """TextField that encrypts data at rest using Fernet (AES-128-CBC + HMAC).
c588255… ragelink 19
c588255… ragelink 20 Values are transparently encrypted on save and decrypted on read.
c588255… ragelink 21 Stored as base64-encoded ciphertext in the database.
c588255… ragelink 22 """
c588255… ragelink 23
c588255… ragelink 24 def get_prep_value(self, value):
c588255… ragelink 25 if value is None or value == "":
c588255… ragelink 26 return value
c588255… ragelink 27 f = _get_fernet()
c588255… ragelink 28 return f.encrypt(value.encode("utf-8")).decode("utf-8")
c588255… ragelink 29
c588255… ragelink 30 def from_db_value(self, value, expression, connection):
c588255… ragelink 31 if value is None or value == "":
c588255… ragelink 32 return value
c588255… ragelink 33 f = _get_fernet()
c588255… ragelink 34 try:
c588255… ragelink 35 return f.decrypt(value.encode("utf-8")).decode("utf-8")
c588255… ragelink 36 except InvalidToken:
c588255… ragelink 37 # Value may not be encrypted (e.g. pre-existing data).
c588255… ragelink 38 return value
c588255… ragelink 39
c588255… ragelink 40 def deconstruct(self):
c588255… ragelink 41 name, path, args, kwargs = super().deconstruct()
c588255… ragelink 42 return name, path, args, kwargs

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button