Skip to content

Commit 88e6d58

Browse files
authored
chore: added raw rsa and raw aes keyring examples (#661)
1 parent 750bb68 commit 88e6d58

File tree

6 files changed

+514
-2
lines changed

6 files changed

+514
-2
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ __pycache__
3131

3232
# PyTest
3333
.pytest_cache
34+
# Ignore key materials generated by examples or tests
35+
test_keys/
3436

3537
# PyCharm
3638
.idea/
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
This example sets up the Raw AES Keyring
5+
6+
The Raw AES keyring lets you use an AES symmetric key that you provide as a wrapping key that
7+
protects your data key. You need to generate, store, and protect the key material,
8+
preferably in a hardware security module (HSM) or key management system. Use a Raw AES keyring
9+
when you need to provide the wrapping key and encrypt the data keys locally or offline.
10+
11+
This example creates a Raw AES Keyring and then encrypts a custom input EXAMPLE_DATA
12+
with an encryption context. This example also includes some sanity checks for demonstration:
13+
1. Ciphertext and plaintext data are not the same
14+
2. Encryption context is correct in the decrypted message header
15+
3. Decrypted plaintext value matches EXAMPLE_DATA
16+
These sanity checks are for demonstration in the example only. You do not need these in your code.
17+
18+
The Raw AES keyring encrypts data by using the AES-GCM algorithm and a wrapping key that
19+
you specify as a byte array. You can specify only one wrapping key in each Raw AES keyring,
20+
but you can include multiple Raw AES keyrings, alone or with other keyrings, in a multi-keyring.
21+
22+
For more information on how to use Raw AES keyrings, see
23+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-aes-keyring.html
24+
"""
25+
import secrets
26+
import sys
27+
28+
from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders
29+
from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig
30+
from aws_cryptographic_materialproviders.mpl.models import AesWrappingAlg, CreateRawAesKeyringInput
31+
from aws_cryptographic_materialproviders.mpl.references import IKeyring
32+
from typing import Dict
33+
34+
import aws_encryption_sdk
35+
from aws_encryption_sdk import CommitmentPolicy
36+
37+
# TODO-MPL: Remove this as part of removing PYTHONPATH hacks.
38+
MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1])
39+
40+
sys.path.append(MODULE_ROOT_DIR)
41+
42+
EXAMPLE_DATA: bytes = b"Hello World"
43+
44+
45+
def encrypt_and_decrypt_with_keyring():
46+
"""Demonstrate an encrypt/decrypt cycle using a Raw AES keyring.
47+
48+
Usage: encrypt_and_decrypt_with_keyring()
49+
"""
50+
# 1. Instantiate the encryption SDK client.
51+
# This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy,
52+
# which enforces that this client only encrypts using committing algorithm suites and enforces
53+
# that this client will only decrypt encrypted messages that were created with a committing
54+
# algorithm suite.
55+
# This is the default commitment policy if you were to build the client as
56+
# `client = aws_encryption_sdk.EncryptionSDKClient()`.
57+
client = aws_encryption_sdk.EncryptionSDKClient(
58+
commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
59+
)
60+
61+
# 2. The key namespace and key name are defined by you.
62+
# and are used by the Raw AES keyring to determine
63+
# whether it should attempt to decrypt an encrypted data key.
64+
# For more information, see
65+
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-aes-keyring.html
66+
key_name_space = "Some managed raw keys"
67+
key_name = "My 256-bit AES wrapping key"
68+
69+
# 3. Create encryption context.
70+
# Remember that your encryption context is NOT SECRET.
71+
# For more information, see
72+
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
73+
encryption_context: Dict[str, str] = {
74+
"encryption": "context",
75+
"is not": "secret",
76+
"but adds": "useful metadata",
77+
"that can help you": "be confident that",
78+
"the data you are handling": "is what you think it is",
79+
}
80+
81+
# 4. Generate a 256-bit AES key to use with your keyring.
82+
# In practice, you should get this key from a secure key management system such as an HSM.
83+
84+
# Here, the input to secrets.token_bytes() = 32 bytes = 256 bits
85+
static_key = secrets.token_bytes(32)
86+
87+
# 5. Create a Raw AES keyring
88+
mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(
89+
config=MaterialProvidersConfig()
90+
)
91+
92+
keyring_input: CreateRawAesKeyringInput = CreateRawAesKeyringInput(
93+
key_namespace=key_name_space,
94+
key_name=key_name,
95+
wrapping_key=static_key,
96+
wrapping_alg=AesWrappingAlg.ALG_AES256_GCM_IV12_TAG16
97+
)
98+
99+
raw_aes_keyring: IKeyring = mat_prov.create_raw_aes_keyring(
100+
input=keyring_input
101+
)
102+
103+
# 6. Encrypt the data with the encryptionContext
104+
ciphertext, _ = client.encrypt(
105+
source=EXAMPLE_DATA,
106+
keyring=raw_aes_keyring,
107+
encryption_context=encryption_context
108+
)
109+
110+
# 7. Demonstrate that the ciphertext and plaintext are different.
111+
# (This is an example for demonstration; you do not need to do this in your own code.)
112+
assert ciphertext != EXAMPLE_DATA, \
113+
"Ciphertext and plaintext data are the same. Invalid encryption"
114+
115+
# 8. Decrypt your encrypted data using the same keyring you used on encrypt.
116+
plaintext_bytes, dec_header = client.decrypt(
117+
source=ciphertext,
118+
keyring=raw_aes_keyring
119+
)
120+
121+
# 9. Demonstrate that the encryption context is correct in the decrypted message header
122+
# (This is an example for demonstration; you do not need to do this in your own code.)
123+
for k, v in encryption_context.items():
124+
assert v == dec_header.encryption_context[k], \
125+
"Encryption context does not match expected values"
126+
127+
# 10. Demonstrate that the decrypted plaintext is identical to the original plaintext.
128+
# (This is an example for demonstration; you do not need to do this in your own code.)
129+
assert plaintext_bytes == EXAMPLE_DATA
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
This example sets up the Raw RSA Keyring
5+
6+
The Raw RSA keyring performs asymmetric encryption and decryption of data keys in local memory
7+
with RSA public and private keys that you provide. In this example, we define the RSA keys to
8+
encrypt and decrypt the data keys.
9+
10+
You need to generate, store, and protect the private key, preferably in a
11+
hardware security module (HSM) or key management system.
12+
The encryption function encrypts the data key under the RSA public key. The decryption function
13+
decrypts the data key using the private key.
14+
15+
This example creates a Raw RSA Keyring and then encrypts a custom input EXAMPLE_DATA
16+
with an encryption context. This example also includes some sanity checks for demonstration:
17+
1. Ciphertext and plaintext data are not the same
18+
2. Encryption context is correct in the decrypted message header
19+
3. Decrypted plaintext value matches EXAMPLE_DATA
20+
4. The original ciphertext is not decryptable using a keyring with a different RSA key pair
21+
These sanity checks are for demonstration in the example only. You do not need these in your code.
22+
23+
A Raw RSA keyring that encrypts and decrypts must include an asymmetric public key and private
24+
key pair. However, you can encrypt data with a Raw RSA keyring that has only a public key,
25+
and you can decrypt data with a Raw RSA keyring that has only a private key. This example requires
26+
the user to either provide both private and public keys, or not provide any keys and the example
27+
generates both to test encryption and decryption. If you configure a Raw RSA keyring with a
28+
public and private key, be sure that they are part of the same key pair. Some language
29+
implementations of the AWS Encryption SDK will not construct a Raw RSA keyring with keys
30+
from different pairs. Others rely on you to verify that your keys are from the same key pair.
31+
You can include any Raw RSA keyring in a multi-keyring.
32+
33+
For more information on how to use Raw RSA keyrings, see
34+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-rsa-keyring.html
35+
"""
36+
import sys
37+
38+
from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders
39+
from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig
40+
from aws_cryptographic_materialproviders.mpl.models import CreateRawRsaKeyringInput, PaddingScheme
41+
from aws_cryptographic_materialproviders.mpl.references import IKeyring
42+
from cryptography.hazmat.backends import default_backend as crypto_default_backend
43+
from cryptography.hazmat.primitives import serialization as crypto_serialization
44+
from cryptography.hazmat.primitives.asymmetric import rsa
45+
from typing import Dict
46+
47+
import aws_encryption_sdk
48+
from aws_encryption_sdk import CommitmentPolicy
49+
from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError
50+
51+
# TODO-MPL: Remove this as part of removing PYTHONPATH hacks.
52+
MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1])
53+
54+
sys.path.append(MODULE_ROOT_DIR)
55+
56+
EXAMPLE_DATA: bytes = b"Hello World"
57+
58+
59+
def should_generate_new_rsa_key_pair(public_key_file_name, private_key_file_name):
60+
"""Returns True if user doesn't provide keys, and we need to generate them;
61+
Returns False if the user has already provided both public and private keys
62+
Raises a ValueError if the user only provides one of private_key and public_key
63+
64+
Usage: should_generate_new_rsa_key_pair(public_key_file_name, private_key_file_name)
65+
"""
66+
# If only one of public_key and private_key files is provided, raise a ValueError
67+
if (public_key_file_name and not private_key_file_name)\
68+
or (not public_key_file_name and private_key_file_name):
69+
raise ValueError("Either both public and private keys should be provided! Or no keys \
70+
should be provided and the example can create the keys for you!")
71+
72+
# If no keys are provided, we should generate a new rsa key pair, so return True
73+
if not public_key_file_name and not private_key_file_name:
74+
return True
75+
76+
# If both keys are already provided, return False
77+
return False
78+
79+
80+
def generate_rsa_keys():
81+
"""Generates a 4096-bit RSA public and private key pair
82+
83+
Usage: generate_rsa_keys()
84+
"""
85+
ssh_rsa_exponent = 65537
86+
bit_strength = 4096
87+
key = rsa.generate_private_key(
88+
backend=crypto_default_backend(),
89+
public_exponent=ssh_rsa_exponent,
90+
key_size=bit_strength
91+
)
92+
93+
# This example choses a particular type of encoding, format and encryption_algorithm
94+
# Users can choose the PublicFormat, PrivateFormat and encryption_algorithm that align most
95+
# with their use-cases
96+
public_key = key.public_key().public_bytes(
97+
encoding=crypto_serialization.Encoding.PEM,
98+
format=crypto_serialization.PublicFormat.SubjectPublicKeyInfo
99+
)
100+
private_key = key.private_bytes(
101+
encoding=crypto_serialization.Encoding.PEM,
102+
format=crypto_serialization.PrivateFormat.TraditionalOpenSSL,
103+
encryption_algorithm=crypto_serialization.NoEncryption()
104+
)
105+
106+
return public_key, private_key
107+
108+
109+
def create_rsa_keyring(public_key, private_key):
110+
"""Create a Raw RSA keyring using the key pair
111+
112+
Usage: create_rsa_keyring(public_key, private_key)
113+
"""
114+
# 1. The key namespace and key name are defined by you.
115+
# and are used by the Raw RSA keyring
116+
# For more information, see
117+
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-rsa-keyring.html
118+
key_name_space = "Some managed raw keys"
119+
key_name = "My 4096-bit RSA wrapping key"
120+
121+
# 2. Create a Raw RSA keyring
122+
mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(
123+
config=MaterialProvidersConfig()
124+
)
125+
126+
keyring_input: CreateRawRsaKeyringInput = CreateRawRsaKeyringInput(
127+
key_namespace=key_name_space,
128+
key_name=key_name,
129+
padding_scheme=PaddingScheme.OAEP_SHA256_MGF1,
130+
public_key=public_key,
131+
private_key=private_key
132+
)
133+
134+
raw_rsa_keyring: IKeyring = mat_prov.create_raw_rsa_keyring(
135+
input=keyring_input
136+
)
137+
138+
return raw_rsa_keyring
139+
140+
141+
def encrypt_and_decrypt_with_keyring(public_key_file_name=None, private_key_file_name=None):
142+
"""Demonstrate an encrypt/decrypt cycle using a Raw RSA keyring
143+
with user defined keys. If no keys are present, generate new RSA
144+
public and private keys and use them to create a Raw RSA keyring
145+
146+
Usage: encrypt_and_decrypt_with_keyring(public_key_file_name, private_key_file_name)
147+
"""
148+
# 1. Instantiate the encryption SDK client.
149+
# This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy,
150+
# which enforces that this client only encrypts using committing algorithm suites and enforces
151+
# that this client will only decrypt encrypted messages that were created with a committing
152+
# algorithm suite.
153+
# This is the default commitment policy if you were to build the client as
154+
# `client = aws_encryption_sdk.EncryptionSDKClient()`.
155+
client = aws_encryption_sdk.EncryptionSDKClient(
156+
commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
157+
)
158+
159+
# 2. Create encryption context.
160+
# Remember that your encryption context is NOT SECRET.
161+
# For more information, see
162+
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
163+
encryption_context: Dict[str, str] = {
164+
"encryption": "context",
165+
"is not": "secret",
166+
"but adds": "useful metadata",
167+
"that can help you": "be confident that",
168+
"the data you are handling": "is what you think it is",
169+
}
170+
171+
# 3. Create a Raw RSA keyring.
172+
# If you have provided keys in a PEM file, they will be loaded into the keyring.
173+
# Otherwise, a key pair will be randomly generated for you.
174+
175+
# Check if we need to generate an RSA key pair
176+
should_generate_new_rsa_key_pair_bool = \
177+
should_generate_new_rsa_key_pair(public_key_file_name=public_key_file_name,
178+
private_key_file_name=private_key_file_name)
179+
180+
# If user doesn't provide the keys, that is, if should_generate_new_rsa_key_pair_bool is True
181+
# generate a new RSA public and private key pair
182+
if should_generate_new_rsa_key_pair_bool:
183+
public_key, private_key = generate_rsa_keys()
184+
else:
185+
# If user provides the keys, read the keys from the files
186+
with open(public_key_file_name, "r", encoding='utf-8') as f:
187+
public_key = f.read()
188+
189+
# Convert the public key from a string to bytes
190+
public_key = bytes(public_key, 'utf-8')
191+
192+
with open(private_key_file_name, "r", encoding='utf-8') as f:
193+
private_key = f.read()
194+
195+
# Convert the private key from a string to bytes
196+
private_key = bytes(private_key, 'utf-8')
197+
198+
# Create the keyring
199+
raw_rsa_keyring = create_rsa_keyring(public_key=public_key, private_key=private_key)
200+
201+
# 4. Encrypt the data with the encryptionContext
202+
ciphertext, _ = client.encrypt(
203+
source=EXAMPLE_DATA,
204+
keyring=raw_rsa_keyring,
205+
encryption_context=encryption_context
206+
)
207+
208+
# 5. Demonstrate that the ciphertext and plaintext are different.
209+
# (This is an example for demonstration; you do not need to do this in your own code.)
210+
assert ciphertext != EXAMPLE_DATA, \
211+
"Ciphertext and plaintext data are the same. Invalid encryption"
212+
213+
# 6. Decrypt your encrypted data using the same keyring you used on encrypt.
214+
plaintext_bytes, dec_header = client.decrypt(
215+
source=ciphertext,
216+
keyring=raw_rsa_keyring
217+
)
218+
219+
# 7. Demonstrate that the encryption context is correct in the decrypted message header
220+
# (This is an example for demonstration; you do not need to do this in your own code.)
221+
for k, v in encryption_context.items():
222+
assert v == dec_header.encryption_context[k], \
223+
"Encryption context does not match expected values"
224+
225+
# 8. Demonstrate that the decrypted plaintext is identical to the original plaintext.
226+
# (This is an example for demonstration; you do not need to do this in your own code.)
227+
assert plaintext_bytes == EXAMPLE_DATA
228+
229+
# The next part of the example creates a new RSA keyring (for Bob) to demonstrate that
230+
# decryption of the original ciphertext is not possible with a different keyring (Bob's).
231+
# (This is an example for demonstration; you do not need to do this in your own code.)
232+
233+
# 9. Create a new Raw RSA keyring for Bob
234+
# Generate new keys
235+
public_key_bob, private_key_bob = generate_rsa_keys()
236+
237+
# Create the keyring
238+
raw_rsa_keyring_bob = create_rsa_keyring(public_key=public_key_bob, private_key=private_key_bob)
239+
240+
# 10. Test decrypt for the original ciphertext using raw_rsa_keyring_bob
241+
try:
242+
plaintext_bytes_bob, _ = client.decrypt(
243+
source=ciphertext,
244+
keyring=raw_rsa_keyring_bob
245+
)
246+
247+
raise AssertionError("client.decrypt should throw an error of type AWSEncryptionSDKClientError!")
248+
except AWSEncryptionSDKClientError:
249+
pass

0 commit comments

Comments
 (0)