diff --git a/examples/README.md b/examples/README.md index 592872232..c4c0e67ec 100644 --- a/examples/README.md +++ b/examples/README.md @@ -33,10 +33,13 @@ We start with AWS KMS examples, then show how to use other wrapping keys. * Using AWS Key Management Service (AWS KMS) * How to use one AWS KMS CMK * [with keyrings](./src/keyring/aws_kms/single_cmk.py) + * [with master key providers](./src/master_key_provider/aws_kms/single_cmk.py) * How to use multiple AWS KMS CMKs in different regions * [with keyrings](./src/keyring/aws_kms/multiple_regions.py) + * [with master key providers](./src/master_key_provider/aws_kms/multiple_regions.py) * How to decrypt when you don't know the CMK * [with keyrings](./src/keyring/aws_kms/discovery_decrypt.py) + * [with master key providers](./src/master_key_provider/aws_kms/discovery_decrypt.py) * How to decrypt within a region * [with keyrings](./src/keyring/aws_kms/discovery_decrypt_in_region_only.py) * How to decrypt with a preferred region but failover to others @@ -44,15 +47,18 @@ We start with AWS KMS examples, then show how to use other wrapping keys. * Using raw wrapping keys * How to use a raw AES wrapping key * [with keyrings](./src/keyring/raw_aes/raw_aes.py) + * [with master key providers](./src/master_key_provider/raw_aes/raw_aes.py) * How to use a raw RSA wrapping key * [with keyrings](./src/keyring/raw_rsa/private_key_only.py) * How to use a raw RSA wrapping key when the key is PEM or DER encoded * [with keyrings](./src/keyring/raw_rsa/private_key_only_from_pem.py) + * [with master key providers](./src/master_key_provider/raw_rsa/private_key_only_from_pem.py) * How to encrypt with a raw RSA public key wrapping key without access to the private key * [with keyrings](./src/keyring/raw_rsa/public_private_key_separate.py) * Combining wrapping keys * How to combine AWS KMS with an offline escrow key * [with keyrings](./src/keyring/multi/aws_kms_with_escrow.py) + * [with master key providers](./src/master_key_provider/multi/aws_kms_with_escrow.py) ### Keyrings diff --git a/examples/src/keyring/raw_aes/raw_aes.py b/examples/src/keyring/raw_aes/raw_aes.py index 58aa697c9..c688141f2 100644 --- a/examples/src/keyring/raw_aes/raw_aes.py +++ b/examples/src/keyring/raw_aes/raw_aes.py @@ -42,7 +42,7 @@ def run(source_plaintext): # Create the keyring that determines how your data keys are protected. keyring = RawAESKeyring( # The key namespace and key name are defined by you - # and are used by the raw RSA keyring + # and are used by the raw AES keyring # to determine whether it should attempt to decrypt # an encrypted data key. # diff --git a/examples/src/master_key_provider/__init__.py b/examples/src/master_key_provider/__init__.py new file mode 100644 index 000000000..0edf699c5 --- /dev/null +++ b/examples/src/master_key_provider/__init__.py @@ -0,0 +1,7 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Master key provider examples. + +These examples show how to use master key providers. +""" diff --git a/examples/src/master_key_provider/aws_kms/__init__.py b/examples/src/master_key_provider/aws_kms/__init__.py new file mode 100644 index 000000000..5da30d818 --- /dev/null +++ b/examples/src/master_key_provider/aws_kms/__init__.py @@ -0,0 +1,7 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +AWS KMS master key provider examples. + +These examples show how to use the KMS master key provider. +""" diff --git a/examples/src/master_key_provider/aws_kms/discovery_decrypt.py b/examples/src/master_key_provider/aws_kms/discovery_decrypt.py new file mode 100644 index 000000000..1b7e09452 --- /dev/null +++ b/examples/src/master_key_provider/aws_kms/discovery_decrypt.py @@ -0,0 +1,71 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example is intended to serve as reference material for users migrating away from master key providers. +We recommend using keyrings rather than master key providers. +For examples using keyrings, see the ``examples/src/keyrings`` directory. + +The KMS master key provider uses any key IDs that you specify on encrypt, +but attempts to decrypt *any* data keys that were encrypted under a KMS CMK. +This means that you do not need to know which CMKs were used to encrypt a message. + +This example shows how to configure and use a KMS master key provider to decrypt without provider key IDs. + +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider + +For an example of how to use the KMS master key with a single CMK, +see the ``master_key_provider/aws_kms/single_cmk`` example. + +For an example of how to use the KMS master key provider with CMKs in multiple regions, +see the ``master_key_provider/aws_kms/multiple_regions`` example. +""" +import aws_encryption_sdk +from aws_encryption_sdk.key_providers.kms import KMSMasterKey, KMSMasterKeyProvider + + +def run(aws_kms_cmk, source_plaintext): + # type: (str, bytes) -> None + """Demonstrate configuring a KMS master key provider for decryption. + + :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys + :param bytes source_plaintext: Plaintext to encrypt + """ + # Prepare your encryption context. + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context = { + "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", + } + + # Create the master key that determines how your data keys are protected. + encrypt_master_key = KMSMasterKey(key_id=aws_kms_cmk) + + # Create a KMS master key provider to use on decrypt. + decrypt_master_key_provider = KMSMasterKeyProvider() + + # Encrypt your plaintext data. + ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( + source=source_plaintext, encryption_context=encryption_context, key_provider=encrypt_master_key + ) + + # Demonstrate that the ciphertext and plaintext are different. + assert ciphertext != source_plaintext + + # Decrypt your encrypted data using the KMS master key provider. + # + # You do not need to specify the encryption context on decrypt + # because the header of the encrypted message includes the encryption context. + decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=decrypt_master_key_provider) + + # Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert decrypted == source_plaintext + + # Verify that the encryption context used in the decrypt operation includes + # the encryption context that you specified when encrypting. + # The AWS Encryption SDK can add pairs, so don't require an exact match. + # + # In production, always use a meaningful encryption context. + assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/master_key_provider/aws_kms/multiple_regions.py b/examples/src/master_key_provider/aws_kms/multiple_regions.py new file mode 100644 index 000000000..81fee9ec5 --- /dev/null +++ b/examples/src/master_key_provider/aws_kms/multiple_regions.py @@ -0,0 +1,91 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example is intended to serve as reference material for users migrating away from master key providers. +We recommend using keyrings rather than master key providers. +For examples using keyrings, see the ``examples/src/keyrings`` directory. + +This example shows how to configure and use a KMS master key provider with with CMKs in multiple regions. + +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider + +For an example of how to use the KMS master key with a single CMK, +see the ``master_key_provider/aws_kms/single_cmk`` example. + +For an example of how to use the KMS master key provider in discovery mode on decrypt, +see the ``master_key_provider/aws_kms/discovery_decrypt`` example. +""" +import aws_encryption_sdk +from aws_encryption_sdk.key_providers.kms import KMSMasterKey, KMSMasterKeyProvider + +try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import Sequence # noqa pylint: disable=unused-import +except ImportError: # pragma: no cover + # We only actually need these imports when running the mypy checks + pass + + +def run(aws_kms_generator_cmk, aws_kms_additional_cmks, source_plaintext): + # type: (str, Sequence[str], bytes) -> None + """Demonstrate an encrypt/decrypt cycle using a KMS master key provider with CMKs in multiple regions. + + :param str aws_kms_generator_cmk: The ARN of the primary AWS KMS CMK + :param List[str] aws_kms_additional_cmks: Additional ARNs of secondary KMS CMKs + :param bytes source_plaintext: Plaintext to encrypt + """ + # Prepare your encryption context. + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context = { + "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", + } + + # Create the master key provider that will encrypt your data keys under all requested CMKs. + # + # The KMS master key provider generates the data key using the first key ID in the list. + key_ids = [aws_kms_generator_cmk] + key_ids.extend(aws_kms_additional_cmks) + master_key_provider = KMSMasterKeyProvider(key_ids=key_ids) + + # Create master keys that each only use one of the CMKs. + # We will use these later to demonstrate that any of the CMKs can be used to decrypt the message. + single_cmk_master_key_that_generated = KMSMasterKey(key_id=aws_kms_generator_cmk) + single_cmk_master_key_that_encrypted = KMSMasterKey(key_id=aws_kms_additional_cmks[0]) + + # Encrypt your plaintext data using the master key provider that uses all requests CMKs. + ciphertext, encrypt_header = aws_encryption_sdk.encrypt( + source=source_plaintext, encryption_context=encryption_context, key_provider=master_key_provider + ) + + # Verify that the header contains the expected number of encrypted data keys (EDKs). + # It should contain one EDK for each CMK. + assert len(encrypt_header.encrypted_data_keys) == len(aws_kms_additional_cmks) + 1 + + # Demonstrate that the ciphertext and plaintext are different. + assert ciphertext != source_plaintext + + # Decrypt your encrypted data separately using the single-CMK master keys. + # + # You do not need to specify the encryption context on decrypt + # because the header of the encrypted message includes the encryption context. + decrypted_1, decrypt_header_1 = aws_encryption_sdk.decrypt( + source=ciphertext, key_provider=single_cmk_master_key_that_generated + ) + decrypted_2, decrypt_header_2 = aws_encryption_sdk.decrypt( + source=ciphertext, key_provider=single_cmk_master_key_that_encrypted + ) + + # Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert decrypted_1 == source_plaintext + assert decrypted_2 == source_plaintext + + # Verify that the encryption context used in the decrypt operation includes + # the encryption context that you specified when encrypting. + # The AWS Encryption SDK can add pairs, so don't require an exact match. + # + # In production, always use a meaningful encryption context. + assert set(encryption_context.items()) <= set(decrypt_header_1.encryption_context.items()) + assert set(encryption_context.items()) <= set(decrypt_header_2.encryption_context.items()) diff --git a/examples/src/master_key_provider/aws_kms/single_cmk.py b/examples/src/master_key_provider/aws_kms/single_cmk.py new file mode 100644 index 000000000..5a23493d6 --- /dev/null +++ b/examples/src/master_key_provider/aws_kms/single_cmk.py @@ -0,0 +1,64 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example is intended to serve as reference material for users migrating away from master key providers. +We recommend using keyrings rather than master key providers. +For examples using keyrings, see the ``examples/src/keyrings`` directory. + +This example shows how to configure and use a KMS master key with a single KMS CMK. + +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider + +For an example of how to use the KMS master key provider with CMKs in multiple regions, +see the ``master_key_provider/aws_kms/multiple_regions`` example. + +For an example of how to use the KMS master key provider in discovery mode on decrypt, +see the ``master_key_provider/aws_kms/discovery_decrypt`` example. +""" +import aws_encryption_sdk +from aws_encryption_sdk.key_providers.kms import KMSMasterKey + + +def run(aws_kms_cmk, source_plaintext): + # type: (str, bytes) -> None + """Demonstrate an encrypt/decrypt cycle using a KMS master key with a single CMK. + + :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys + :param bytes source_plaintext: Plaintext to encrypt + """ + # Prepare your encryption context. + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context = { + "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", + } + + # Create the master key that determines how your data keys are protected. + master_key = KMSMasterKey(key_id=aws_kms_cmk) + + # Encrypt your plaintext data. + ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( + source=source_plaintext, encryption_context=encryption_context, key_provider=master_key + ) + + # Demonstrate that the ciphertext and plaintext are different. + assert ciphertext != source_plaintext + + # Decrypt your encrypted data using the same master key you used on encrypt. + # + # You do not need to specify the encryption context on decrypt + # because the header of the encrypted message includes the encryption context. + decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=master_key) + + # Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert decrypted == source_plaintext + + # Verify that the encryption context used in the decrypt operation includes + # the encryption context that you specified when encrypting. + # The AWS Encryption SDK can add pairs, so don't require an exact match. + # + # In production, always use a meaningful encryption context. + assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/master_key_provider/multi/__init__.py b/examples/src/master_key_provider/multi/__init__.py new file mode 100644 index 000000000..22f5195fc --- /dev/null +++ b/examples/src/master_key_provider/multi/__init__.py @@ -0,0 +1,7 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Multi-master key provider examples. + +These examples show how to combine master key providers. +""" diff --git a/examples/src/master_key_provider/multi/aws_kms_with_escrow.py b/examples/src/master_key_provider/multi/aws_kms_with_escrow.py new file mode 100644 index 000000000..a9c4a2130 --- /dev/null +++ b/examples/src/master_key_provider/multi/aws_kms_with_escrow.py @@ -0,0 +1,150 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example is intended to serve as reference material for users migrating away from master key providers. +We recommend using keyrings rather than master key providers. +For examples using keyrings, see the ``examples/src/keyrings`` directory. + +One use-case that we have seen customers need is +the ability to enjoy the benefits of AWS KMS during normal operation +but retain the ability to decrypt encrypted messages without access to AWS KMS. +This example shows how you can achieve this +by combining a KMS master key with a raw RSA master key. + +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider + +For more examples of how to use the KMS master key provider, see the ``master_key_provider/aws_kms`` examples. + +For more examples of how to use the raw RSA master key, see the ``master_key_provider/raw_rsa`` examples. + +In this example we generate a RSA keypair +but in practice you would want to keep your private key in an HSM +or other key management system. + +In this example, we use the one-step encrypt and decrypt APIs. +""" +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa + +import aws_encryption_sdk +from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm +from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider +from aws_encryption_sdk.key_providers.raw import RawMasterKey, WrappingKey + + +def run(aws_kms_cmk, source_plaintext): + # type: (str, bytes) -> None + """Demonstrate configuring a master key provider to use an AWS KMS CMK and a RSA wrapping key. + + :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys + :param bytes source_plaintext: Plaintext to encrypt + """ + # Prepare your encryption context. + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context = { + "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", + } + + # Generate an RSA private key to use with your master key. + # In practice, you should get this key from a secure key management system such as an HSM. + # + # The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. + # https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths + # + # Why did we use this public exponent? + # https://crypto.stanford.edu/~dabo/pubs/papers/RSA-survey.pdf + private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) + + # Serialize the RSA private key to PEM encoding. + # This or DER encoding is likely to be what you get from your key management system in practice. + private_key_pem = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + + # Collect the public key from the private key. + public_key = private_key.public_key() + + # Serialize the RSA public key to PEM encoding. + # This or DER encoding is likely to be what you get from your key management system in practice. + public_key_pem = public_key.public_bytes( + encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + + # Create the encrypt master key that only has access to the public key. + escrow_encrypt_master_key = RawMasterKey( + # The provider ID and key ID are defined by you + # and are used by the raw RSA master key + # to determine whether it should attempt to decrypt + # an encrypted data key. + provider_id="some managed raw keys", # provider ID corresponds to key namespace for keyrings + key_id=b"my RSA wrapping key", # key ID corresponds to key name for keyrings + wrapping_key=WrappingKey( + wrapping_key=public_key_pem, + wrapping_key_type=EncryptionKeyType.PUBLIC, + # The wrapping algorithm tells the raw RSA master key + # how to use your wrapping key to encrypt data keys. + # + # We recommend using RSA_OAEP_SHA256_MGF1. + # You should not use RSA_PKCS1 unless you require it for backwards compatibility. + wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, + ), + ) + + # Create the decrypt master key that has access to the private key. + escrow_decrypt_master_key = RawMasterKey( + # The key namespace and key name MUST match the encrypt master key. + provider_id="some managed raw keys", # provider ID corresponds to key namespace for keyrings + key_id=b"my RSA wrapping key", # key ID corresponds to key name for keyrings + wrapping_key=WrappingKey( + wrapping_key=private_key_pem, + wrapping_key_type=EncryptionKeyType.PRIVATE, + # The wrapping algorithm MUST match the encrypt master key. + wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, + ), + ) + + # Create the KMS master key that you will use for decryption during normal operations. + kms_master_key = KMSMasterKeyProvider(key_ids=[aws_kms_cmk]) + + # Add the escrow encrypt master key to the KMS master key. + kms_master_key.add_master_key_provider(escrow_encrypt_master_key) + + # Encrypt your plaintext data using the combined master keys. + ciphertext, encrypt_header = aws_encryption_sdk.encrypt( + source=source_plaintext, encryption_context=encryption_context, key_provider=kms_master_key + ) + + # Verify that the header contains the expected number of encrypted data keys (EDKs). + # It should contain one EDK for KMS and one for the escrow key. + assert len(encrypt_header.encrypted_data_keys) == 2 + + # Demonstrate that the ciphertext and plaintext are different. + assert ciphertext != source_plaintext + + # Decrypt your encrypted data separately using the KMS master key and the escrow decrypt master key. + # + # You do not need to specify the encryption context on decrypt + # because the header of the encrypted message includes the encryption context. + decrypted_kms, decrypt_header_kms = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=kms_master_key) + decrypted_escrow, decrypt_header_escrow = aws_encryption_sdk.decrypt( + source=ciphertext, key_provider=escrow_decrypt_master_key + ) + + # Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert decrypted_kms == source_plaintext + assert decrypted_escrow == source_plaintext + + # Verify that the encryption context used in the decrypt operation includes + # the encryption context that you specified when encrypting. + # The AWS Encryption SDK can add pairs, so don't require an exact match. + # + # In production, always use a meaningful encryption context. + assert set(encryption_context.items()) <= set(decrypt_header_kms.encryption_context.items()) + assert set(encryption_context.items()) <= set(decrypt_header_escrow.encryption_context.items()) diff --git a/examples/src/master_key_provider/raw_aes/__init__.py b/examples/src/master_key_provider/raw_aes/__init__.py new file mode 100644 index 000000000..5572015a7 --- /dev/null +++ b/examples/src/master_key_provider/raw_aes/__init__.py @@ -0,0 +1,7 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Raw AES master key provider examples. + +These examples show how to use the raw AES master key. +""" diff --git a/examples/src/master_key_provider/raw_aes/raw_aes.py b/examples/src/master_key_provider/raw_aes/raw_aes.py new file mode 100644 index 000000000..943fcb67b --- /dev/null +++ b/examples/src/master_key_provider/raw_aes/raw_aes.py @@ -0,0 +1,81 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example is intended to serve as reference material for users migrating away from master key providers. +We recommend using keyrings rather than master key providers. +For examples using keyrings, see the ``examples/src/keyrings`` directory. + +This examples shows how to configure and use a raw AES master key. + +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider + +In this example, we use the one-step encrypt and decrypt APIs. +""" +import os + +import aws_encryption_sdk +from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm +from aws_encryption_sdk.key_providers.raw import RawMasterKey, WrappingKey + + +def run(source_plaintext): + # type: (bytes) -> None + """Demonstrate an encrypt/decrypt cycle using a raw AES master key. + + :param bytes source_plaintext: Plaintext to encrypt + """ + # Prepare your encryption context. + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context = { + "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", + } + + # Choose the wrapping algorithm for your master key to use. + wrapping_algorithm = WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING + + # Generate an AES key to use with your master key. + # The key size depends on the wrapping algorithm. + # + # In practice, you should get this key from a secure key management system such as an HSM. + key = os.urandom(wrapping_algorithm.algorithm.kdf_input_len) + + # Create the master key that determines how your data keys are protected. + master_key = RawMasterKey( + # The provider ID and key ID are defined by you + # and are used by the raw AES master key + # to determine whether it should attempt to decrypt + # an encrypted data key. + provider_id="some managed raw keys", # provider ID corresponds to key namespace for keyrings + key_id=b"my AES wrapping key", # key ID corresponds to key name for keyrings + wrapping_key=WrappingKey( + wrapping_algorithm=wrapping_algorithm, wrapping_key_type=EncryptionKeyType.SYMMETRIC, wrapping_key=key, + ), + ) + + # Encrypt your plaintext data. + ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( + source=source_plaintext, encryption_context=encryption_context, key_provider=master_key + ) + + # Demonstrate that the ciphertext and plaintext are different. + assert ciphertext != source_plaintext + + # Decrypt your encrypted data using the same master key you used on encrypt. + # + # You do not need to specify the encryption context on decrypt + # because the header of the encrypted message includes the encryption context. + decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=master_key) + + # Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert decrypted == source_plaintext + + # Verify that the encryption context used in the decrypt operation includes + # the encryption context that you specified when encrypting. + # The AWS Encryption SDK can add pairs, so don't require an exact match. + # + # In production, always use a meaningful encryption context. + assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items()) diff --git a/examples/src/master_key_provider/raw_rsa/__init__.py b/examples/src/master_key_provider/raw_rsa/__init__.py new file mode 100644 index 000000000..374a606fb --- /dev/null +++ b/examples/src/master_key_provider/raw_rsa/__init__.py @@ -0,0 +1,7 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Raw RSA master key provider examples. + +These examples show how to use the raw RSA master key. +""" diff --git a/examples/src/master_key_provider/raw_rsa/private_key_only_from_pem.py b/examples/src/master_key_provider/raw_rsa/private_key_only_from_pem.py new file mode 100644 index 000000000..5ad78c00b --- /dev/null +++ b/examples/src/master_key_provider/raw_rsa/private_key_only_from_pem.py @@ -0,0 +1,104 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +This example is intended to serve as reference material for users migrating away from master key providers. +We recommend using keyrings rather than master key providers. +For examples using keyrings, see the ``examples/src/keyrings`` directory. + +This example shows how to configure and use a raw RSA master key using a PEM-encoded RSA private key. + +The most commonly used encodings for RSA keys tend to be PEM and DER. +The raw RSA master key supports loading both public and private keys from PEM encoding. + +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider + +In this example, we use the one-step encrypt and decrypt APIs. +""" +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa + +import aws_encryption_sdk +from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm +from aws_encryption_sdk.key_providers.raw import RawMasterKey, WrappingKey + + +def run(source_plaintext): + # type: (bytes) -> None + """Demonstrate an encrypt/decrypt cycle using a raw RSA master key loaded from a PEM-encoded key. + + :param bytes source_plaintext: Plaintext to encrypt + """ + # Prepare your encryption context. + # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + encryption_context = { + "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", + } + + # Generate an RSA private key to use with your master key. + # In practice, you should get this key from a secure key management system such as an HSM. + # + # The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. + # https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths + # + # Why did we use this public exponent? + # https://crypto.stanford.edu/~dabo/pubs/papers/RSA-survey.pdf + private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) + + # Serialize the RSA private key to PEM encoding. + # This or DER encoding is likely to be what you get from your key management system in practice. + private_key_pem = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + + # Create the master key that determines how your data keys are protected. + # + # WrappingKey can only load PEM-encoded keys. + master_key = RawMasterKey( + # The provider ID and key ID are defined by you + # and are used by the raw RSA master key + # to determine whether it should attempt to decrypt + # an encrypted data key. + provider_id="some managed raw keys", # provider ID corresponds to key namespace for keyrings + key_id=b"my RSA wrapping key", # key ID corresponds to key name for keyrings + wrapping_key=WrappingKey( + wrapping_key=private_key_pem, + wrapping_key_type=EncryptionKeyType.PRIVATE, + # The wrapping algorithm tells the raw RSA master key + # how to use your wrapping key to encrypt data keys. + # + # We recommend using RSA_OAEP_SHA256_MGF1. + # You should not use RSA_PKCS1 unless you require it for backwards compatibility. + wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, + ), + ) + + # Encrypt your plaintext data. + ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( + source=source_plaintext, encryption_context=encryption_context, key_provider=master_key + ) + + # Demonstrate that the ciphertext and plaintext are different. + assert ciphertext != source_plaintext + + # Decrypt your encrypted data using the same master key you used on encrypt. + # + # You do not need to specify the encryption context on decrypt + # because the header of the encrypted message includes the encryption context. + decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=master_key) + + # Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert decrypted == source_plaintext + + # Verify that the encryption context used in the decrypt operation includes + # the encryption context that you specified when encrypting. + # The AWS Encryption SDK can add pairs, so don't require an exact match. + # + # In production, always use a meaningful encryption context. + assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items())