Skip to content

Commit 2aec2c3

Browse files
author
Lucas McDonald
committed
m
1 parent 60b3835 commit 2aec2c3

File tree

5 files changed

+274
-0
lines changed

5 files changed

+274
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Stub to allow relative imports of examples from tests."""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Stub to allow relative imports of examples from tests."""
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
"""
2+
This example sets up an ItemEncryptor and uses
3+
its APIs to encrypt and decrypt items in 3 different formats.
4+
5+
You should use the ItemEncryptor
6+
if you already have an item to encrypt or decrypt,
7+
and do not need to make a Put or Get call to DynamoDb.
8+
For example, if you are using DynamoDb Streams,
9+
you may already be working with an encrypted item obtained from
10+
DynamoDb, and want to directly decrypt the item.
11+
12+
This example demonstrates the 3 formats the Item Encryptor can accept:
13+
- Python dictionaries (encrypt_python_item, decrypt_python_item)
14+
- DynamoDB JSON (encrypt_dynamodb_item, decrypt_dynamodb_item)
15+
- DBESDK shapes (encrypt_item, decrypt_item)
16+
17+
Running this example requires access to the DDB Table whose name
18+
is provided in CLI arguments.
19+
This table must be configured with the following
20+
primary key configuration:
21+
- Partition key is named "partition_key" with type (S)
22+
- Sort key is named "sort_key" with type (S)
23+
"""
24+
25+
import sys
26+
from typing import Dict, Any
27+
28+
29+
import boto3
30+
from boto3.dynamodb.types import Binary
31+
from decimal import Decimal
32+
33+
from aws_cryptographic_material_providers.mpl import AwsCryptographicMaterialProviders
34+
from aws_cryptographic_material_providers.mpl.config import MaterialProvidersConfig
35+
from aws_cryptographic_material_providers.mpl.models import (
36+
CreateAwsKmsMrkKeyringInput,
37+
CreateAwsKmsMrkMultiKeyringInput,
38+
DBEAlgorithmSuiteId,
39+
)
40+
from aws_cryptographic_material_providers.mpl.references import IKeyring
41+
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_structuredencryption.models import (
42+
CryptoAction,
43+
)
44+
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.models import (
45+
DynamoDbTableEncryptionConfig,
46+
DynamoDbTablesEncryptionConfig,
47+
)
48+
from aws_dbesdk_dynamodb.encrypted.client import (
49+
EncryptedClient
50+
)
51+
52+
from aws_dbesdk_dynamodb.encrypted.item import (
53+
ItemEncryptor,
54+
DynamoDbItemEncryptorConfig,
55+
EncryptItemInput,
56+
DecryptItemInput,
57+
)
58+
59+
def encrypt_decrypt_example(kms_key_id: str, ddb_table_name: str) -> None:
60+
# 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data.
61+
# For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use.
62+
# We will use the `CreateMrkMultiKeyring` method to create this keyring,
63+
# as it will correctly handle both single region and Multi-Region KMS Keys.
64+
mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(
65+
config=MaterialProvidersConfig()
66+
)
67+
kms_mrk_multi_keyring_input: CreateAwsKmsMrkMultiKeyringInput =\
68+
CreateAwsKmsMrkMultiKeyringInput(
69+
generator=kms_key_id,
70+
)
71+
kms_mrk_multi_keyring: IKeyring = mat_prov.create_aws_kms_mrk_multi_keyring(
72+
input=kms_mrk_multi_keyring_input
73+
)
74+
75+
# 2. Configure which attributes are encrypted and/or signed when writing new items.
76+
# For each attribute that may exist on the items we plan to write to our DynamoDbTable,
77+
# we must explicitly configure how they should be treated during item encryption:
78+
# - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
79+
# - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
80+
# - DO_NOTHING: The attribute is not encrypted and not included in the signature
81+
attribute_actions_on_encrypt: Dict[str, str] = {
82+
"partition_key": CryptoAction.SIGN_ONLY, # Our partition attribute must be SIGN_ONLY
83+
"sort_key": CryptoAction.SIGN_ONLY, # Our sort attribute must be SIGN_ONLY
84+
"attribute1": CryptoAction.ENCRYPT_AND_SIGN,
85+
"attribute2": CryptoAction.SIGN_ONLY,
86+
":attribute3": CryptoAction.DO_NOTHING,
87+
}
88+
89+
# 3. Configure which attributes we expect to be included in the signature
90+
# when reading items. There are two options for configuring this:
91+
#
92+
# - (Recommended) Configure `allowedUnsignedAttributesPrefix`:
93+
# When defining your DynamoDb schema and deciding on attribute names,
94+
# choose a distinguishing prefix (such as ":") for all attributes that
95+
# you do not want to include in the signature.
96+
# This has two main benefits:
97+
# - It is easier to reason about the security and authenticity of data within your item
98+
# when all unauthenticated data is easily distinguishable by their attribute name.
99+
# - If you need to add new unauthenticated attributes in the future,
100+
# you can easily make the corresponding update to your `attributeActionsOnEncrypt`
101+
# and immediately start writing to that new attribute, without
102+
# any other configuration update needed.
103+
# Once you configure this field, it is not safe to update it.
104+
#
105+
# - Configure `allowedUnsignedAttributes`: You may also explicitly list
106+
# a set of attributes that should be considered unauthenticated when encountered
107+
# on read. Be careful if you use this configuration. Do not remove an attribute
108+
# name from this configuration, even if you are no longer writing with that attribute,
109+
# as old items may still include this attribute, and our configuration needs to know
110+
# to continue to exclude this attribute from the signature scope.
111+
# If you add new attribute names to this field, you must first deploy the update to this
112+
# field to all readers in your host fleet before deploying the update to start writing
113+
# with that new attribute.
114+
#
115+
# For this example, we have designed our DynamoDb table such that any attribute name with
116+
# the ":" prefix should be considered unauthenticated.
117+
unsign_attr_prefix = ":"
118+
119+
# 4. Create the configuration for the DynamoDb Item Encryptor
120+
config = DynamoDbItemEncryptorConfig(
121+
logical_table_name=ddb_table_name,
122+
partition_key_name="partition_key",
123+
sort_key_name="sort_key",
124+
attribute_actions_on_encrypt=attribute_actions_on_encrypt,
125+
keyring=kms_mrk_multi_keyring,
126+
allowed_unsigned_attribute_prefix=unsign_attr_prefix,
127+
# Specifying an algorithm suite is not required,
128+
# but is done here to demonstrate how to do so.
129+
# We suggest using the
130+
# `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite,
131+
# which includes AES-GCM with key derivation, signing, and key commitment.
132+
# This is also the default algorithm suite if one is not specified in this config.
133+
# For more information on supported algorithm suites, see:
134+
# https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html
135+
algorithm_suite_id=DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384
136+
)
137+
138+
# 5. Create the DynamoDb Item Encryptor
139+
item_encryptor = ItemEncryptor(config)
140+
141+
# 6. Directly encrypt a Python dictionary item using the ItemEncryptor
142+
plaintext_dict_item: Dict[str, Any] = {
143+
"partition_key": "ItemEncryptDecryptExample",
144+
"sort_key": 0,
145+
"attribute1": "encrypt and sign me!",
146+
"attribute2": "sign me!",
147+
":attribute3": "ignore me!",
148+
}
149+
150+
encrypt_output = item_encryptor.encrypt_python_item(
151+
plaintext_dict_item
152+
)
153+
encrypted_dict_item = encrypt_output.encrypted_item
154+
155+
# Demonstrate that the item has been encrypted according to the configuration
156+
# Our configuration specified that the partition key should be SIGN_ONLY,
157+
# so it should not have been encrypted
158+
assert encrypted_dict_item["partition_key"] == "ItemEncryptDecryptExample"
159+
# Our configuration specified that the sort key should be SIGN_ONLY,
160+
# so it should not have been encrypted
161+
assert encrypted_dict_item["sort_key"] == 0
162+
# Our configuration specified that attribute1 should be ENCRYPT_AND_SIGN,
163+
# so it should have been encrypted
164+
assert "attribute1" in encrypted_dict_item
165+
assert encrypted_dict_item["attribute1"] != plaintext_dict_item["attribute1"]
166+
167+
# 7. Directly decrypt the encrypted item using the DynamoDb Item Encryptor
168+
decrypt_output = item_encryptor.decrypt_python_item(
169+
encrypted_dict_item
170+
)
171+
decrypted_dict_item = decrypt_output.plaintext_item
172+
173+
# Demonstrate that GetItem succeeded and returned the decrypted item
174+
assert decrypted_dict_item["partition_key"] == "ItemEncryptDecryptExample"
175+
assert decrypted_dict_item["sort_key"] == 0
176+
assert decrypted_dict_item["attribute1"] == "encrypt and sign me!"
177+
178+
# 8. Directly encrypt a DynamoDB JSON item using the ItemEncryptor
179+
plaintext_dynamodb_item: Dict[str, Any] = {
180+
"partition_key": {"S": "ItemEncryptDecryptExample"},
181+
"sort_key": {"N": "0"},
182+
"attribute1": {"S": "encrypt and sign me!"},
183+
"attribute2": {"S": "sign me!"},
184+
":attribute3": {"S": "ignore me!"},
185+
}
186+
encrypt_output = item_encryptor.encrypt_dynamodb_item(
187+
plaintext_dynamodb_item
188+
)
189+
encrypted_dynamodb_item = encrypt_output.encrypted_item
190+
191+
# Demonstrate that the item has been encrypted according to the configuration
192+
# Our configuration specified that the partition key should be SIGN_ONLY,
193+
# so it should not have been encrypted
194+
assert encrypted_dynamodb_item["partition_key"] == {"S": "ItemEncryptDecryptExample"}
195+
# Our configuration specified that the sort key should be SIGN_ONLY,
196+
# so it should not have been encrypted
197+
assert encrypted_dynamodb_item["sort_key"] == {"N": "0"}
198+
# Our configuration specified that attribute1 should be ENCRYPT_AND_SIGN,
199+
# so it should have been encrypted
200+
assert "attribute1" in encrypted_dynamodb_item
201+
assert encrypted_dynamodb_item["attribute1"] != plaintext_dynamodb_item["attribute1"]
202+
203+
# 9. Directly decrypt the encrypted item using the DynamoDb Item Encryptor
204+
decrypt_output = item_encryptor.decrypt_dynamodb_item(
205+
encrypted_dynamodb_item
206+
)
207+
decrypted_dynamodb_item = decrypt_output.plaintext_item
208+
209+
# Demonstrate that GetItem succeeded and returned the decrypted item
210+
assert decrypted_dynamodb_item["partition_key"] == {"S": "ItemEncryptDecryptExample"}
211+
assert decrypted_dynamodb_item["sort_key"] == {"N": "0"}
212+
assert decrypted_dynamodb_item["attribute1"] == {"S": "encrypt and sign me!"}
213+
214+
# 10. Directly encrypt a DBESDK shape item using the ItemEncryptor
215+
encrypt_item_input: EncryptItemInput = EncryptItemInput(
216+
plaintext_item=plaintext_dynamodb_item
217+
)
218+
encrypt_item_output = item_encryptor.encrypt_item(
219+
encrypt_item_input
220+
)
221+
encrypted_item = encrypt_item_output.encrypted_item
222+
223+
# Demonstrate that the item has been encrypted according to the configuration
224+
# Our configuration specified that the partition key should be SIGN_ONLY,
225+
# so it should not have been encrypted
226+
assert encrypted_item["partition_key"] == {"S": "ItemEncryptDecryptExample"}
227+
# Our configuration specified that the sort key should be SIGN_ONLY,
228+
# so it should not have been encrypted
229+
assert encrypted_item["sort_key"] == {"N": "0"}
230+
# Our configuration specified that attribute1 should be ENCRYPT_AND_SIGN,
231+
# so it should have been encrypted
232+
assert "attribute1" in encrypted_item
233+
assert encrypted_item["attribute1"] != plaintext_dynamodb_item["attribute1"]
234+
235+
# 11. Directly decrypt the encrypted item using the DynamoDb Item Encryptor
236+
decrypt_item_input: DecryptItemInput = DecryptItemInput(
237+
encrypted_item=encrypted_item
238+
)
239+
decrypt_output = item_encryptor.decrypt_item(
240+
decrypt_item_input
241+
)
242+
decrypted_item = decrypt_output.plaintext_item
243+
244+
# Demonstrate that GetItem succeeded and returned the decrypted item
245+
assert decrypted_item["partition_key"] == {"S": "ItemEncryptDecryptExample"}
246+
assert decrypted_item["sort_key"] == {"N": "0"}
247+
assert decrypted_item["attribute1"] == {"S": "encrypt and sign me!"}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Stub to allow relative imports of examples from tests."""
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Test suite for the EncryptedClient example."""
4+
import pytest
5+
6+
from ...src.item_encryptor.encrypt_decrypt_example import encrypt_decrypt_example
7+
8+
pytestmark = [pytest.mark.examples]
9+
10+
11+
def test_encrypt_decrypt_example():
12+
"""Test function for encrypt and decrypt using the EncryptedClient example."""
13+
test_kms_key_id = \
14+
"arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f"
15+
test_dynamodb_table_name = \
16+
"DynamoDbEncryptionInterceptorTestTable"
17+
encrypt_decrypt_example(test_kms_key_id,
18+
test_dynamodb_table_name)

0 commit comments

Comments
 (0)