Skip to main content

KMS Setup

Configure AWS Key Management Service (KMS) for field-level encryption with FluentDynamoDB.

Installation

dotnet add package Oproto.FluentDynamoDb.Encryption.Kms

Creating a KMS Key

Create a symmetric encryption key in AWS KMS:

aws kms create-key \
--description "FluentDynamoDB field encryption key" \
--key-usage ENCRYPT_DECRYPT \
--key-spec SYMMETRIC_DEFAULT

Note the KeyId or Arn from the response — you'll use this when configuring the encryptor.

Enable Key Rotation

Enable automatic key rotation for security best practices:

aws kms enable-key-rotation --key-id <your-key-id>

Basic Configuration

Single Key Setup

using Oproto.FluentDynamoDb;
using Oproto.FluentDynamoDb.Encryption.Kms;

// Create key resolver with a single default key
var keyResolver = new DefaultKmsKeyResolver("arn:aws:kms:us-east-1:123456789012:key/my-key-id");

// Create the field encryptor
var encryptor = new AwsEncryptionSdkFieldEncryptor(keyResolver);

// Configure options
var options = new FluentDynamoDbOptions()
.WithEncryption(encryptor);

var table = new CustomersTable(client, "customers", options);

With Caching Enabled

var encryptorOptions = new AwsEncryptionSdkOptions
{
EnableCaching = true
};

var encryptor = new AwsEncryptionSdkFieldEncryptor(keyResolver, encryptorOptions);

var options = new FluentDynamoDbOptions()
.WithEncryption(encryptor);

Multi-Tenant Key Configuration

For multi-tenant applications, use per-tenant KMS keys for isolation:

var keyResolver = new DefaultKmsKeyResolver(
defaultKeyId: "arn:aws:kms:us-east-1:123456789012:key/default-key",
contextKeyMap: new Dictionary<string, string>
{
["tenant-a"] = "arn:aws:kms:us-east-1:123456789012:key/tenant-a-key",
["tenant-b"] = "arn:aws:kms:us-east-1:123456789012:key/tenant-b-key",
["tenant-c"] = "arn:aws:kms:us-east-1:123456789012:key/tenant-c-key"
});

var encryptor = new AwsEncryptionSdkFieldEncryptor(keyResolver);

Key resolution:

  • WithEncryptionContext("tenant-a") → uses tenant-a-key
  • WithEncryptionContext("unknown") → uses default-key
  • No context provided → uses default-key

Custom Key Resolver

For dynamic key resolution (database, external service, etc.):

public class DatabaseKmsKeyResolver : IKmsKeyResolver
{
private readonly IKeyRepository _keyRepo;
private readonly string _defaultKey;

public DatabaseKmsKeyResolver(IKeyRepository keyRepo, string defaultKey)
{
_keyRepo = keyRepo;
_defaultKey = defaultKey;
}

public string ResolveKeyId(string? contextId)
{
if (contextId == null)
return _defaultKey;

var keyArn = _keyRepo.GetKmsKeyForTenant(contextId);
return keyArn ?? _defaultKey;
}
}

IAM Permissions

Your application needs the following KMS permissions:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey"
],
"Resource": "arn:aws:kms:us-east-1:123456789012:key/my-key-id"
}
]
}

With Encryption Context Conditions

For additional security, restrict access based on encryption context:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey"
],
"Resource": "arn:aws:kms:us-east-1:123456789012:key/my-key-id",
"Condition": {
"StringEquals": {
"kms:EncryptionContext:app": "my-app"
}
}
}
]
}

AwsEncryptionSdkOptions Reference

public class AwsEncryptionSdkOptions
{
/// <summary>
/// Enable keyring caching (default: true).
/// Caches keyring objects to reduce creation overhead.
/// </summary>
public bool EnableCaching { get; set; } = true;

/// <summary>
/// Algorithm suite (default: AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384).
/// Uses key commitment by default for security.
/// </summary>
public string Algorithm { get; set; } =
"AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384";
}

Troubleshooting

"KMS key not found" or "Access denied"

  1. Verify the KMS key ARN is correct and the key exists
  2. Check IAM role has kms:Encrypt, kms:Decrypt, and kms:GenerateDataKey permissions
  3. Verify encryption context matches key policy conditions
# Test KMS access
aws kms encrypt \
--key-id <your-key-id> \
--plaintext "test" \
--encryption-context field=test,entity=User

"Failed to decrypt: Invalid ciphertext"

  1. Verify you're using the same KMS key for encryption and decryption
  2. Check if encryption context matches
  3. Verify data hasn't been corrupted in DynamoDB

Diagnostic Warning FDDB4001

If you use [Encrypted] without the encryption package installed:

Warning FDDB4001: Property 'SensitiveData' has [Encrypted] attribute but
Oproto.FluentDynamoDb.Encryption.Kms package is not referenced.

Solution: Install the package with dotnet add package Oproto.FluentDynamoDb.Encryption.Kms.

See Also