def test_aws_kms_single_cmk_keyring_on_decrypt_multiple_cmk(fake_generator_and_child): generator, child = fake_generator_and_child encrypting_keyring = AwsKmsKeyring(generator_key_id=generator, key_ids=(child,)) decrypting_keyring = _AwsKmsSingleCmkKeyring(key_id=child, client_supplier=DefaultClientSupplier()) initial_encryption_materials = EncryptionMaterials(algorithm=ALGORITHM, encryption_context={}) encryption_materials = encrypting_keyring.on_encrypt(initial_encryption_materials) initial_decryption_materials = DecryptionMaterials( algorithm=encryption_materials.algorithm, encryption_context=encryption_materials.encryption_context ) result_materials = decrypting_keyring.on_decrypt( decryption_materials=initial_decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys ) generator_flags = _matching_flags( MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=generator), result_materials.keyring_trace ) assert len(generator_flags) == 0 child_flags = _matching_flags( MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=child), result_materials.keyring_trace ) assert KeyringTraceFlag.DECRYPTED_DATA_KEY in child_flags assert KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT in child_flags
def run(aws_kms_cmk, source_plaintext): # type: (str, bytes) -> None """Demonstrate configuring an AWS KMS discovery keyring to only work within a single region. :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 keyring that determines how your data keys are protected. encrypt_keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) # Extract the region from the CMK ARN. decrypt_region = aws_kms_cmk.split(":", 4)[3] # Create the AWS KMS discovery keyring that we will use on decrypt. # # The client supplier that we specify here will only supply clients for the specified region. # The keyring only attempts to decrypt data keys if it can get a client for that region, # so this keyring will now ignore any data keys that were encrypted under a CMK in another region. decrypt_keyring = AwsKmsKeyring(is_discovery=True, client_supplier=AllowRegionsClientSupplier( allowed_regions=[decrypt_region])) # Encrypt your plaintext data. ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, keyring=encrypt_keyring) # Demonstrate that the ciphertext and plaintext are different. assert ciphertext != source_plaintext # Decrypt your encrypted data using the AWS KMS discovery keyring. # # 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, keyring=decrypt_keyring) # 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())
def test_kms_keyring_inner_keyring_on_encrypt(mocker): mock_keyring = mocker.Mock() keyring = AwsKmsKeyring(is_discovery=True) keyring._inner_keyring = mock_keyring test = keyring.on_encrypt( encryption_materials=mocker.sentinel.encryption_materials) # on_encrypt MUST be a straight passthrough to the inner keyring assert mock_keyring.on_encrypt.called_once_with( encryption_materials=mocker.sentinel.encryption_materials) assert test is mock_keyring.on_encrypt.return_value
def run(aws_kms_cmk, source_plaintext): # type: (str, bytes) -> None """Demonstrate configuring an AWS KMS discovery keyring 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. # Remember that your encryption context is NOT SECRET. # 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 keyring that determines how your data keys are protected. encrypt_keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) # Create an AWS KMS discovery keyring to use on decrypt. decrypt_keyring = AwsKmsKeyring(is_discovery=True) # Encrypt your plaintext data. ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, keyring=encrypt_keyring) # Demonstrate that the ciphertext and plaintext are different. assert ciphertext != source_plaintext # Decrypt your encrypted data using the AWS KMS discovery keyring. # # 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, keyring=decrypt_keyring) # 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())
def test_kms_keyring_builds_correct_inner_keyring_multikeyring_no_generator(): test = AwsKmsKeyring(key_ids=("bar", "baz")) # We specified child IDs, so the inner keyring MUST be a multikeyring assert isinstance(test._inner_keyring, MultiKeyring) # We did not specify a generator ID, so the generator MUST NOT be set assert test._inner_keyring.generator is None # We specified two child IDs, so there MUST be exactly two children assert len(test._inner_keyring.children) == 2
def test_kms_keyring_builds_correct_inner_keyring_multikeyring_no_children(): test = AwsKmsKeyring(generator_key_id="foo") # We specified a generator ID, so the inner keyring MUST be a multikeyring assert isinstance(test._inner_keyring, MultiKeyring) # We specified a generator ID, so the generator MUST be set assert test._inner_keyring.generator is not None # We did not specify any child IDs, so the multikeyring MUST NOT contain any children assert len(test._inner_keyring.children) == 0
def run(aws_kms_cmk, source_plaintext): # type: (str, bytes) -> None """Demonstrate requesting a specific algorithm suite through the one-step encrypt/decrypt APIs. :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 keyring that determines how your data keys are protected. keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) # Encrypt your plaintext data. ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, keyring=keyring, # Here we can specify the algorithm suite that we want to use. algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA256, ) # Demonstrate that the ciphertext and plaintext are different. assert ciphertext != source_plaintext # Decrypt your encrypted data using the same keyring 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. # # You do not need to specify the algorithm suite on decrypt # because the header message includes the algorithm suite identifier. decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring) # 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())
def test_kms_keyring_builds_correct_inner_keyring_discovery(): grants = ("asdf", "fdas") supplier = DefaultClientSupplier() test = AwsKmsKeyring(is_discovery=True, grant_tokens=grants, client_supplier=supplier) # We specified neither a generator nor children, so the inner keyring MUST be a discovery keyring assert isinstance(test._inner_keyring, _AwsKmsDiscoveryKeyring) # Verify that the discovery keyring is configured correctly assert test._inner_keyring._grant_tokens == grants assert test._inner_keyring._client_supplier is supplier
def build_aws_kms_keyring(generate=True, cache=True): """Build an AWS KMS keyring.""" global _KMS_KEYRING # pylint: disable=global-statement if cache and _KMS_KEYRING is not None: return _KMS_KEYRING cmk_arn = get_cmk_arn() if generate: kwargs = dict(generator_key_id=cmk_arn) else: kwargs = dict(key_ids=[cmk_arn]) keyring = AwsKmsKeyring(**kwargs) if cache: _KMS_KEYRING = keyring return keyring
def test_kms_keyring_builds_correct_inner_keyring_multikeyring(): generator_id = "foo" child_id_1 = "bar" child_id_2 = "baz" grants = ("asdf", "fdsa") supplier = DefaultClientSupplier() test = AwsKmsKeyring( generator_key_id=generator_id, key_ids=(child_id_1, child_id_2), grant_tokens=grants, client_supplier=supplier, ) # We specified a generator and child IDs, so the inner keyring MUST be a multikeyring assert isinstance(test._inner_keyring, MultiKeyring) # Verify that the generator is configured correctly assert isinstance(test._inner_keyring.generator, _AwsKmsSingleCmkKeyring) assert test._inner_keyring.generator._key_id == generator_id assert test._inner_keyring.generator._grant_tokens == grants assert test._inner_keyring.generator._client_supplier is supplier # We specified two child IDs, so there MUST be exactly two children assert len(test._inner_keyring.children) == 2 # Verify that the first child is configured correctly assert isinstance(test._inner_keyring.children[0], _AwsKmsSingleCmkKeyring) assert test._inner_keyring.children[0]._key_id == child_id_1 assert test._inner_keyring.children[0]._grant_tokens == grants assert test._inner_keyring.children[0]._client_supplier is supplier # Verify that the second child is configured correctly assert isinstance(test._inner_keyring.children[1], _AwsKmsSingleCmkKeyring) assert test._inner_keyring.children[1]._key_id == child_id_2 assert test._inner_keyring.children[1]._grant_tokens == grants assert test._inner_keyring.children[1]._client_supplier is supplier
def run(aws_kms_cmk, source_plaintext): # type: (str, bytes) -> None """Demonstrate an encrypt/decrypt cycle using an AWS KMS keyring with custom AWS KMS client configuration. :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. # Remember that your encryption context is NOT SECRET. # 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", } # Prepare your custom configuration values. # # Set your custom connection timeout value. # https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html custom_client_config = Config(connect_timeout=10.0, user_agent_extra=USER_AGENT_SUFFIX) # For this example we will just use the default botocore session configuration # but if you need to, you can set custom credentials in the botocore session. custom_session = Session() # Use your custom configuration values to configure your client supplier. client_supplier = DefaultClientSupplier(botocore_session=custom_session, client_config=custom_client_config) # Create the keyring that determines how your data keys are protected, # providing the client supplier that you created. keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk, client_supplier=client_supplier) # Encrypt your plaintext data. ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, keyring=keyring) # Demonstrate that the ciphertext and plaintext are different. assert ciphertext != source_plaintext # Decrypt your encrypted data using the same keyring 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, keyring=keyring) # 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())
def run(aws_kms_cmk, source_plaintext_filename): # type: (str, str) -> None """Demonstrate an encrypt/decrypt cycle using the streaming encrypt/decrypt APIs with files. :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys :param str source_plaintext_filename: Path to plaintext file to encrypt """ # We assume that you can also write to the directory containing the plaintext file, # so that is where we will put all of the results. ciphertext_filename = source_plaintext_filename + ".encrypted" decrypted_filename = ciphertext_filename + ".decrypted" # Prepare your encryption context. # Remember that your encryption context is NOT SECRET. # 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 keyring that determines how your data keys are protected. keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) # Open the files you want to work with. with open(source_plaintext_filename, "rb") as plaintext, open(ciphertext_filename, "wb") as ciphertext: # The streaming API provides a context manager. # You can read from it just as you read from a file. with aws_encryption_sdk.stream(mode="encrypt", source=plaintext, encryption_context=encryption_context, keyring=keyring) as encryptor: # Iterate through the segments in the context manager # and write the results to the ciphertext. for segment in encryptor: ciphertext.write(segment) # Demonstrate that the ciphertext and plaintext are different. assert not filecmp.cmp(source_plaintext_filename, ciphertext_filename) # Open the files you want to work with. with open(ciphertext_filename, "rb") as ciphertext, open(decrypted_filename, "wb") as decrypted: # Decrypt your encrypted data using the same keyring 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. with aws_encryption_sdk.stream(mode="decrypt", source=ciphertext, keyring=keyring) as decryptor: # Check the encryption context in the header before we start decrypting. # # 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( decryptor.header.encryption_context.items()) # Now that we are more confident that we will decrypt the right message, # we can start decrypting. for segment in decryptor: decrypted.write(segment) # Demonstrate that the decrypted plaintext is identical to the original plaintext. assert filecmp.cmp(source_plaintext_filename, decrypted_filename)
def run(aws_kms_generator_cmk, aws_kms_additional_cmks, source_plaintext): # type: (str, Sequence[str], bytes) -> None """Demonstrate an encrypt/decrypt cycle using an AWS KMS keyring 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 AWS 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 keyring that will encrypt your data keys under all requested CMKs. many_cmks_keyring = AwsKmsKeyring(generator_key_id=aws_kms_generator_cmk, key_ids=aws_kms_additional_cmks) # Create keyrings 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. # # We provide these in "key_ids" rather than "generator_key_id" # so that these keyrings cannot be used to generate a new data key. # We will only be using them on decrypt. single_cmk_keyring_that_generated = AwsKmsKeyring( key_ids=[aws_kms_generator_cmk]) single_cmk_keyring_that_encrypted = AwsKmsKeyring( key_ids=[aws_kms_additional_cmks[0]]) # Encrypt your plaintext data using the keyring that uses all requests CMKs. ciphertext, encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, keyring=many_cmks_keyring) # 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 keyrings. # # 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, keyring=single_cmk_keyring_that_generated) decrypted_2, decrypt_header_2 = aws_encryption_sdk.decrypt( source=ciphertext, keyring=single_cmk_keyring_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())
def run(aws_kms_cmk, source_plaintext): # type: (str, bytes) -> None """Demonstrate an encrypt/decrypt cycle using the streaming encrypt/decrypt APIs in-memory. :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 keyring that determines how your data keys are protected. keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) ciphertext = io.BytesIO() # The streaming API provides a context manager. # You can read from it just as you read from a file. with aws_encryption_sdk.stream(mode="encrypt", source=source_plaintext, encryption_context=encryption_context, keyring=keyring) as encryptor: # Iterate through the segments in the context manager # and write the results to the ciphertext. for segment in encryptor: ciphertext.write(segment) # Demonstrate that the ciphertext and plaintext are different. assert ciphertext.getvalue() != source_plaintext # Reset the ciphertext stream position so that we can read from the beginning. ciphertext.seek(0) # Decrypt your encrypted data using the same keyring 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 = io.BytesIO() with aws_encryption_sdk.stream(mode="decrypt", source=ciphertext, keyring=keyring) as decryptor: # Check the encryption context in the header before we start decrypting. # # 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( decryptor.header.encryption_context.items()) # Now that we are more confident that we will decrypt the right message, # we can start decrypting. for segment in decryptor: decrypted.write(segment) # Demonstrate that the decrypted plaintext is identical to the original plaintext. assert decrypted.getvalue() == source_plaintext
def run(aws_kms_cmk, source_plaintext): # type: (str, bytes) -> None """Demonstrate configuring an AWS KMS discovery-like keyring a particular AWS region and failover to others. :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. # Remember that your encryption context is NOT SECRET. # 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 keyring that determines how your data keys are protected. encrypt_keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) # To create our decrypt keyring, we need to know our current default AWS region. # # Create a throw-away boto3 session to discover the default region. local_region = Session().region_name # Now, use that region name to create two AWS KMS discovery keyrings: # # One that only works in the local region local_region_decrypt_keyring = AwsKmsKeyring( is_discovery=True, client_supplier=AllowRegionsClientSupplier(allowed_regions=[local_region]) ) # and one that will work in any other region but NOT the local region. other_regions_decrypt_keyring = AwsKmsKeyring( is_discovery=True, client_supplier=DenyRegionsClientSupplier(denied_regions=[local_region]) ) # Finally, combine those two keyrings into a multi-keyring. # # The multi-keyring steps through its member keyrings in the order that you provide them, # attempting to decrypt every encrypted data key with each keyring before moving on to the next keyring. # Because of this, other_regions_decrypt_keyring will not be called # unless local_region_decrypt_keyring fails to decrypt every encrypted data key. decrypt_keyring = MultiKeyring(children=[local_region_decrypt_keyring, other_regions_decrypt_keyring]) # Encrypt your plaintext data. ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, keyring=encrypt_keyring ) # Demonstrate that the ciphertext and plaintext are different. assert ciphertext != source_plaintext # Decrypt your encrypted data using the multi-keyring. # # 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, keyring=decrypt_keyring) # 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())
def test_kms_keyring_invalid_parameters(kwargs): with pytest.raises(TypeError): AwsKmsKeyring(**kwargs)
def run(aws_kms_cmk, source_plaintext): # type: (str, bytes) -> None """Demonstrate an encrypt/decrypt cycle using a custom cryptographic materials manager that filters requests. :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. # Remember that your encryption context is NOT SECRET. # 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 keyring that determines how your data keys are protected. keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) # Create the classification requiring cryptographic materials manager using your keyring. cmm = ClassificationRequiringCryptoMaterialsManager(keyring=keyring) # Demonstrate that the classification requiring CMM will not let you encrypt without a classification identifier. try: aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, materials_manager=cmm, ) except MissingClassificationError: # Your encryption context did not contain a classification identifier. # Reaching this point means everything is working as expected. pass else: # The classification requiring CMM keeps this from happening. raise AssertionError("The classification requiring CMM does not let this happen!") # Encrypt your plaintext data. classified_ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=dict(classification="secret", **encryption_context), materials_manager=cmm, ) # Demonstrate that the ciphertext and plaintext are different. assert classified_ciphertext != source_plaintext # Decrypt your encrypted data using the same cryptographic materials manager 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=classified_ciphertext, materials_manager=cmm) # 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()) # Now demonstrate the decrypt path of the classification requiring cryptographic materials manager. # Encrypt your plaintext using the keyring and do not include a classification identifier. unclassified_ciphertext, encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, keyring=keyring ) assert "classification" not in encrypt_header.encryption_context # Demonstrate that the classification requiring CMM # will not let you decrypt messages without classification identifiers. try: aws_encryption_sdk.decrypt(source=unclassified_ciphertext, materials_manager=cmm) except MissingClassificationError: # Your encryption context did not contain a classification identifier. # Reaching this point means everything is working as expected. pass else: # The classification requiring CMM keeps this from happening. raise AssertionError("The classification requiring CMM does not let this happen!")
def run(aws_kms_cmk, source_plaintext): # type: (str, bytes) -> None """Demonstrate configuring a keyring to use an AWS KMS CMK and an 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 keyring. # 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()) # Collect the public key from the private key. public_key = private_key.public_key() # Create the encrypt keyring that only has access to the public key. escrow_encrypt_keyring = RawRSAKeyring( # The key namespace and key name are defined by you # and are used by the raw RSA keyring # to determine whether it should attempt to decrypt # an encrypted data key. # # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring key_namespace="some managed raw keys", key_name=b"my RSA wrapping key", public_wrapping_key=public_key, # The wrapping algorithm tells the raw RSA keyring # 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 keyring that has access to the private key. escrow_decrypt_keyring = RawRSAKeyring( # The key namespace and key name MUST match the encrypt keyring. key_namespace="some managed raw keys", key_name=b"my RSA wrapping key", private_wrapping_key=private_key, # The wrapping algorithm MUST match the encrypt keyring. wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, ) # Create the AWS KMS keyring that you will use for decryption during normal operations. kms_keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) # Combine the AWS KMS keyring and the escrow encrypt keyring using the multi-keyring. encrypt_keyring = MultiKeyring(generator=kms_keyring, children=[escrow_encrypt_keyring]) # Encrypt your plaintext data using the multi-keyring. ciphertext, encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, keyring=encrypt_keyring) # Verify that the header contains the expected number of encrypted data keys (EDKs). # It should contain one EDK for AWS 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 AWS KMS keyring and the escrow decrypt keyring. # # 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, keyring=kms_keyring) decrypted_escrow, decrypt_header_escrow = aws_encryption_sdk.decrypt( source=ciphertext, keyring=escrow_decrypt_keyring) # 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())
def run(aws_kms_cmk, aws_kms_additional_cmks, source_plaintext): # type: (str, Sequence[str], bytes) -> None """Demonstrate how to create a keyring that behaves like an AWS KMS master key provider. :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys :param List[str] aws_kms_additional_cmks: Additional ARNs of secondary AWS 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", } # This is the master key provider whose behavior we want to reproduce. # # When encrypting, this master key provider generates the data key using the first CMK in the list # and encrypts the data key using all specified CMKs. # However, when decrypting, this master key provider attempts to decrypt # any data keys that were encrypted under an AWS KMS CMK. master_key_provider_cmks = [aws_kms_cmk] + aws_kms_additional_cmks _master_key_provider_to_replicate = KMSMasterKeyProvider( # noqa: intentionally never used key_ids=master_key_provider_cmks, ) # Create a CMK keyring that encrypts and decrypts using the specified AWS KMS CMKs. # # This keyring reproduces the encryption behavior of the AWS KMS master key provider. # # The AWS KMS keyring requires that you explicitly identify the CMK # that you want the keyring to use to generate the data key. cmk_keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk, key_ids=aws_kms_additional_cmks) # Create an AWS KMS discovery keyring that will attempt to decrypt # any data keys that were encrypted under an AWS KMS CMK. discovery_keyring = AwsKmsKeyring(is_discovery=True) # Combine the CMK and discovery keyrings # to create a keyring that behaves like an AWS KMS master key provider. # # The CMK keyring reproduces the encryption behavior # and the discovery keyring reproduces the decryption behavior. # This also means that it does not matter if the CMK keyring fails to decrypt. # For example, if you configured the CMK keyring with aliases, # it works on encrypt but fails to match any encrypted data keys on decrypt # because the serialized key name is the resulting CMK ARN rather than the alias name. # However, because the discovery keyring attempts to decrypt any AWS KMS-encrypted # data keys that it finds, the message still decrypts successfully. keyring = MultiKeyring(generator=cmk_keyring, children=[discovery_keyring]) # Encrypt your plaintext data. ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, keyring=keyring) # Demonstrate that the ciphertext and plaintext are different. assert ciphertext != source_plaintext # Decrypt your encrypted data using the same keyring 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, keyring=keyring) # 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())
def run(aws_kms_cmk, source_plaintext): # type: (str, bytes) -> None """Demonstrate an encrypt/decrypt cycle using the caching cryptographic materials manager. :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. # Remember that your encryption context is NOT SECRET. # 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 keyring that determines how your data keys are protected. keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk) # Create the caching cryptographic materials manager using your keyring. cmm = CachingCryptoMaterialsManager( keyring=keyring, # The cache is where the caching CMM stores the materials. # # LocalCryptoMaterialsCache gives you a local, in-memory, cache. cache=LocalCryptoMaterialsCache(capacity=100), # max_age determines how long the caching CMM will reuse materials. # # This example uses two minutes. # In production, always choose as small a value as possible # that works for your requirements. max_age=120.0, # max_messages_encrypted determines how many messages # the caching CMM will protect with the same materials. # # In production, always choose as small a value as possible # that works for your requirements. max_messages_encrypted=10, ) # Encrypt your plaintext data. ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, materials_manager=cmm) # Demonstrate that the ciphertext and plaintext are different. assert ciphertext != source_plaintext # Decrypt your encrypted data using the same cryptographic materials manager 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, materials_manager=cmm) # 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())