Field-Level Encryption
Encrypt specific fields in your entities using the [Encrypted] attribute.
Overview
Field-level encryption lets you encrypt individual properties on your entities. Encrypted fields are stored as binary data in DynamoDB and decrypted transparently when entities are loaded.
Marking Fields for Encryption
Use the [Encrypted] attribute on any property that should be encrypted at rest:
[DynamoDbTable("customers")]
public partial class Customer
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string CustomerId { get; set; } = string.Empty;
[DynamoDbAttribute("name")]
public string Name { get; set; } = string.Empty;
[Encrypted]
[Sensitive]
[DynamoDbAttribute("ssn")]
public string SocialSecurityNumber { get; set; } = string.Empty;
[Encrypted]
[Sensitive]
[DynamoDbAttribute("creditCard")]
public string CreditCardNumber { get; set; } = string.Empty;
}
Transparent Encryption and Decryption
Once configured, encryption and decryption happen automatically during Put and Get operations:
// Data is encrypted before writing to DynamoDB
await table.Customers.PutAsync(customer);
// Data is decrypted after reading from DynamoDB
var loaded = await table.Customers.GetAsync("customer123");
Console.WriteLine(loaded.SocialSecurityNumber); // Decrypted value
Decryption Failure Modes
By default, any decryption failure throws an exception. You can configure the library to skip encrypted fields that fail to decrypt, which is useful for STS downscoping scenarios where a service has reduced KMS permissions.
DecryptionFailureMode Enum
| Value | Behavior |
|---|---|
Throw (default) | Any decryption failure throws an exception |
SkipFields | Recoverable failures (no encryptor, access denied) leave the property at its CLR default and log a warning. Integrity failures (corrupted ciphertext, wrong key) always throw. |
Configuration
using Oproto.FluentDynamoDb;
using Oproto.FluentDynamoDb.Providers.Encryption;
// Skip encrypted fields when decryption fails (e.g., access denied)
var options = new FluentDynamoDbOptions()
.WithEncryption(encryptor)
.WithDecryptionFailureMode(DecryptionFailureMode.SkipFields);
var table = new CustomersTable(client, "customers", options);
Read-Only Access Without Encryptor
When a service only needs non-encrypted fields:
// No encryptor configured — encrypted fields are skipped
var readOnlyOptions = new FluentDynamoDbOptions()
.WithDecryptionFailureMode(DecryptionFailureMode.SkipFields);
var table = new CustomersTable(client, "customers", readOnlyOptions);
// Non-encrypted fields load normally; encrypted fields stay at CLR default
var customer = await table.Customers.GetAsync("customer123");
Console.WriteLine(customer.Name); // Populated normally
Console.WriteLine(customer.SocialSecurityNumber); // "" (CLR default)
Write Behavior
Write operations (ToDynamoDbAsync) are never affected by this setting — they always throw on failure to prevent silent data loss.
Failure Classification
| Category | Examples | Behavior in SkipFields Mode |
|---|---|---|
| Recoverable | No encryptor, KMS access denied | Field skipped, warning logged |
| Integrity | Invalid ciphertext, wrong key, context validation failed | Always throws |
Combining with Other Attributes
The [Encrypted] attribute works alongside other attributes:
| Combination | Behavior |
|---|---|
[Encrypted] + [Sensitive] | Encrypted at rest, redacted in logs |
[Encrypted] + [BlobStorage] | Encrypted before uploading to S3 |
[Encrypted] + [JsonBlob] | JSON serialized, then encrypted |
Cache TTL
Control how long decrypted data keys are cached:
[Encrypted(CacheTtlSeconds = 600)]
[DynamoDbAttribute("ssn")]
public string SocialSecurityNumber { get; set; } = string.Empty;
See Also
- KMS Setup - Configure AWS KMS keys
- Envelope Encryption - How the encryption pattern works
- Logging with Redaction - Redacting sensitive data in logs