diff --git a/.gitignore b/.gitignore index 78bc3d5b8..24df397ed 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,7 @@ __pycache__ # PyTest .pytest_cache # Ignore key materials generated by examples or tests -test_keys/ +test_keyrings/ # PyCharm .idea/ diff --git a/examples/src/keyrings/aws_kms_discovery_keyring_example.py b/examples/src/keyrings/aws_kms_discovery_keyring_example.py index 24dc111f1..53d9cf1e4 100644 --- a/examples/src/keyrings/aws_kms_discovery_keyring_example.py +++ b/examples/src/keyrings/aws_kms_discovery_keyring_example.py @@ -172,7 +172,8 @@ def encrypt_and_decrypt_with_keyring( # 10. Demonstrate that the decrypted plaintext is identical to the original plaintext. # (This is an example for demonstration; you do not need to do this in your own code.) - assert plaintext_bytes == EXAMPLE_DATA + assert plaintext_bytes == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" # 11. Demonstrate that if a discovery keyring (Bob's) doesn't have the correct AWS Account ID's, # the decrypt will fail with an error message diff --git a/examples/src/keyrings/aws_kms_discovery_multi_keyring_example.py b/examples/src/keyrings/aws_kms_discovery_multi_keyring_example.py index 0830ecb58..adfadec37 100644 --- a/examples/src/keyrings/aws_kms_discovery_multi_keyring_example.py +++ b/examples/src/keyrings/aws_kms_discovery_multi_keyring_example.py @@ -170,4 +170,5 @@ def encrypt_and_decrypt_with_keyring( # 10. Demonstrate that the decrypted plaintext is identical to the original plaintext. # (This is an example for demonstration; you do not need to do this in your own code.) - assert plaintext_bytes == EXAMPLE_DATA + assert plaintext_bytes == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" diff --git a/examples/src/keyrings/aws_kms_keyring_example.py b/examples/src/keyrings/aws_kms_keyring_example.py index fa7ffd12f..4d04ba538 100644 --- a/examples/src/keyrings/aws_kms_keyring_example.py +++ b/examples/src/keyrings/aws_kms_keyring_example.py @@ -116,4 +116,5 @@ def encrypt_and_decrypt_with_keyring( # 9. Demonstrate that the decrypted plaintext is identical to the original plaintext. # (This is an example for demonstration; you do not need to do this in your own code.) - assert plaintext_bytes == EXAMPLE_DATA + assert plaintext_bytes == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" diff --git a/examples/src/keyrings/aws_kms_mrk_keyring_example.py b/examples/src/keyrings/aws_kms_mrk_keyring_example.py index b82748cf9..d5342ba64 100644 --- a/examples/src/keyrings/aws_kms_mrk_keyring_example.py +++ b/examples/src/keyrings/aws_kms_mrk_keyring_example.py @@ -151,4 +151,5 @@ def encrypt_and_decrypt_with_keyring( # 9. Demonstrate that the decrypted plaintext is identical to the original plaintext. # (This is an example for demonstration; you do not need to do this in your own code.) - assert plaintext_bytes == EXAMPLE_DATA + assert plaintext_bytes == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" diff --git a/examples/src/keyrings/aws_kms_mrk_multi_keyring_example.py b/examples/src/keyrings/aws_kms_mrk_multi_keyring_example.py index 38c5b1232..e5445a87c 100644 --- a/examples/src/keyrings/aws_kms_mrk_multi_keyring_example.py +++ b/examples/src/keyrings/aws_kms_mrk_multi_keyring_example.py @@ -143,7 +143,8 @@ def encrypt_and_decrypt_with_keyring( # 8. Demonstrate that the decrypted plaintext is identical to the original plaintext. # (This is an example for demonstration; you do not need to do this in your own code.) - assert plaintext_bytes == EXAMPLE_DATA + assert plaintext_bytes == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" # Demonstrate that a single AwsKmsMrkKeyring configured with a replica of the MRK from the # multi-keyring used to encrypt the data is also capable of decrypting the data. diff --git a/examples/src/keyrings/aws_kms_multi_keyring_example.py b/examples/src/keyrings/aws_kms_multi_keyring_example.py index 0e556f5e3..9662405d6 100644 --- a/examples/src/keyrings/aws_kms_multi_keyring_example.py +++ b/examples/src/keyrings/aws_kms_multi_keyring_example.py @@ -144,7 +144,8 @@ def encrypt_and_decrypt_with_keyring( # 6b. Demonstrate that the decrypted plaintext is identical to the original plaintext. # (This is an example for demonstration; you do not need to do this in your own code.) - assert plaintext_bytes_multi_keyring == EXAMPLE_DATA + assert plaintext_bytes_multi_keyring == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" # Because you used a multi_keyring on Encrypt, you can use either of the two # kms keyrings individually to decrypt the data. @@ -174,7 +175,8 @@ def encrypt_and_decrypt_with_keyring( # 7d. Demonstrate that the decrypted plaintext is identical to the original plaintext. # (This is an example for demonstration; you do not need to do this in your own code.) - assert plaintext_bytes_default_region_kms_keyring == EXAMPLE_DATA + assert plaintext_bytes_default_region_kms_keyring == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" # 8. Demonstrate that you can also successfully decrypt data using a KMS keyring with just the # `second_region_kms_key_id` directly. @@ -201,4 +203,5 @@ def encrypt_and_decrypt_with_keyring( # 8d. Demonstrate that the decrypted plaintext is identical to the original plaintext. # (This is an example for demonstration; you do not need to do this in your own code.) - assert plaintext_bytes_second_region_kms_keyring == EXAMPLE_DATA + assert plaintext_bytes_second_region_kms_keyring == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" diff --git a/examples/src/keyrings/aws_kms_rsa_keyring_example.py b/examples/src/keyrings/aws_kms_rsa_keyring_example.py index 5c0bbe736..536581eb8 100644 --- a/examples/src/keyrings/aws_kms_rsa_keyring_example.py +++ b/examples/src/keyrings/aws_kms_rsa_keyring_example.py @@ -122,4 +122,5 @@ def encrypt_and_decrypt_with_keyring( # 9. Demonstrate that the decrypted plaintext is identical to the original plaintext. # (This is an example for demonstration; you do not need to do this in your own code.) - assert plaintext_bytes == EXAMPLE_DATA + assert plaintext_bytes == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" diff --git a/examples/src/keyrings/file_streaming_example.py b/examples/src/keyrings/file_streaming_example.py new file mode 100644 index 000000000..b7352ba0e --- /dev/null +++ b/examples/src/keyrings/file_streaming_example.py @@ -0,0 +1,152 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example demonstrates file streaming for encryption and decryption. + +File streaming is useful when the plaintext or ciphertext file/data is too large to load into +memory. Therefore, the AWS Encryption SDK allows users to stream the data, instead of loading it +all at once in memory. In this example, we demonstrate file streaming for encryption and decryption +using a Raw AES keyring. However, you can use any keyring with streaming. + +This example creates a Raw AES Keyring and then encrypts an input stream from the file +`plaintext_filename` with an encryption context to an output (encrypted) file `ciphertext_filename`. +It then decrypts the ciphertext from `ciphertext_filename` to a new file `decrypted_filename`. +This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Encryption context is correct in the decrypted message header +3. Decrypted plaintext value matches EXAMPLE_DATA +These sanity checks are for demonstration in the example only. You do not need these in your code. + +For more information on how to use Raw AES keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-aes-keyring.html + +See raw_aes_keyring_example.py in the same directory for another raw AES keyring example +in the AWS Encryption SDK for Python. +""" +import filecmp +import secrets +import sys + +from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig +from aws_cryptographic_materialproviders.mpl.models import AesWrappingAlg, CreateRawAesKeyringInput +from aws_cryptographic_materialproviders.mpl.references import IKeyring +from typing import Dict + +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy + +# TODO-MPL: Remove this as part of removing PYTHONPATH hacks. +MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1]) + +sys.path.append(MODULE_ROOT_DIR) + + +def encrypt_and_decrypt_with_keyring( + plaintext_filename: str, + ciphertext_filename: str, + decrypted_filename: str +): + """Demonstrate a streaming encrypt/decrypt cycle. + + Usage: encrypt_and_decrypt_with_keyring(plaintext_filename + ciphertext_filename + decrypted_filename) + :param plaintext_filename: filename of the plaintext data + :type plaintext_filename: string + :param ciphertext_filename: filename of the ciphertext data + :type ciphertext_filename: string + :param decrypted_filename: filename of the decrypted data + :type decrypted_filename: string + """ + # 1. Instantiate the encryption SDK client. + # This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy, + # which enforces that this client only encrypts using committing algorithm suites and enforces + # that this client will only decrypt encrypted messages that were created with a committing + # algorithm suite. + # This is the default commitment policy if you were to build the client as + # `client = aws_encryption_sdk.EncryptionSDKClient()`. + client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT + ) + + # 2. The key namespace and key name are defined by you. + # and are used by the Raw AES keyring to determine + # whether it should attempt to decrypt an encrypted data key. + # For more information, see + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-aes-keyring.html + key_name_space = "Some managed raw keys" + key_name = "My 256-bit AES wrapping key" + + # 3. Create encryption context. + # Remember that your encryption context is NOT SECRET. + # For more information, see + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context: Dict[str, str] = { + "encryption": "context", + "is not": "secret", + "but adds": "useful metadata", + "that can help you": "be confident that", + "the data you are handling": "is what you think it is", + } + + # 4. Generate a 256-bit AES key to use with your keyring. + # In practice, you should get this key from a secure key management system such as an HSM. + + # Here, the input to secrets.token_bytes() = 32 bytes = 256 bits + static_key = secrets.token_bytes(32) + + # 5. Create a Raw AES keyring + # We choose to use a raw AES keyring, but any keyring can be used with streaming. + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + + keyring_input: CreateRawAesKeyringInput = CreateRawAesKeyringInput( + key_namespace=key_name_space, + key_name=key_name, + wrapping_key=static_key, + wrapping_alg=AesWrappingAlg.ALG_AES256_GCM_IV12_TAG16 + ) + + raw_aes_keyring: IKeyring = mat_prov.create_raw_aes_keyring( + input=keyring_input + ) + + # 6. Encrypt the data stream with the encryptionContext + with open(plaintext_filename, 'rb') as pt_file, open(ciphertext_filename, 'wb') as ct_file: + with client.stream( + mode='e', + source=pt_file, + keyring=raw_aes_keyring, + encryption_context=encryption_context + ) as encryptor: + for chunk in encryptor: + ct_file.write(chunk) + + # 7. Demonstrate that the ciphertext and plaintext are different. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert not filecmp.cmp(plaintext_filename, ciphertext_filename), \ + "Ciphertext and plaintext data are the same. Invalid encryption" + + # 8. Decrypt your encrypted data stream using the same keyring you used on encrypt. + with open(ciphertext_filename, 'rb') as ct_file, open(decrypted_filename, 'wb') as pt_file: + with client.stream( + mode='d', + source=ct_file, + keyring=raw_aes_keyring, + encryption_context=encryption_context + ) as decryptor: + for chunk in decryptor: + pt_file.write(chunk) + + # 9. Demonstrate that the encryption context is correct in the decrypted message header + # (This is an example for demonstration; you do not need to do this in your own code.) + for k, v in encryption_context.items(): + assert v == decryptor.header.encryption_context[k], \ + "Encryption context does not match expected values" + + # 10. Demonstrate that the decrypted plaintext is identical to the original plaintext. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert filecmp.cmp(plaintext_filename, decrypted_filename), \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" diff --git a/examples/src/keyrings/hierarchical_keyring_example.py b/examples/src/keyrings/hierarchical_keyring_example.py index 1edcab285..7020b68b4 100644 --- a/examples/src/keyrings/hierarchical_keyring_example.py +++ b/examples/src/keyrings/hierarchical_keyring_example.py @@ -101,13 +101,13 @@ def encrypt_and_decrypt_with_keyring( ) # 4. Call CreateKey to create two new active branch keys - branch_key_id_A: str = keystore.create_key(input=CreateKeyInput()).branch_key_identifier - branch_key_id_B: str = keystore.create_key(input=CreateKeyInput()).branch_key_identifier + branch_key_id_a: str = keystore.create_key(input=CreateKeyInput()).branch_key_identifier + branch_key_id_b: str = keystore.create_key(input=CreateKeyInput()).branch_key_identifier # 5. Create a branch key supplier that maps the branch key id to a more readable format branch_key_id_supplier: IBranchKeyIdSupplier = ExampleBranchKeyIdSupplier( - tenant_1_id=branch_key_id_A, - tenant_2_id=branch_key_id_B, + tenant_1_id=branch_key_id_a, + tenant_2_id=branch_key_id_b, ) # 6. Create the Hierarchical Keyring. @@ -135,7 +135,7 @@ def encrypt_and_decrypt_with_keyring( # be used to encrypt data. # Create encryption context for TenantA - encryption_context_A: Dict[str, str] = { + encryption_context_a: Dict[str, str] = { "tenant": "TenantA", "encryption": "context", "is not": "secret", @@ -145,7 +145,7 @@ def encrypt_and_decrypt_with_keyring( } # Create encryption context for TenantB - encryption_context_B: Dict[str, str] = { + encryption_context_b: Dict[str, str] = { "tenant": "TenantB", "encryption": "context", "is not": "secret", @@ -155,22 +155,22 @@ def encrypt_and_decrypt_with_keyring( } # 8. Encrypt the data with encryptionContextA & encryptionContextB - ciphertext_A, _ = client.encrypt( + ciphertext_a, _ = client.encrypt( source=EXAMPLE_DATA, keyring=hierarchical_keyring, - encryption_context=encryption_context_A + encryption_context=encryption_context_a ) - ciphertext_B, _ = client.encrypt( + ciphertext_b, _ = client.encrypt( source=EXAMPLE_DATA, keyring=hierarchical_keyring, - encryption_context=encryption_context_B + encryption_context=encryption_context_b ) # 9. To attest that TenantKeyB cannot decrypt a message written by TenantKeyA, # let's construct more restrictive hierarchical keyrings. - keyring_input_A: CreateAwsKmsHierarchicalKeyringInput = CreateAwsKmsHierarchicalKeyringInput( + keyring_input_a: CreateAwsKmsHierarchicalKeyringInput = CreateAwsKmsHierarchicalKeyringInput( key_store=keystore, - branch_key_id=branch_key_id_A, + branch_key_id=branch_key_id_a, ttl_seconds=600, cache=CacheTypeDefault( value=DefaultCache( @@ -179,13 +179,13 @@ def encrypt_and_decrypt_with_keyring( ), ) - hierarchical_keyring_A: IKeyring = mat_prov.create_aws_kms_hierarchical_keyring( - input=keyring_input_A + hierarchical_keyring_a: IKeyring = mat_prov.create_aws_kms_hierarchical_keyring( + input=keyring_input_a ) - keyring_input_B: CreateAwsKmsHierarchicalKeyringInput = CreateAwsKmsHierarchicalKeyringInput( + keyring_input_b: CreateAwsKmsHierarchicalKeyringInput = CreateAwsKmsHierarchicalKeyringInput( key_store=keystore, - branch_key_id=branch_key_id_B, + branch_key_id=branch_key_id_b, ttl_seconds=600, cache=CacheTypeDefault( value=DefaultCache( @@ -194,8 +194,8 @@ def encrypt_and_decrypt_with_keyring( ), ) - hierarchical_keyring_B: IKeyring = mat_prov.create_aws_kms_hierarchical_keyring( - input=keyring_input_B + hierarchical_keyring_b: IKeyring = mat_prov.create_aws_kms_hierarchical_keyring( + input=keyring_input_b ) # 10. Demonstrate that data encrypted by one tenant's key @@ -205,8 +205,8 @@ def encrypt_and_decrypt_with_keyring( # This will fail and raise a AWSEncryptionSDKClientError, which we swallow ONLY for demonstration purposes. try: client.decrypt( - source=ciphertext_A, - keyring=hierarchical_keyring_B + source=ciphertext_a, + keyring=hierarchical_keyring_b ) except AWSEncryptionSDKClientError: pass @@ -215,21 +215,23 @@ def encrypt_and_decrypt_with_keyring( # This will fail and raise a AWSEncryptionSDKClientError, which we swallow ONLY for demonstration purposes. try: client.decrypt( - source=ciphertext_B, - keyring=hierarchical_keyring_A + source=ciphertext_b, + keyring=hierarchical_keyring_a ) except AWSEncryptionSDKClientError: pass # 10. Demonstrate that data encrypted by one tenant's branch key can be decrypted by that tenant, # and that the decrypted data matches the input data. - plaintext_bytes_A, _ = client.decrypt( - source=ciphertext_A, - keyring=hierarchical_keyring_A + plaintext_bytes_a, _ = client.decrypt( + source=ciphertext_a, + keyring=hierarchical_keyring_a ) - assert plaintext_bytes_A == EXAMPLE_DATA - plaintext_bytes_B, _ = client.decrypt( - source=ciphertext_B, - keyring=hierarchical_keyring_B + assert plaintext_bytes_a == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" + plaintext_bytes_b, _ = client.decrypt( + source=ciphertext_b, + keyring=hierarchical_keyring_b ) - assert plaintext_bytes_B == EXAMPLE_DATA + assert plaintext_bytes_b == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" diff --git a/examples/src/keyrings/migration_set_commitment_policy_example.py b/examples/src/keyrings/migration_set_commitment_policy_example.py new file mode 100644 index 000000000..0a9e6a3a6 --- /dev/null +++ b/examples/src/keyrings/migration_set_commitment_policy_example.py @@ -0,0 +1,128 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example configures a client with a specific commitment policy for the +AWS Encryption SDK client, then encrypts and decrypts data using an AWS KMS Keyring. + +The commitment policy in this example (FORBID_ENCRYPT_ALLOW_DECRYPT) should only be +used as part of a migration from version 1.x to 2.x, or for advanced users with +specialized requirements. Most AWS Encryption SDK users should use the default +commitment policy (REQUIRE_ENCRYPT_REQUIRE_DECRYPT). + +This example creates a KMS Keyring and then encrypts a custom input EXAMPLE_DATA +with an encryption context for the commitment policy FORBID_ENCRYPT_ALLOW_DECRYPT. +This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Encryption context is correct in the decrypted message header +3. Decrypted plaintext value matches EXAMPLE_DATA +These sanity checks are for demonstration in the example only. You do not need these in your code. + +For more information on setting your commitment policy, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#commitment-policy +""" +import sys + +import boto3 +from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig +from aws_cryptographic_materialproviders.mpl.models import CreateAwsKmsKeyringInput +from aws_cryptographic_materialproviders.mpl.references import IKeyring +from typing import Dict + +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy + +# TODO-MPL: Remove this as part of removing PYTHONPATH hacks. +MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1]) + +sys.path.append(MODULE_ROOT_DIR) + +EXAMPLE_DATA: bytes = b"Hello World" + + +def encrypt_and_decrypt_with_keyring( + kms_key_id: str +): + """Demonstrate how to set your commitment policy for migration. + + Usage: encrypt_and_decrypt_with_keyring(kms_key_id) + :param kms_key_id: KMS Key identifier for the KMS key you want to use for encryption and + decryption of your data keys. + :type kms_key_id: string + + For more information on KMS Key identifiers, see + https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id + """ + # 1. Instantiate the encryption SDK client. + # This example builds the client with the FORBID_ENCRYPT_ALLOW_DECRYPT commitment policy, + # which enforces that this client cannot encrypt with key commitment + # and it can decrypt ciphertexts encrypted with or without key commitment. + # The default commitment policy if you were to build the client as + # `client = aws_encryption_sdk.EncryptionSDKClient()` is REQUIRE_ENCRYPT_REQUIRE_DECRYPT. + # We recommend that AWS Encryption SDK users use the default commitment policy + # (REQUIRE_ENCRYPT_REQUIRE_DECRYPT) whenever possible. + client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + ) + + # 2. Create a boto3 client for KMS. + kms_client = boto3.client('kms', region_name="us-west-2") + + # 3. Create encryption context. + # Remember that your encryption context is NOT SECRET. + # For more information, see + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context: Dict[str, str] = { + "encryption": "context", + "is not": "secret", + "but adds": "useful metadata", + "that can help you": "be confident that", + "the data you are handling": "is what you think it is", + } + + # 4. Create a KMS keyring + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + + keyring_input: CreateAwsKmsKeyringInput = CreateAwsKmsKeyringInput( + kms_key_id=kms_key_id, + kms_client=kms_client + ) + + kms_keyring: IKeyring = mat_prov.create_aws_kms_keyring( + input=keyring_input + ) + + # 5. Encrypt the data with the encryptionContext. Make sure you use a non-committing algorithm + # with the commitment policy FORBID_ENCRYPT_ALLOW_DECRYPT. Otherwise client.encrypt() will throw + # aws_encryption_sdk.exceptions.ActionNotAllowedError. By default for + # FORBID_ENCRYPT_ALLOW_DECRYPT, the algorithm used is + # AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384 which is a non-committing algorithm. + ciphertext, _ = client.encrypt( + source=EXAMPLE_DATA, + keyring=kms_keyring, + encryption_context=encryption_context + ) + + # 6. Demonstrate that the ciphertext and plaintext are different. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert ciphertext != EXAMPLE_DATA, \ + "Ciphertext and plaintext data are the same. Invalid encryption" + + # 7. Decrypt your encrypted data using the same keyring you used on encrypt. + plaintext_bytes, dec_header = client.decrypt( + source=ciphertext, + keyring=kms_keyring + ) + + # 8. Demonstrate that the encryption context is correct in the decrypted message header + # (This is an example for demonstration; you do not need to do this in your own code.) + for k, v in encryption_context.items(): + assert v == dec_header.encryption_context[k], \ + "Encryption context does not match expected values" + + # 9. Demonstrate that the decrypted plaintext is identical to the original plaintext. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert plaintext_bytes == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" diff --git a/examples/src/keyrings/multi_keyring_example.py b/examples/src/keyrings/multi_keyring_example.py index 58e4839ac..fd9968f59 100644 --- a/examples/src/keyrings/multi_keyring_example.py +++ b/examples/src/keyrings/multi_keyring_example.py @@ -175,7 +175,8 @@ def encrypt_and_decrypt_with_keyring( # 10b. Demonstrate that the decrypted plaintext is identical to the original plaintext. # (This is an example for demonstration; you do not need to do this in your own code.) - assert plaintext_bytes_multi_keyring == EXAMPLE_DATA + assert plaintext_bytes_multi_keyring == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" # Because you used a multi_keyring on Encrypt, you can use either the # `kms_keyring` or `raw_aes_keyring` individually to decrypt the data. @@ -192,7 +193,8 @@ def encrypt_and_decrypt_with_keyring( # 11b. Demonstrate that the decrypted plaintext is identical to the original plaintext. # (This is an example for demonstration; you do not need to do this in your own code.) - assert plaintext_bytes_kms_keyring == EXAMPLE_DATA + assert plaintext_bytes_kms_keyring == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" # 12. Demonstrate that you can also successfully decrypt data using the `raw_aes_keyring` # directly. @@ -206,4 +208,5 @@ def encrypt_and_decrypt_with_keyring( # 12b. Demonstrate that the decrypted plaintext is identical to the original plaintext. # (This is an example for demonstration; you do not need to do this in your own code.) - assert plaintext_bytes_raw_aes_keyring == EXAMPLE_DATA + assert plaintext_bytes_raw_aes_keyring == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" diff --git a/examples/src/keyrings/raw_aes_keyring_example.py b/examples/src/keyrings/raw_aes_keyring_example.py index ee0ab7618..be37886da 100644 --- a/examples/src/keyrings/raw_aes_keyring_example.py +++ b/examples/src/keyrings/raw_aes_keyring_example.py @@ -126,4 +126,5 @@ def encrypt_and_decrypt_with_keyring(): # 10. Demonstrate that the decrypted plaintext is identical to the original plaintext. # (This is an example for demonstration; you do not need to do this in your own code.) - assert plaintext_bytes == EXAMPLE_DATA + assert plaintext_bytes == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" diff --git a/examples/src/keyrings/raw_rsa_keyring_example.py b/examples/src/keyrings/raw_rsa_keyring_example.py index 49d868a86..faf617209 100644 --- a/examples/src/keyrings/raw_rsa_keyring_example.py +++ b/examples/src/keyrings/raw_rsa_keyring_example.py @@ -224,7 +224,8 @@ def encrypt_and_decrypt_with_keyring(public_key_file_name=None, private_key_file # 8. Demonstrate that the decrypted plaintext is identical to the original plaintext. # (This is an example for demonstration; you do not need to do this in your own code.) - assert plaintext_bytes == EXAMPLE_DATA + assert plaintext_bytes == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" # The next part of the example creates a new RSA keyring (for Bob) to demonstrate that # decryption of the original ciphertext is not possible with a different keyring (Bob's). diff --git a/examples/src/keyrings/required_encryption_context_cmm.py b/examples/src/keyrings/required_encryption_context_cmm.py index e0c19697c..3f106c5ee 100644 --- a/examples/src/keyrings/required_encryption_context_cmm.py +++ b/examples/src/keyrings/required_encryption_context_cmm.py @@ -108,26 +108,28 @@ def encrypt_and_decrypt_with_keyring( } # 8. Decrypt the data - plaintext_bytes_A, _ = client.decrypt( + plaintext_bytes_a, _ = client.decrypt( source=ciphertext, materials_manager=required_ec_cmm, encryption_context=reproduced_encryption_context ) - assert plaintext_bytes_A == EXAMPLE_DATA + assert plaintext_bytes_a == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" # We can also decrypt using the underlying CMM, # but must also provide the reproduced encryption context - plaintext_bytes_A, _ = client.decrypt( + plaintext_bytes_a, _ = client.decrypt( source=ciphertext, materials_manager=underlying_cmm, encryption_context=reproduced_encryption_context ) - assert plaintext_bytes_A == EXAMPLE_DATA + assert plaintext_bytes_a == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" # 9. Extra: Demonstrate that if we don't provide the reproduced encryption context, # decryption will fail. try: - plaintext_bytes_A, _ = client.decrypt( + plaintext_bytes_a, _ = client.decrypt( source=ciphertext, materials_manager=required_ec_cmm, # No reproduced encryption context for required EC CMM-produced message makes decryption fail. @@ -143,7 +145,7 @@ def encrypt_and_decrypt_with_keyring( # Same for the default CMM; # If we don't provide the reproduced encryption context, decryption will fail. try: - plaintext_bytes_A, _ = client.decrypt( + plaintext_bytes_a, _ = client.decrypt( source=ciphertext, materials_manager=required_ec_cmm, # No reproduced encryption context for required EC CMM-produced message makes decryption fail. diff --git a/examples/src/keyrings/set_encryption_algorithm_suite_example.py b/examples/src/keyrings/set_encryption_algorithm_suite_example.py new file mode 100644 index 000000000..c14eb4bb3 --- /dev/null +++ b/examples/src/keyrings/set_encryption_algorithm_suite_example.py @@ -0,0 +1,151 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example demonstrates how to set an algorithm suite while using the Raw AES Keyring +in the AWS Encryption SDK. + +The algorithm suite used in the encrypt() method is the algorithm used to protect your +data using the data key. By setting this algorithm, you can configure the algorithm used +to encrypt and decrypt your data. + +Algorithm suites can be set in a similar manner in other keyrings as well. However, +please make sure that you're using a logical algorithm suite that is compatible with your +keyring. For more information on algorithm suites supported by the AWS Encryption SDK, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/supported-algorithms.html + +The AES wrapping algorithm (AesWrappingAlg.ALG_AES256_GCM_IV12_TAG16) protects your data key using +the user-provided wrapping key. In contrast, the algorithm suite used in the encrypt() method +is the algorithm used to protect your data using the data key. This example demonstrates setting the +latter, which is the algorithm suite for protecting your data. When the commitment policy is +REQUIRE_ENCRYPT_REQUIRE_DECRYPT, the default algorithm used in the encrypt method is +AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384, which is a committing and signing algorithm. +Signature verification ensures the integrity of a digital message as it goes across trust +boundaries. However, signature verification adds a significant performance cost to encryption +and decryption. If encryptors and decryptors are equally trusted, we can consider using an algorithm +suite that does not include signing. This example sets the algorithm suite as +AES_256_GCM_HKDF_SHA512_COMMIT_KEY, which is a committing but non-signing algorithm. +For more information on digital signatures, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#digital-sigs + +This example creates a Raw AES Keyring and then encrypts a custom input EXAMPLE_DATA +with an encryption context and the algorithm suite AES_256_GCM_HKDF_SHA512_COMMIT_KEY. +This example also includes some sanity checks for demonstration: +1. Ciphertext and plaintext data are not the same +2. Encryption context is correct in the decrypted message header +3. Decrypted plaintext value matches EXAMPLE_DATA +These sanity checks are for demonstration in the example only. You do not need these in your code. + +For more information on how to use Raw AES keyrings, see +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-aes-keyring.html +""" +import secrets +import sys + +from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders +from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig +from aws_cryptographic_materialproviders.mpl.models import AesWrappingAlg, CreateRawAesKeyringInput +from aws_cryptographic_materialproviders.mpl.references import IKeyring +from typing import Dict + +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy +from aws_encryption_sdk.identifiers import AlgorithmSuite + +# TODO-MPL: Remove this as part of removing PYTHONPATH hacks. +MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1]) + +sys.path.append(MODULE_ROOT_DIR) + +EXAMPLE_DATA: bytes = b"Hello World" + + +def encrypt_and_decrypt_with_keyring(): + """Demonstrate an encrypt/decrypt cycle using a Raw AES keyring. + + Usage: encrypt_and_decrypt_with_keyring() + """ + # 1. Instantiate the encryption SDK client. + # This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy, + # which enforces that this client only encrypts using committing algorithm suites and enforces + # that this client will only decrypt encrypted messages that were created with a committing + # algorithm suite. + # This is the default commitment policy if you were to build the client as + # `client = aws_encryption_sdk.EncryptionSDKClient()`. + client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT + ) + + # 2. The key namespace and key name are defined by you. + # and are used by the Raw AES keyring to determine + # whether it should attempt to decrypt an encrypted data key. + # For more information, see + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-aes-keyring.html + key_name_space = "Some managed raw keys" + key_name = "My 256-bit AES wrapping key" + + # 3. Create encryption context. + # Remember that your encryption context is NOT SECRET. + # For more information, see + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context: Dict[str, str] = { + "encryption": "context", + "is not": "secret", + "but adds": "useful metadata", + "that can help you": "be confident that", + "the data you are handling": "is what you think it is", + } + + # 4. Generate a 256-bit AES wrapping key to use with your keyring. + # In practice, you should get this key from a secure key management system such as an HSM. + + # Here, the input to secrets.token_bytes() = 32 bytes = 256 bits + static_key = secrets.token_bytes(32) + + # 5. Create a Raw AES keyring + mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders( + config=MaterialProvidersConfig() + ) + + # The wrapping algorithm here is NOT the algorithm suite we set in this example. + keyring_input: CreateRawAesKeyringInput = CreateRawAesKeyringInput( + key_namespace=key_name_space, + key_name=key_name, + wrapping_key=static_key, + wrapping_alg=AesWrappingAlg.ALG_AES256_GCM_IV12_TAG16 + ) + + raw_aes_keyring: IKeyring = mat_prov.create_raw_aes_keyring( + input=keyring_input + ) + + # 6. Encrypt the data with the encryptionContext. + # This is the important step in this example where we specify the algorithm suite + # you want to use for encrypting your data + ciphertext, _ = client.encrypt( + source=EXAMPLE_DATA, + keyring=raw_aes_keyring, + encryption_context=encryption_context, + algorithm=AlgorithmSuite.AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ) + + # 7. Demonstrate that the ciphertext and plaintext are different. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert ciphertext != EXAMPLE_DATA, \ + "Ciphertext and plaintext data are the same. Invalid encryption" + + # 8. Decrypt your encrypted data using the same keyring you used on encrypt. + plaintext_bytes, dec_header = client.decrypt( + source=ciphertext, + keyring=raw_aes_keyring + ) + + # 9. Demonstrate that the encryption context is correct in the decrypted message header + # (This is an example for demonstration; you do not need to do this in your own code.) + for k, v in encryption_context.items(): + assert v == dec_header.encryption_context[k], \ + "Encryption context does not match expected values" + + # 10. Demonstrate that the decrypted plaintext is identical to the original plaintext. + # (This is an example for demonstration; you do not need to do this in your own code.) + assert plaintext_bytes == EXAMPLE_DATA, \ + "Decrypted plaintext should be identical to the original plaintext. Invalid decryption" diff --git a/examples/test/keyrings/test_i_file_streaming_example.py b/examples/test/keyrings/test_i_file_streaming_example.py new file mode 100644 index 000000000..2e502f185 --- /dev/null +++ b/examples/test/keyrings/test_i_file_streaming_example.py @@ -0,0 +1,62 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Test suite for the file streaming keyring example.""" +import os + +import pytest + +from ...src.keyrings.file_streaming_example import encrypt_and_decrypt_with_keyring + +pytestmark = [pytest.mark.examples] + + +def test_encrypt_and_decrypt_with_keyring(): + """Test function for encrypt and decrypt for file streaming example using Raw AES keyring.""" + test_keyrings_directory = 'test_keyrings' + if not os.path.exists(test_keyrings_directory): + os.makedirs(test_keyrings_directory) + + # Define the filename of the input plaintext data. + plaintext_filename = test_keyrings_directory + '/my-secret-data.dat' + + # Define the plaintext data to be encrypted and decrypted. + plaintext_data = '''Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Praesent non feugiat leo. Aenean iaculis tellus ut velit consectetur, +quis convallis orci eleifend. Sed eu dictum sapien. Nulla facilisi. Suspendisse potenti. +Proin vehicula vehicula maximus. Donec varius et elit vel rutrum. Nulla lacinia neque turpis +quis consequat orci pharetra et. Etiam consequat ullamcorper mauris. Vivamus molestie mollis +mauris a gravida. Curabitur sed bibendum nisl. Cras varius tortor non erat sodales, quis congu +tellus laoreet. Etiam fermentum purus eu diam sagittis, vitae commodo est vehicula. +Nulla feugiat viverra orci vel interdum. Quisque pulvinar elit eget nulla facilisis varius. +Mauris at suscipit sem. Aliquam in purus ut velit fringilla volutpat id non mi. +Curabitur quis nunc eleifend, ornare lectus non, fringilla quam. Nam maximus volutpat placerat. +Nulla ullamcorper lorem velit, nec sagittis ex tristique posuere. Aliquam fringilla magna commod +libero faucibus tempor. Vestibulum non ligula tincidunt, finibus sapien in, sollicitudin +ex. Pellentesque congue laoreet mi in condimentum. Cras convallis nisi ac nunc tincidunt +venenatis. Suspendisse urna elit, cursus eu lacus a, aliquet porttitor mi. +Nulla vel congue nibh, sed condimentum dui. Ut ante ligula, blandit eu finibus nec, +scelerisque quis eros. Maecenas gravida odio eget nibh dictum, dictum varius lacus interdum. +Integer quis nulla vulputate, rhoncus diam vitae, mollis mauris. Sed ut porttitor dolor. +Fusce ut justo a ex bibendum imperdiet nec sit amet magna. Sed ullamcorper luctus augue, +tempor viverra elit interdum sed. Cras sit amet arcu eu turpis molestie sollicitudin. +Curabitur fermentum varius nibh, ut aliquet nisi. Aliquam id tempus tellus. +Nulla porttitor nulla at nibh interdum, quis sollicitudin erat egestas. +Ut blandit mauris quis efficitur efficitur. Morbi neque sapien, posuere ut aliquam eget, +aliquam at velit. Morbi sit amet rhoncus felis, et hendrerit sem. Nulla porta dictum ligula +eget iaculis. Cras lacinia ligula quis risus ultrices, sed consectetur metus imperdiet. +Nullam id enim vestibulum nibh ultricies auctor. Morbi neque lacus, faucibus vitae commodo quis, +malesuada sed velit.''' + + # Write plaintext data to plaintext_filename file + with open(plaintext_filename, "w", encoding="utf-8") as f: + f.write(plaintext_data) + + # Define the filename of the encrypted data. + ciphertext_filename = test_keyrings_directory + '/my-encrypted-data.ct' + + # Define the filename of the decrypted data. + decrypted_filename = test_keyrings_directory + '/my-decrypted-data.dat' + + encrypt_and_decrypt_with_keyring(plaintext_filename, + ciphertext_filename, + decrypted_filename) diff --git a/examples/test/keyrings/test_i_migration_set_commitment_policy_example.py b/examples/test/keyrings/test_i_migration_set_commitment_policy_example.py new file mode 100644 index 000000000..7580ed670 --- /dev/null +++ b/examples/test/keyrings/test_i_migration_set_commitment_policy_example.py @@ -0,0 +1,14 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Test suite for the migration_set_commitment_policy_example.""" +import pytest + +from ...src.keyrings.migration_set_commitment_policy_example import encrypt_and_decrypt_with_keyring + +pytestmark = [pytest.mark.examples] + + +def test_encrypt_and_decrypt_with_keyring(): + """Test function for setting commitment policy using the AWS KMS Keyring example.""" + kms_key_id = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" + encrypt_and_decrypt_with_keyring(kms_key_id) diff --git a/examples/test/keyrings/test_i_raw_rsa_keyring_example.py b/examples/test/keyrings/test_i_raw_rsa_keyring_example.py index e036eb0b0..87786b4b3 100644 --- a/examples/test/keyrings/test_i_raw_rsa_keyring_example.py +++ b/examples/test/keyrings/test_i_raw_rsa_keyring_example.py @@ -32,13 +32,13 @@ def test_encrypt_and_decrypt_with_keyring_with_user_defined_keys(): user_public_key = user_public_key.decode('utf-8') user_private_key = user_private_key.decode('utf-8') - test_keys_directory = 'test_keys' - if not os.path.exists(test_keys_directory): - os.makedirs(test_keys_directory) + test_keyrings_directory = 'test_keyrings' + if not os.path.exists(test_keyrings_directory): + os.makedirs(test_keyrings_directory) # Define the file names for the keys - user_public_key_file_name = test_keys_directory + '/user_public_key_file_name.pem' - user_private_key_file_name = test_keys_directory + '/user_private_key_file_name.pem' + user_public_key_file_name = test_keyrings_directory + '/user_public_key_file_name.pem' + user_private_key_file_name = test_keyrings_directory + '/user_private_key_file_name.pem' # Write the public key to the file with open(user_public_key_file_name, "w", encoding="utf-8") as f: @@ -65,12 +65,12 @@ def test_encrypt_and_decrypt_fails_if_user_provides_only_public_key(): # Convert the public key to string user_public_key = user_public_key.decode('utf-8') - test_keys_directory = 'test_keys' - if not os.path.exists(test_keys_directory): - os.makedirs(test_keys_directory) + test_keyrings_directory = 'test_keyrings' + if not os.path.exists(test_keyrings_directory): + os.makedirs(test_keyrings_directory) # Define the file name for the public key - user_public_key_file_name = test_keys_directory + '/user_public_key_file_name.pem' + user_public_key_file_name = test_keyrings_directory + '/user_public_key_file_name.pem' # Write the public key to the file with open(user_public_key_file_name, "w", encoding="utf-8") as f: @@ -97,12 +97,12 @@ def test_encrypt_and_decrypt_fails_if_user_provides_only_private_key(): # Convert the private key to string user_private_key = user_private_key.decode('utf-8') - test_keys_directory = 'test_keys' - if not os.path.exists(test_keys_directory): - os.makedirs(test_keys_directory) + test_keyrings_directory = 'test_keyrings' + if not os.path.exists(test_keyrings_directory): + os.makedirs(test_keyrings_directory) # Define the file name for the private key - user_private_key_file_name = test_keys_directory + '/user_private_key_file_name.pem' + user_private_key_file_name = test_keyrings_directory + '/user_private_key_file_name.pem' # Write the private key to the file with open(user_private_key_file_name, "w", encoding="utf-8") as f: diff --git a/examples/test/keyrings/test_i_set_encryption_algorithm_suite_example.py b/examples/test/keyrings/test_i_set_encryption_algorithm_suite_example.py new file mode 100644 index 000000000..0703bc961 --- /dev/null +++ b/examples/test/keyrings/test_i_set_encryption_algorithm_suite_example.py @@ -0,0 +1,13 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Test suite for the Set Algorithm Suite example for a Raw AES keyring.""" +import pytest + +from ...src.keyrings.set_encryption_algorithm_suite_example import encrypt_and_decrypt_with_keyring + +pytestmark = [pytest.mark.examples] + + +def test_encrypt_and_decrypt_with_keyring(): + """Test function for setting an algorithm suite in a Raw AES Keyring.""" + encrypt_and_decrypt_with_keyring()