Field encryption
Encrypt sensitive fields like SSN or credit cards using AWS KMS.
Key features
- Per-field encryption with KMS
- Three modes: ReadWrite, WriteOnly, ReadOnly
- Encryption context for extra security
- Automatic encrypt on save, decrypt on load
Getting started
Basic usage
Add EncryptedAttribute to fields that need encryption:
"""Basic field encryption example."""
from pydynox import Model, ModelConfig
from pydynox.attributes import EncryptedAttribute, StringAttribute
class User(Model):
model_config = ModelConfig(table="users")
pk = StringAttribute(hash_key=True)
email = StringAttribute()
ssn = EncryptedAttribute(key_id="alias/my-app-key")
# Create a user with sensitive data
user = User(
pk="USER#123",
email="john@example.com",
ssn="123-45-6789",
)
user.save()
# The SSN is encrypted in DynamoDB as "ENC:base64data..."
# When you read it back, it's decrypted automatically
loaded = User.get(pk="USER#123")
print(loaded.ssn) # "123-45-6789"
The field is encrypted before saving to DynamoDB. When you read it back, it's decrypted automatically. In DynamoDB, the value looks like ENC:base64data....
Encryption modes
Not all services need both encrypt and decrypt. A service that only writes data shouldn't be able to read it back. Use modes to control this:
| Mode | Can encrypt | Can decrypt | Use case |
|---|---|---|---|
ReadWrite |
✓ | ✓ | Full access (default) |
WriteOnly |
✓ | ✗ (returns encrypted) | Ingest services |
ReadOnly |
✗ (returns plaintext) | ✓ | Report services |
Import EncryptionMode from pydynox.attributes:
"""Encryption modes example."""
from pydynox import Model, ModelConfig
from pydynox.attributes import EncryptedAttribute, EncryptionMode, StringAttribute
# Write-only service: can encrypt, cannot decrypt
class IngestService(Model):
model_config = ModelConfig(table="users")
pk = StringAttribute(hash_key=True)
ssn = EncryptedAttribute(
key_id="alias/my-app-key",
mode=EncryptionMode.WriteOnly,
)
# Read-only service: can decrypt, cannot encrypt
class ReportService(Model):
model_config = ModelConfig(table="users")
pk = StringAttribute(hash_key=True)
ssn = EncryptedAttribute(
key_id="alias/my-app-key",
mode=EncryptionMode.ReadOnly,
)
# Full access (default): can encrypt and decrypt
class AdminService(Model):
model_config = ModelConfig(table="users")
pk = StringAttribute(hash_key=True)
ssn = EncryptedAttribute(key_id="alias/my-app-key")
If you try to decrypt in WriteOnly mode, you get an EncryptionError. Same for encrypting in ReadOnly mode.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
key_id |
str | Required | KMS key ID, ARN, or alias |
mode |
EncryptionMode | ReadWrite | Controls encrypt/decrypt access |
region |
str | None | AWS region (uses env default) |
context |
dict | None | Encryption context for extra security |
Advanced
Encryption context
KMS supports encryption context - extra key-value pairs that must match on decrypt. If someone tries to decrypt with a different context, it fails.
"""Encryption context example."""
from pydynox import Model, ModelConfig
from pydynox.attributes import EncryptedAttribute, StringAttribute
class User(Model):
model_config = ModelConfig(table="users")
pk = StringAttribute(hash_key=True)
ssn = EncryptedAttribute(
key_id="alias/my-app-key",
context={"tenant": "acme-corp", "purpose": "pii"},
)
# The context is passed to KMS on encrypt/decrypt.
# If the context doesn't match, decryption fails.
# This adds an extra layer of security.
This is useful for:
- Multi-tenant apps - Include tenant ID in context
- Audit - Context is logged in CloudTrail
- Extra validation - Ensure data is decrypted in the right context
How it works
- On save, the attribute calls KMS
Encryptwith your plaintext - KMS returns ciphertext encrypted with your key
- The ciphertext is base64-encoded and stored with
ENC:prefix - On read, the attribute detects the prefix and calls KMS
Decrypt - KMS returns the original plaintext
All encryption happens in Rust for speed. The KMS client is created lazily on first use.
Storage format
Encrypted values are stored as:
Values without the ENC: prefix are treated as plaintext. This means you can add encryption to existing fields - old unencrypted values still work.
Limitations
- AWS credentials from environment - Uses the default credential chain (env vars, IAM role, etc.). You cannot pass credentials directly.
- Region from environment - Uses
AWS_REGIONorAWS_DEFAULT_REGIONenv var by default. You can override with theregionparameter. - Strings only - Only encrypts string values. For other types, convert to string first.
- No key rotation - If you rotate your KMS key, old data still decrypts (KMS handles this), but you need to re-encrypt to use the new key.
IAM permissions
Your service needs these KMS permissions:
{
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt"
],
"Resource": "arn:aws:kms:us-east-1:123456789:key/your-key-id"
}
For WriteOnly mode, you only need kms:Encrypt. For ReadOnly, only kms:Decrypt.
Error handling
Encryption errors raise EncryptionError:
from pydynox.exceptions import EncryptionError
try:
user.save()
except EncryptionError as e:
print(f"Encryption failed: {e}")
Common errors:
| Error | Cause |
|---|---|
| KMS key not found | Wrong key ID or alias |
| Access denied | Missing IAM permissions |
| Cannot encrypt in ReadOnly mode | Wrong mode for operation |
| Cannot decrypt in WriteOnly mode | Wrong mode for operation |