Skip to content

Commit 9bbc3cc

Browse files
feat(Examples): Create raw AES and RSA keyring examples (#169)
1 parent a524025 commit 9bbc3cc

File tree

8 files changed

+568
-4
lines changed

8 files changed

+568
-4
lines changed

Examples/runtimes/java/DynamoDbEncryption/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ dependencies {
6464
implementation("software.amazon.awssdk:dynamodb-enhanced")
6565
implementation("software.amazon.awssdk:kms")
6666

67+
implementation("org.bouncycastle:bcprov-jdk18on:1.72")
68+
6769
// https://mvnrepository.com/artifact/org.testng/testng
6870
testImplementation("org.testng:testng:7.5")
6971
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package software.aws.cryptography.examples;
1+
package software.aws.cryptography.examples.keyring;
22

33
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
44
import software.amazon.cryptography.dbencryptionsdk.dynamodb.IDynamoDbKeyBranchKeyIdSupplier;
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package software.aws.cryptography.examples;
1+
package software.aws.cryptography.examples.keyring;
22

33
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
44
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
@@ -17,7 +17,6 @@
1717
import software.amazon.cryptography.materialproviders.IKeyring;
1818
import software.amazon.cryptography.materialproviders.MaterialProviders;
1919
import software.amazon.cryptography.materialproviders.model.CreateAwsKmsHierarchicalKeyringInput;
20-
import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMrkMultiKeyringInput;
2120
import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig;
2221
import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.CryptoAction;
2322
import software.aws.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package software.aws.cryptography.examples.keyring;
2+
3+
import java.nio.ByteBuffer;
4+
import java.security.NoSuchAlgorithmException;
5+
import java.security.SecureRandom;
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
import javax.crypto.KeyGenerator;
9+
import javax.crypto.SecretKey;
10+
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
11+
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
12+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
13+
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
14+
import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
15+
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
16+
import software.amazon.awssdk.services.dynamodb.model.PutItemResponse;
17+
import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbTableEncryptionConfig;
18+
import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbTablesEncryptionConfig;
19+
import software.amazon.cryptography.materialproviders.IKeyring;
20+
import software.amazon.cryptography.materialproviders.MaterialProviders;
21+
import software.amazon.cryptography.materialproviders.model.AesWrappingAlg;
22+
import software.amazon.cryptography.materialproviders.model.CreateRawAesKeyringInput;
23+
import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig;
24+
import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.CryptoAction;
25+
import software.aws.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor;
26+
27+
/*
28+
This example sets up DynamoDb Encryption for the AWS SDK client
29+
using the raw AES Keyring. This keyring takes in an AES key
30+
and uses that key to protect the data keys that encrypt and
31+
decrypt DynamoDb table items.
32+
33+
This example takes in an `aesKeyBytes` parameter. This parameter
34+
should be a ByteBuffer representing a 256-bit AES key. If this example
35+
is run through the class' main method, it will create a new key.
36+
In practice, users of this library should not randomly generate a key,
37+
and should instead retrieve an existing key from a secure key
38+
management system (e.g. an HSM).
39+
40+
This example encrypts a test item using the provided AES key and puts the
41+
encrypted item to the provided DynamoDb table. Then, it gets the
42+
item from the table and decrypts it.
43+
44+
Running this example requires access to the DDB Table whose name
45+
is provided in CLI arguments.
46+
This table must be configured with the following
47+
primary key configuration:
48+
- Partition key is named "partition_key" with type (S)
49+
- Sort key is named "sort_key" with type (S)
50+
*/
51+
public class RawAesKeyringExample {
52+
53+
public static void RawAesKeyringGetItemPutItem(String ddbTableName, ByteBuffer aesKeyBytes) {
54+
// 1. Create the keyring.
55+
// The DynamoDb encryption client uses this to encrypt and decrypt items.
56+
final CreateRawAesKeyringInput keyringInput = CreateRawAesKeyringInput.builder()
57+
.keyName("my-aes-key-name")
58+
.keyNamespace("my-key-namespace")
59+
.wrappingKey(aesKeyBytes)
60+
.wrappingAlg(AesWrappingAlg.ALG_AES256_GCM_IV12_TAG16)
61+
.build();
62+
final MaterialProviders matProv = MaterialProviders.builder()
63+
.MaterialProvidersConfig(MaterialProvidersConfig.builder().build())
64+
.build();
65+
IKeyring rawAesKeyring = matProv.CreateRawAesKeyring(keyringInput);
66+
67+
// 2. Configure which attributes are encrypted and/or signed when writing new items.
68+
// For each attribute that may exist on the items we plan to write to our DynamoDbTable,
69+
// we must explicitly configure how they should be treated during item encryption:
70+
// - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
71+
// - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
72+
// - DO_NOTHING: The attribute is not encrypted and not included in the signature
73+
final Map<String, CryptoAction> attributeActions = new HashMap<>();
74+
attributeActions.put("partition_key", CryptoAction.SIGN_ONLY); // Our partition attribute must be SIGN_ONLY
75+
attributeActions.put("sort_key", CryptoAction.SIGN_ONLY); // Our sort attribute must be SIGN_ONLY
76+
attributeActions.put("sensitive_data", CryptoAction.ENCRYPT_AND_SIGN);
77+
78+
// 3. Configure which attributes we expect to be included in the signature
79+
// when reading items. There are two options for configuring this:
80+
//
81+
// - (Recommended) Configure `allowedUnauthenticatedAttributesPrefix`:
82+
// When defining your DynamoDb schema and deciding on attribute names,
83+
// choose a distinguishing prefix (such as ":") for all attributes that
84+
// you do not want to include in the signature.
85+
// This has two main benefits:
86+
// - It is easier to reason about the security and authenticity of data within your item
87+
// when all unauthenticated data is easily distinguishable by their attribute name.
88+
// - If you need to add new unauthenticated attributes in the future,
89+
// you can easily make the corresponding update to your `attributeActions`
90+
// and immediately start writing to that new attribute, without
91+
// any other configuration update needed.
92+
// Once you configure this field, it is not safe to update it.
93+
//
94+
// - Configure `allowedUnauthenticatedAttributes`: You may also explicitly list
95+
// a set of attributes that should be considered unauthenticated when encountered
96+
// on read. Be careful if you use this configuration. Do not remove an attribute
97+
// name from this configuration, even if you are no longer writing with that attribute,
98+
// as old items may still include this attribute, and our configuration needs to know
99+
// to continue to exclude this attribute from the signature scope.
100+
// If you add new attribute names to this field, you must first deploy the update to this
101+
// field to all readers in your host fleet before deploying the update to start writing
102+
// with that new attribute.
103+
//
104+
// For this example, we currently authenticate all attributes. To make it easier to
105+
// add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
106+
final String unauthAttrPrefix = ":";
107+
108+
// 4. Create the DynamoDb Encryption configuration for the table we will be writing to.
109+
final Map<String, DynamoDbTableEncryptionConfig> tableConfigs = new HashMap<>();
110+
final DynamoDbTableEncryptionConfig config = DynamoDbTableEncryptionConfig.builder()
111+
.logicalTableName(ddbTableName)
112+
.partitionKeyName("partition_key")
113+
.sortKeyName("sort_key")
114+
.attributeActions(attributeActions)
115+
.keyring(rawAesKeyring)
116+
.allowedUnauthenticatedAttributePrefix(unauthAttrPrefix)
117+
.build();
118+
tableConfigs.put(ddbTableName, config);
119+
120+
// 5. Create the DynamoDb Encryption Interceptor
121+
DynamoDbEncryptionInterceptor encryptionInterceptor = DynamoDbEncryptionInterceptor.builder()
122+
.config(DynamoDbTablesEncryptionConfig.builder()
123+
.tableEncryptionConfigs(tableConfigs)
124+
.build())
125+
.build();
126+
127+
// 6. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above
128+
final DynamoDbClient ddb = DynamoDbClient.builder()
129+
.overrideConfiguration(
130+
ClientOverrideConfiguration.builder()
131+
.addExecutionInterceptor(encryptionInterceptor)
132+
.build())
133+
.build();
134+
135+
// 7. Put an item into our table using the above client.
136+
// Before the item gets sent to DynamoDb, it will be encrypted
137+
// client-side, according to our configuration.
138+
final HashMap<String, AttributeValue> item = new HashMap<>();
139+
item.put("partition_key", AttributeValue.builder().s("rawAesKeyringItem").build());
140+
item.put("sort_key", AttributeValue.builder().n("0").build());
141+
item.put("sensitive_data", AttributeValue.builder().s("encrypt and sign me!").build());
142+
143+
final PutItemRequest putRequest = PutItemRequest.builder()
144+
.tableName(ddbTableName)
145+
.item(item)
146+
.build();
147+
148+
final PutItemResponse putResponse = ddb.putItem(putRequest);
149+
150+
// Demonstrate that PutItem succeeded
151+
assert 200 == putResponse.sdkHttpResponse().statusCode();
152+
153+
// 8. Get the item back from our table using the same client.
154+
// The client will decrypt the item client-side, and return
155+
// back the original item.
156+
final HashMap<String, AttributeValue> keyToGet = new HashMap<>();
157+
keyToGet.put("partition_key", AttributeValue.builder().s("rawAesKeyringItem").build());
158+
keyToGet.put("sort_key", AttributeValue.builder().n("0").build());
159+
160+
final GetItemRequest getRequest = GetItemRequest.builder()
161+
.key(keyToGet)
162+
.tableName(ddbTableName)
163+
.build();
164+
165+
final GetItemResponse getResponse = ddb.getItem(getRequest);
166+
167+
// Demonstrate that GetItem succeeded and returned the decrypted item
168+
assert 200 == getResponse.sdkHttpResponse().statusCode();
169+
final Map<String, AttributeValue> returnedItem = getResponse.item();
170+
assert returnedItem.get("sensitive_data").s().equals("encrypt and sign me!");
171+
}
172+
173+
public static void main(final String[] args) {
174+
if (args.length <= 0) {
175+
throw new IllegalArgumentException("To run this example, include the ddbTable in args");
176+
}
177+
final String ddbTableName = args[0];
178+
179+
// Generate a new AES key
180+
ByteBuffer aesKeyBytes = generateAesKeyBytes();
181+
182+
RawAesKeyringGetItemPutItem(ddbTableName, aesKeyBytes);
183+
}
184+
185+
static ByteBuffer generateAesKeyBytes() {
186+
// This example uses BouncyCastle's KeyGenerator to generate the key bytes.
187+
// In practice, you should not generate this key in your code, and should instead
188+
// retrieve this key from a secure key management system (e.g. HSM).
189+
// This key is created here for example purposes only and should not be used for any other purpose.
190+
KeyGenerator aesGen;
191+
try {
192+
aesGen = KeyGenerator.getInstance("AES");
193+
} catch (NoSuchAlgorithmException e) {
194+
throw new RuntimeException("No such algorithm", e);
195+
}
196+
aesGen.init(256, new SecureRandom());
197+
SecretKey encryptionKey = aesGen.generateKey();
198+
ByteBuffer encryptionKeyByteBuffer = ByteBuffer.wrap(encryptionKey.getEncoded());
199+
return encryptionKeyByteBuffer;
200+
}
201+
}

0 commit comments

Comments
 (0)