CodamAIDocs
Topicdone

Encrypted fields

A text field with the rule "encrypted" is stored encrypted in the database and comes back decrypted. What you configure for it, how a key change works and what the field cannot do.

Variants
create and change: store encryptedread, search, history: answer decryptedkey and previous keysvalues still stored as plain textfilter and sorting refusednot together with "unique"

What this is about

Some values should not be readable in the database, such as an IBAN or a tax number. Whoever gets hold of the database or a backup should not be able to use them.

For that you set the rule encrypted (@encrypted) on the text field in the hub. CDMS then encrypts the value with AES-256-GCM before storing it and decrypts it when reading. The client notices nothing: it sends plain text and gets plain text back.

The path of a value

sequenceDiagram
    participant C as Client
    participant D as CDMS
    participant DB as Database
    C->>D: POST iban = "DE44 5001 …"
    D->>D: check the rules (on the plain text)
    D->>D: encrypt, new random IV
    D->>DB: stores "enc:v1:fdd5117f:InOkKngP…"
    D->>DB: reads the object for the response
    D->>D: decrypt
    D-->>C: 200, iban = "DE44 5001 …"

The stored form always starts with enc:v1:, followed by the id of the key, then the ciphertext. Every encryption takes a new random value (IV). Two equal IBANs are therefore stored differently.

The encryption is done by a field hook that ships with CodamAI: EncryptedFieldHook from the module cdms-field-encryption. You write no code for it. The generator attaches the hook to every field with the rule, and the project frame adds the module to the pom.xml. If the field has field hooks of its own, they see the plain text: encryption is the last step before the database.

Setting the key

The application needs a key of 32 random bytes, written in Base64:

openssl rand -base64 32
Environment variablePropertyMeaning
CODAMAI_CDMS_ENCRYPTION_KEYcodamai.cdms.encryption.keythe active key every value is written with
CODAMAI_CDMS_ENCRYPTION_PREVIOUS_KEYScodamai.cdms.encryption.previous-keysearlier keys, comma separated, only read

The key belongs in a secret of your environment, never in the model and never in the repository. Only an application in which a field is encrypted needs it. There it does not start without it:

field-encryption-key-missing|codamai.cdms.encryption.key: a model encrypts a field (@encrypted), …

A key that is not 32 bytes long stops the start as well (field-encryption-key-invalid).

Changing the key

  1. 1
    Operations
    creates a new key
  2. 2
    Operations
    sets it as CODAMAI_CDMS_ENCRYPTION_KEY and moves the old one to CODAMAI_CDMS_ENCRYPTION_PREVIOUS_KEYS
  3. 3
    CDMS
    reads old values with the old key, recognised by its id in the stored value
  4. 4
    CDMS
    encrypts every value that is written again with the new key

Remove an old key only once no value is stored with it any more. If the key of a stored value is missing, CDMS does not answer it but responds with 500 field-encryption-key-unknown|<id>.

Values still stored as plain text

If you make a field encrypted later, its existing values are still plain text in the database. CDMS recognises them by the missing enc:v1: and answers them unchanged. The next time the field is written, the value is encrypted. A write that does not send the field, such as a PATCH of another field, leaves it as it is.

What an encrypted field cannot do

Does this work with an encrypted field?
AttemptAnswer
read the field, also in a list or in the historyyes, decrypted
write the field (POST, PUT, PATCH)yes, CDMS encrypts
filter by the field400 field-not-searchable|<field>
sort by the field400 field-not-searchable|<field>
also make the field "unique"not possible: the hub refuses it, the generator stops
encrypt a number or date fieldnot possible: the rule exists on text fields only

Filters and sorting work in the database, and therefore on the stored form. That form looks different for every value. A filter would never find anything, and a sort order would be random. Instead of answering wrong, CDMS refuses both. For the same reason the database cannot check uniqueness.

Pitfalls

Where to go next

Sources in the code and the knowledge base
  • CDMS/cdms-field-encryption – EncryptedFieldHook (AES/GCM, MARKER, KEY, PREVIOUS_KEYS, storedLength)
  • CDMS/cdms-generator – DtoMetaProcessor.fieldHooks (ENCRYPTION_HOOK), EntityProcessor (columnLength, isLob)
  • CDMS/cdms-scaffold – CdmsSystemContext (detectEncryptedFields), cdms-version-registry.yaml (encryptionDependencies), CdmsScaffoldService (.env.example)
  • CDMS/cdms-system-layer – SimpleFieldRoles.assertSearchable, HookManagementSystem (isSearchable, callFieldHooksAfterRead), AbstractLayer.queryHistory
  • CDMS/frontend – shared/utils/cdmsFieldRules (ENCRYPTED_RULE, fieldRulesError)
  • CDMS/cdms-integrationtest – AbstractEncryptedFieldTest, model secret (iban)
  • CDMS/cdms-system-layer/docs/adr – ADR-018
  • documentation/40-sicherheit/05-feldverschluesselung.md
Search