@@ -2,7 +2,8 @@ Encrypted Model Fields
22======================
33
44If your ``custom_code `` or reusable app contains a model field storing
5- user-specific sensitive data, you may want to encrypt that data at rest.
5+ user-specific sensitive data, TOM Toolkit provides a way to encrypt
6+ that data in in the database ("at rest").
67
78Examples of user-specific sensitive data include passwords or API keys
89for external services that your TOM uses on the user's behalf. TOM
@@ -16,18 +17,15 @@ How encryption works
1617--------------------
1718
1819Encrypted fields are protected by a single Fernet cipher derived from
19- ``settings.SECRET_KEY `` using HKDF (RFC 5869) with a domain-separator
20- label. There is no per-user key material and no additional environment
21- variable to manage. For the operator-side concerns (rotating
20+ ``settings.SECRET_KEY ``. (For TOM administrator concerns (rotating
2221``SECRET_KEY `` without losing data, etc.) see
23- :doc: `/deployment/encryption `.
22+ :doc: `/deployment/encryption `) .
2423
2524.. note ::
2625
2726 Encryption protects data from passive database exposure. It does NOT
28- protect against a server administrator with access to ``SECRET_KEY ``
29- — by design, since the same admin needs to be able to run the
30- ``rotate_encryption_key `` command. If you need user-level isolation
27+ protect against a server administrator with access to the
28+ ``settings.SECRET_KEY ``. If you need user-level isolation
3129 from administrators, the toolkit's current scheme is not sufficient.
3230
3331Adding an encrypted field to a model
@@ -45,19 +43,21 @@ encryption on write and decryption on read.
4543
4644
4745 class MyAppProfile (models .Model ):
48- user = models.OneToOneField(settings.AUTH_USER_MODEL ,
49- on_delete = models.CASCADE )
46+ # you probably have a user OneToOneField here
47+
48+ # this is the encryption part:
5049 _api_key_encrypted = models.BinaryField(null = True , blank = True ) # ciphertext (private)
5150 api_key = EncryptedProperty(' _api_key_encrypted' ) # descriptor (public)
5251
5352 By convention, the ``BinaryField ``'s name starts with an underscore —
54- its only consumer is the :class: `EncryptedProperty ` descriptor; plugin
55- code should never read or write it directly.
53+ it is only referenced by the :class: `EncryptedProperty ` descriptor; never
54+ read or write the `` BinaryField `` directly.
5655
5756Reading and writing the field
5857-----------------------------
5958
60- Plain Python attribute access on the descriptor handles everything:
59+ Access the encrypted field just like a regular Python attribute.
60+ The ``EncryptedProperty `` descriptor handles everything:
6161
6262.. code-block :: python
6363
@@ -67,23 +67,27 @@ Plain Python attribute access on the descriptor handles everything:
6767 # later, possibly in a different process / request:
6868 value = profile.api_key # 'something-secret'
6969
70- On assignment, the descriptor calls
70+
71+ Some explanations
72+ -----------------
73+
74+ That (above) is really all you need to know. However, here's what's
75+ going on "under the hood": On assignment, the descriptor calls
7176:func: `tom_common.encryption.encrypt `, which builds a Fernet cipher
7277from ``settings.SECRET_KEY `` and encrypts the value, then stores the
7378ciphertext bytes in the underlying ``BinaryField ``. On read, the
7479descriptor calls :func: `tom_common.encryption.decrypt `, which
75- transparently honours ``settings.SECRET_KEY_FALLBACKS `` (see
76- :doc: `/deployment/encryption ` for the rotation procedure).
80+ transparently honours ``settings.SECRET_KEY_FALLBACKS ``. However,
81+ that's really just for admins to worry about (see
82+ :doc: `/deployment/encryption ` for admin/deployment concerns).
7783
7884An empty string assignment clears the ciphertext (stores ``None `` in
7985the ``BinaryField ``). Reading an unset / empty field yields ``'' ``,
8086not ``None `` — so consumers don't need to special-case the empty case.
8187
82- Some explanations
83- -----------------
8488
8589:class: `EncryptedProperty ` (`source <https://github.com/TOMToolkit/tom_base/blob/dev/tom_common/models.py >`__)
86- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
90+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8791
8892A *property descriptor * implementing the Python descriptor protocol
8993(``__get__ ``, ``__set__ ``, ``__set_name__ ``). It handles the details of
0 commit comments