Searchable encryption in DynamoDB - AWS Database Encryption SDK

Searchable encryption in DynamoDB

To configure your Amazon DynamoDB tables for searchable encryption, you must use the AWS KMS Hierarchical keyring to generate, encrypt, and decrypt the data keys used to protect your items. You must also include the SearchConfig in your table encryption configuration.

Note

If you're using the Java client-side encryption library for DynamoDB, you must use the low-level AWS Database Encryption SDK for DynamoDB API to encrypt, sign, verify, and decrypt your table items. The DynamoDB Enhanced Client and lower-level DynamoDBItemEncryptor do not support searchable encryption.

Configuring secondary indexes with beacons

After you configure your beacons, you must configure a secondary index that reflects each beacon before you can search on the encrypted attributes.

When you configure a standard or compound beacon, the AWS Database Encryption SDK adds the aws_dbe_b_ prefix to the beacon name so that the server can easily identify beacons. For example, if you name a compound beacon, compoundBeacon, the full beacon name is actually aws_dbe_b_compoundBeacon. If you want to configure secondary indexes that include a standard or compound beacon, you must include the aws_dbe_b_ prefix when you identify the beacon name.

Partition and sort keys

You cannot encrypt primary key values. Your partition and sort keys must be signed. Your primary key values cannot be a standard or compound beacon.

Your primary key values must be SIGN_ONLY, unless you specify any SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT attributes, then the partition and sort attributes must also be SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT.

Your primary key values can be signed beacons. If you configured distinct signed beacons for each of your primary key values, you must specify the attribute name that identifies the primary key value as the signed beacon name. However, the AWS Database Encryption SDK does not add the aws_dbe_b_ prefix to signed beacons. Even if you configured distinct signed beacons for your primary key values, you only need to specify the attribute names for the primary key values when you configure a secondary index.

Local secondary indexes

The sort key for a local secondary index can be a beacon.

If you specify a beacon for the sort key, the type must be String. If you specify a standard or compound beacon for the sort key, you must include the aws_dbe_b_ prefix when you specify the beacon name. If you specify a signed beacon, specify the beacon name without any prefix.

Global secondary indexes

The partition and sort keys for a global secondary index can both be beacons.

If you specify a beacon for the partition or sort key, the type must be String. If you specify a standard or compound beacon for the sort key, you must include the aws_dbe_b_ prefix when you specify the beacon name. If you specify a signed beacon, specify the beacon name without any prefix.

Attribute projections

A projection is the set of attributes that is copied from a table into a secondary index. The partition key and sort key of the table are always projected into the index; you can project other attributes to support your application's query requirements. DynamoDB provides three different options for attribute projections: KEYS_ONLY, INCLUDE, and ALL.

If you use the INCLUDE attribute projection to search on a beacon, you must specify the names for all of the attributes that the beacon is constructed from and the beacon name with the aws_dbe_b_ prefix. For example, if you configured a compound beacon, compoundBeacon, from field1, field2, and field3, you must specify aws_dbe_b_compoundBeacon, field1, field2, and field3 in the projection.

A global secondary index can only use the attributes explicitly specified in the projection, but a local secondary index can use any attribute.

Testing beacon outputs

If you configured compound beacons or constructed your beacons using virtual fields, we recommend verifying that these beacons produce the expected output before populating your DynamoDB table.

The AWS Database Encryption SDK provides the DynamoDbEncryptionTransforms service to help you troubleshoot virtual field and compound beacon outputs.

The following snippet creates test items, defines the DynamoDbEncryptionTransforms service with the DynamoDB table encryption configuration, and demonstrates how to use ResolveAttributes to verify that the virtual field produces the expected output.

Java

See the complete code sample: VirtualBeaconSearchableEncryptionExample.java

// Create test items final PutItemRequest itemWithHasTestResultPutRequest = PutItemRequest.builder() .tableName(ddbTableName) .item(itemWithHasTestResult) .build(); final PutItemResponse itemWithHasTestResultPutResponse = ddb.putItem(itemWithHasTestResultPutRequest); final PutItemRequest itemWithNoHasTestResultPutRequest = PutItemRequest.builder() .tableName(ddbTableName) .item(itemWithNoHasTestResult) .build(); final PutItemResponse itemWithNoHasTestResultPutResponse = ddb.putItem(itemWithNoHasTestResultPutRequest); // Define the DynamoDbEncryptionTransforms service final DynamoDbEncryptionTransforms trans = DynamoDbEncryptionTransforms.builder() .DynamoDbTablesEncryptionConfig(encryptionConfig).build(); // Verify configuration final ResolveAttributesInput resolveInput = ResolveAttributesInput.builder() .TableName(ddbTableName) .Item(itemWithHasTestResult) .Version(1) .build(); final ResolveAttributesOutput resolveOutput = trans.ResolveAttributes(resolveInput); // Verify that VirtualFields has the expected value Map<String, String> vf = new HashMap<>(); vf.put("stateAndHasTestResult", "CAt"); assert resolveOutput.VirtualFields().equals(vf);
C# / .NET

