PII Encryption¶
AMSDAL provides built-in field-level encryption for Personally Identifiable Information (PII). Mark sensitive fields with the PIIStr type, and AMSDAL automatically encrypts values before writing to the database and decrypts them on demand when reading.
Data is encrypted using AES-256-GCM with envelope encryption backed by AWS KMS — the same approach used by AWS services themselves.
from amsdal.models import Model, PIIStr
from pydantic import Field
class Customer(Model):
name: str
email: PIIStr = Field(title='email')
phone: PIIStr | None = Field(default=None, title='phone')
With this definition:
emailandphoneare stored encrypted in the database- On read, they return encrypted ciphertext by default
- To get plaintext values, explicitly request decryption via QuerySet or REST API
How It Works¶
┌─────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Your App │ ──── │ AMSDAL │ ──── │ PII Cryptor │
│ (Model) │ │ (Framework) │ │ (Service) │
└─────────────┘ └──────────────────┘ └──────────────────┘
│
┌───────┴────────┐
│ AWS KMS │
└────────────────┘
- You define model fields using
PIIStr - On save, AMSDAL detects PII fields and sends their values to the PII Cryptor service for encryption before writing to the database
- On read, encrypted values are returned as-is unless you explicitly request decryption
- The PII Cryptor service uses envelope encryption: AWS KMS generates a data encryption key, the service encrypts values with AES-256-GCM, and only the encrypted key is stored alongside the ciphertext
This means plaintext PII never exists in your database — even if the database is compromised, the data remains protected.
Defining PII Fields¶
Use PIIStr as the field type for any string field containing sensitive data:
from amsdal.models import Model, PIIStr
from pydantic import Field
class User(Model):
username: str
email: PIIStr = Field(title='email')
ssn: PIIStr | None = Field(default=None, title='ssn')
PIIStr is a regular str with metadata that tells AMSDAL to encrypt/decrypt it. You can use it anywhere you'd use str — required fields, optional fields, with defaults.
Detecting PII Fields Programmatically¶
from amsdal.models import get_pii_fields
fields = get_pii_fields(User)
# ['email', 'ssn']
Encryption and Decryption¶
Encryption — Automatic on Save¶
PII fields are encrypted automatically when saving objects. No extra code needed:
user = User(username='alice', email='alice@example.com', ssn='123-45-6789')
await User.objects.bulk_acreate([user])
# email and ssn are encrypted before hitting the database
In the database, the values look like:
#01#<encrypted_key>:<iv>:<ciphertext>:<tag>#10#
Decryption — Opt-in on Read¶
By default, PII fields return encrypted ciphertext. To get plaintext values, chain .decrypt_pii() on the QuerySet:
# Encrypted values (default)
users = await User.objects.aexecute()
print(users[0].email) # #01#abc123:def456:ghi789:jkl000#10#
# Decrypted values
users = await User.objects.decrypt_pii().aexecute()
print(users[0].email) # alice@example.com
This is intentional — decryption requires a call to the PII Cryptor service and ultimately to AWS KMS, so it only happens when you explicitly need the plaintext.
REST API¶
All object endpoints support the decrypt_pii query parameter:
GET /api/objects/?class_name=User&decrypt_pii=true
GET /api/objects/{address}/?decrypt_pii=true
POST /api/objects/?decrypt_pii=true
PATCH /api/objects/{address}/?decrypt_pii=true
When decrypt_pii=true, the response includes decrypted PII field values. When omitted or false, encrypted ciphertext is returned.
Configuration¶
AMSDAL Cloud¶
When deploying to AMSDAL Cloud, PII encryption works out of the box — no configuration needed. The AMSDAL_PII_CRYPTOR_BASE_URL and AMSDAL_PII_CRYPTOR_CLIENT_ID are set automatically during deployment.
By default, all deployed applications use shared encryption keys. If you need a dedicated KMS key for your application (e.g. for compliance or data isolation requirements), contact us and we'll provision one for you.
Self-Hosted / Local Development¶
When running outside AMSDAL Cloud, the PII Cryptor service connection is configured via environment variables:
| Variable | Description | Default |
|---|---|---|
AMSDAL_PII_CRYPTOR_BASE_URL |
URL of the PII Cryptor service | "" |
AMSDAL_PII_CRYPTOR_CLIENT_ID |
Client identifier (for per-client KMS keys) | "" |
# .env
AMSDAL_PII_CRYPTOR_BASE_URL=https://pii-cryptor.internal:8000
AMSDAL_PII_CRYPTOR_CLIENT_ID=my-app
When AMSDAL_PII_CRYPTOR_BASE_URL is empty, AMSDAL uses a fake crypto service that wraps values with #01#...#10# markers without real encryption. This is useful for local development without an AWS setup.
Security Architecture¶
Envelope Encryption¶
The PII Cryptor service implements envelope encryption:
- Encrypt: AWS KMS generates a unique Data Encryption Key (DEK) per request. Values are encrypted with AES-256-GCM using this DEK. The DEK itself is encrypted by KMS and stored alongside the ciphertext.
- Decrypt: The encrypted DEK is sent to KMS for decryption, then used to decrypt the values locally.
This means AWS KMS never sees your actual data — only the encryption keys.
AES-256-GCM¶
Each value is encrypted with:
- AES-256 — 256-bit symmetric encryption
- GCM mode — authenticated encryption that detects tampering
- Random IV — unique initialization vector per value, even within the same request
Key Management¶
- Encryption keys are managed by AWS KMS — you never handle raw key material
- Supports per-client KMS keys for multi-tenant isolation via the
X-CLIENT-IDheader - AWS KMS handles automatic key rotation (every 365 days), retaining old key versions for decryption
What's Protected¶
| Layer | Protection |
|---|---|
| Database | PII stored as encrypted ciphertext |
| Application | Decryption only happens on explicit request |
| Transport | HTTPS between service and KMS |
| Key storage | Keys managed by AWS KMS, never stored in plaintext |