See the complete code sample: VirtualBeaconSearchableEncryptionExample.cs

// Create item with hasTestResult=true var itemWithHasTestResult = new Dictionary<String, AttributeValue> { ["customer_id"] = new AttributeValue("ABC-123"), ["create_time"] = new AttributeValue { N = "1681495205" }, ["state"] = new AttributeValue("CA"), ["hasTestResult"] = new AttributeValue { BOOL = true } }; // Create item with hasTestResult=false var itemWithNoHasTestResult = new Dictionary<String, AttributeValue> { ["customer_id"] = new AttributeValue("DEF-456"), ["create_time"] = new AttributeValue { N = "1681495205" }, ["state"] = new AttributeValue("CA"), ["hasTestResult"] = new AttributeValue { BOOL = false } }; // Define the DynamoDbEncryptionTransforms service var trans = new DynamoDbEncryptionTransforms(encryptionConfig); // Verify configuration var resolveInput = new ResolveAttributesInput { TableName = ddbTableName, Item = itemWithHasTestResult, Version = 1 }; var resolveOutput = trans.ResolveAttributes(resolveInput); // Verify that VirtualFields has the expected value Debug.Assert(resolveOutput.VirtualFields.Count == 1); Debug.Assert(resolveOutput.VirtualFields["stateAndHasTestResult"] == "CAt");

The following snippet creates a test item, defines the DynamoDbEncryptionTransforms service with the DynamoDB table encryption configuration, and demonstrates how to use ResolveAttributes to verify that the compound beacon produces the expected output.

Java

See the complete code sample: CompoundBeaconSearchableEncryptionExample.java

// Create an item with both attributes used in the compound beacon. final HashMap<String, AttributeValue> item = new HashMap<>(); item.put("work_id", AttributeValue.builder().s("9ce39272-8068-4efd-a211-cd162ad65d4c").build()); item.put("inspection_date", AttributeValue.builder().s("2023-06-13").build()); item.put("inspector_id_last4", AttributeValue.builder().s("5678").build()); item.put("unit", AttributeValue.builder().s("011899988199").build()); // Define the DynamoDbEncryptionTransforms service final DynamoDbEncryptionTransforms trans = DynamoDbEncryptionTransforms.builder() .DynamoDbTablesEncryptionConfig(encryptionConfig).build(); // Verify configuration final ResolveAttributesInput resolveInput = ResolveAttributesInput.builder() .TableName(ddbTableName) .Item(item) .Version(1) .build(); final ResolveAttributesOutput resolveOutput = trans.ResolveAttributes(resolveInput); // Verify that CompoundBeacons has the expected value Map<String, String> cbs = new HashMap<>(); cbs.put("last4UnitCompound", "L-5678.U-011899988199"); assert resolveOutput.CompoundBeacons().equals(cbs); // Note : the compound beacon actually stored in the table is not "L-5678.U-011899988199" // but rather something like "L-abc.U-123", as both parts are EncryptedParts // and therefore the text is replaced by the associated beacon
C# / .NET

See the complete code sample: CompoundBeaconSearchableEncryptionExample.cs

// Create an item with both attributes used in the compound beacon var item = new Dictionary<String, AttributeValue> { ["work_id"] = new AttributeValue("9ce39272-8068-4efd-a211-cd162ad65d4c"), ["inspection_date"] = new AttributeValue("2023-06-13"), ["inspector_id_last4"] = new AttributeValue("5678"), ["unit"] = new AttributeValue("011899988199") }; // Define the DynamoDbEncryptionTransforms service var trans = new DynamoDbEncryptionTransforms(encryptionConfig); // Verify configuration var resolveInput = new ResolveAttributesInput { TableName = ddbTableName, Item = item, Version = 1 }; var resolveOutput = trans.ResolveAttributes(resolveInput); // Verify that CompoundBeacons has the expected value Debug.Assert(resolveOutput.CompoundBeacons.Count == 1); Debug.Assert(resolveOutput.CompoundBeacons["last4UnitCompound"] == "L-5678.U-011899988199"); // Note : the compound beacon actually stored in the table is not "L-5678.U-011899988199" // but rather something like "L-abc.U-123", as both parts are EncryptedParts // and therefore the text is replaced by the associated beacon