def encrypt(self, encryption_context_key: str, plaintext: str) -> Tuple[bytes, Sequence[str]]: """ Encrypt a plaintext string value. The return value will include *both* the resulting binary ciphertext and the master key ids used for encryption. In the likely case that the encryptor was initialized with master key aliases, these master key ids returned will represent the unaliased key. """ encryption_context = dict( microcosm=encryption_context_key, ) cyphertext, header = encrypt( source=plaintext, materials_manager=self.materials_manager, encryption_context=encryption_context, ) key_ids = [ self.unpack_key_id(encrypted_data_key.key_provider) for encrypted_data_key in header.encrypted_data_keys ] return cyphertext, key_ids
def test_encryption_cycle_raw_mkp(caplog, wrapping_algorithm, encryption_key_type, decryption_key_type): caplog.set_level(logging.DEBUG) encrypting_key_provider = build_fake_raw_key_provider(wrapping_algorithm, encryption_key_type) decrypting_key_provider = build_fake_raw_key_provider(wrapping_algorithm, decryption_key_type) ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], key_provider=encrypting_key_provider, encryption_context=VALUES["encryption_context"], frame_length=0, ) plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=decrypting_key_provider) assert plaintext == VALUES["plaintext_128"] for member in encrypting_key_provider._members: assert repr(member.config.wrapping_key._wrapping_key)[2:-1] not in caplog.text
def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_single_frame( self): """Test that the enrypt/decrypt cycle completes successfully for a single frame message using the aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384 algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], key_provider=self.kms_master_key_provider, encryption_context=VALUES["encryption_context"], frame_length=1024, algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, ) plaintext, _ = aws_encryption_sdk.decrypt( source=ciphertext, key_provider=self.kms_master_key_provider) assert plaintext == VALUES["plaintext_128"]
def test_encryption_cycle_default_algorithm_multiple_frames(self): """Test that the enrypt/decrypt cycle completes successfully for a framed message with multiple frames using the default algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES['plaintext_128'] * 100, key_provider=self.kms_master_key_provider, encryption_context=VALUES['encryption_context'], frame_length=1024 ) plaintext, _ = aws_encryption_sdk.decrypt( source=ciphertext, key_provider=self.kms_master_key_provider ) assert plaintext == VALUES['plaintext_128'] * 100
def test_encryption_cycle_raw_mkp_openssl_102_plus(wrapping_algorithm, encryption_key_type, decryption_key_type): encrypting_key_provider = build_fake_raw_key_provider( wrapping_algorithm, encryption_key_type) decrypting_key_provider = build_fake_raw_key_provider( wrapping_algorithm, decryption_key_type) ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], key_provider=encrypting_key_provider, encryption_context=VALUES["encryption_context"], frame_length=0, ) plaintext, _ = aws_encryption_sdk.decrypt( source=ciphertext, key_provider=decrypting_key_provider) assert plaintext == VALUES["plaintext_128"]
def test_encryption_cycle_default_algorithm_non_framed_fake_rsa_oaep_sha256( self): """Test that the enrypt/decrypt cycle completes successfully for a non-framed message using the default algorithm and the fake RSA-OAEP-SHA256 key provider. """ key_provider = self.fake_raw_key_provider_rsa_oaep_sha256_private_key ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES['plaintext_128'], key_provider=key_provider, encryption_context=VALUES['encryption_context'], frame_length=0) plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=key_provider) assert plaintext == VALUES['plaintext_128']
def test_encryption_cycle_default_algorithm_non_framed_fake_rsa_pkcs_asymmetric( self): """Test that the enrypt/decrypt cycle completes successfully for a non-framed message using the default algorithm and the fake RSA-PKCS key provider. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES['plaintext_128'], key_provider=self.fake_raw_key_provider_rsa_pkcs1_public_key, encryption_context=VALUES['encryption_context'], frame_length=0) plaintext, _ = aws_encryption_sdk.decrypt( source=ciphertext, key_provider=self.fake_raw_key_provider_rsa_pkcs1_private_key) assert plaintext == VALUES['plaintext_128']
def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_non_framed( self): """Test that the enrypt/decrypt cycle completes successfully for a non-framed message using the aes_128_gcm_iv12_tag16_hkdf_sha256 algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], key_provider=self.kms_master_key_provider, encryption_context=VALUES["encryption_context"], frame_length=0, algorithm=Algorithm.AES_128_GCM_IV12_TAG16_HKDF_SHA256, ) plaintext, _ = aws_encryption_sdk.decrypt( source=ciphertext, key_provider=self.kms_master_key_provider) assert plaintext == VALUES["plaintext_128"]
def test_encryption_cycle_oneshot_kms(frame_length, algorithm, encryption_context): key_provider = fake_kms_key_provider(algorithm.kdf_input_len) ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"] * 10, key_provider=key_provider, frame_length=frame_length, algorithm=algorithm, encryption_context=encryption_context, ) plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=key_provider) assert plaintext == VALUES["plaintext_128"] * 10
def run(aws_kms_cmk, source_plaintext): # type: (str, bytes) -> None """Demonstrate an encrypt/decrypt cycle using an AWS 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. # 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 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())
def test_encryption_cycle_aes_256_gcm_iv12_tag16_single_frame(self): """Test that the enrypt/decrypt cycle completes successfully for a single frame message using the aes_256_gcm_iv12_tag16 algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES['plaintext_128'], key_provider=self.kms_master_key_provider, encryption_context=VALUES['encryption_context'], frame_length=1024, algorithm=Algorithm.AES_256_GCM_IV12_TAG16 ) plaintext, _ = aws_encryption_sdk.decrypt( source=ciphertext, key_provider=self.kms_master_key_provider ) assert plaintext == VALUES['plaintext_128']
def test_stream_decryptor_no_seek_input(): """Test that StreamDecryptor can handle an input stream that is not seekable.""" key_provider = fake_kms_key_provider() ciphertext, _header = aws_encryption_sdk.encrypt( source=VALUES['plaintext_128'], key_provider=key_provider, encryption_context=VALUES['encryption_context'] ) ciphertext_no_seek = NoSeekBytesIO(ciphertext) decrypted = io.BytesIO() with aws_encryption_sdk.StreamDecryptor( source=ciphertext_no_seek, key_provider=key_provider ) as decryptor: for chunk in decryptor: decrypted.write(chunk) assert decrypted.getvalue() == VALUES['plaintext_128']
def store(self, data: bytes, context: Dict[str, str] = {}) -> PointerItem: """ Stores a document in the Document Bucket system. :param data: the bytes of the document to store :param context: TODO do something with this parameter :) :returns: the pointer reference for this document in the Document Bucket system """ encrypted_data, header = aws_encryption_sdk.encrypt( source=data, key_provider=self.master_key_provider, ) item = PointerItem.generate(context) self._write_pointer(item) self._write_object(encrypted_data, item) self._populate_key_records(item) return item
def test_encrypt_decrypt_header_only(): """Test that StreamDecryptor can extract header without reading ciphertext.""" ciphertext, encryptor_header = aws_encryption_sdk.encrypt( source=VALUES['plaintext_128'], key_provider=fake_kms_key_provider(), encryption_context=VALUES['encryption_context'] ) with aws_encryption_sdk.stream( mode='d', source=ciphertext, key_provider=fake_kms_key_provider() ) as decryptor: decryptor_header = decryptor.header assert decryptor.output_buffer == b'' assert all( pair in decryptor_header.encryption_context.items() for pair in encryptor_header.encryption_context.items() )
def test_decryptor_deprecated_attributes(caplog, attribute, no_later_than): caplog.set_level(logging.WARNING) plaintext = exact_length_plaintext(100) key_provider = fake_kms_key_provider() ciphertext, _header = aws_encryption_sdk.encrypt(source=plaintext, key_provider=key_provider, frame_length=0) with aws_encryption_sdk.stream(mode="decrypt", source=ciphertext, key_provider=key_provider) as decryptor: decrypted = decryptor.read() assert decrypted == plaintext assert hasattr(decryptor, attribute) watch_string = "StreamDecryptor.{name} is deprecated and will be removed in {version}".format( name=attribute, version=no_later_than) assert watch_string in caplog.text assert aws_encryption_sdk.__version__ < no_later_than
def encode_payload(payload, master_key_provider): payload_string = json.dumps(payload).encode() if not master_key_provider: return '1-' + str(base64.urlsafe_b64encode(payload_string), 'ascii') else: try: ciphertext, encryptor_header = aws_encryption_sdk.encrypt( source=payload_string, key_provider=master_key_provider) except aws_encryption_sdk.exceptions.GenerateKeyError as e: # This can happen if the key policy does not allow the sfn-callback-urls IAM role # to use the key. raise EncryptionFailed( f'Failed to create DEK; check your key policy ({str(e)})') except aws_encryption_sdk.exceptions.AWSEncryptionSDKClientError as e: # unexpected, turn into a 500 error raise return '2-' + str(base64.urlsafe_b64encode(ciphertext), 'ascii')
def store(self, data: bytes, context: Dict[str, str] = {}) -> PointerItem: """ Stores a document in the Document Bucket system. :param data: the bytes of the document to store :param context: the context for this document :returns: the pointer reference for this document in the Document Bucket system """ # ENCRYPTION-CONTEXT-COMPLETE: Set Encryption Context on Encrypt encrypted_data, header = aws_encryption_sdk.encrypt( source=data, key_provider=self.master_key_provider, encryption_context=context, ) item = PointerItem.generate(context) self._write_pointer(item) self._write_object(encrypted_data, item) self._populate_key_records(item) return item
def test_encrypt_decrypt_cycle_aws_kms( frame_size, algorithm_suite, encrypt_key_provider_param, encrypt_key_provider_partial, decrypt_key_provider_param, decrypt_key_provider_partial, encryption_context, plaintext, ): ciphertext, _ = aws_encryption_sdk.encrypt( source=plaintext, encryption_context=encryption_context, frame_length=frame_size, algorithm=algorithm_suite, **{encrypt_key_provider_param: encrypt_key_provider_partial()}) decrypted, _ = aws_encryption_sdk.decrypt( source=ciphertext, **{decrypt_key_provider_param: decrypt_key_provider_partial()}) assert decrypted == plaintext
def _generate_vectors(key_provider: MasterKeyProvider, plaintext: bytes) -> Iterable[Dict[Text, Text]]: """Generate all desired test vectors for a given key provider and plaintext.""" for algorithm_suite in aws_encryption_sdk.Algorithm: ciphertext, _header = aws_encryption_sdk.encrypt( source=plaintext, encryption_context=ENCRYPTION_CONTEXT, key_provider=key_provider, algorithm=algorithm_suite, ) yield { "key-type": key_provider.provider_id, "algorithm-suite": binascii.hexlify(algorithm_suite.id_as_bytes()).decode("utf-8"), "ciphertext": base64.b64encode(ciphertext).decode("utf-8"), "plaintext": base64.b64encode(plaintext).decode("utf-8"), }
def encrypt_with_caching(kms_cmk_arn, max_age_in_cache, cache_capacity): """Encrypts a string using an AWS KMS customer master key (CMK) and data key caching. :param str kms_cmk_arn: Amazon Resource Name (ARN) of the KMS customer master key :param float max_age_in_cache: Maximum time in seconds that a cached entry can be used :param int cache_capacity: Maximum number of entries to retain in cache at once """ # Data to be encrypted my_data = "My plaintext data" # Security thresholds # Max messages (or max bytes per) data key are optional MAX_ENTRY_MESSAGES = 100 # Create an encryption context encryption_context = {"purpose": "test"} # Create a master key provider for the KMS customer master key (CMK) key_provider = aws_encryption_sdk.KMSMasterKeyProvider( key_ids=[kms_cmk_arn]) # Create a local cache cache = aws_encryption_sdk.LocalCryptoMaterialsCache(cache_capacity) # Create a caching CMM caching_cmm = aws_encryption_sdk.CachingCryptoMaterialsManager( master_key_provider=key_provider, cache=cache, max_age=max_age_in_cache, max_messages_encrypted=MAX_ENTRY_MESSAGES, ) # When the call to encrypt data specifies a caching CMM, # the encryption operation uses the data key cache specified # in the caching CMM encrypted_message, _header = aws_encryption_sdk.encrypt( source=my_data, materials_manager=caching_cmm, encryption_context=encryption_context) return encrypted_message
def test_decryptor_deprecated_attributes(caplog, attribute, no_later_than): caplog.set_level(logging.WARNING) plaintext = exact_length_plaintext(100) key_provider = fake_kms_key_provider() ciphertext, _header = aws_encryption_sdk.encrypt(source=plaintext, key_provider=key_provider, frame_length=0) with aws_encryption_sdk.stream(mode="decrypt", source=ciphertext, key_provider=key_provider) as decryptor: decrypted = decryptor.read() assert decrypted == plaintext if aws_encryption_sdk.__version__ < no_later_than: _assert_deprecated_but_not_yet_removed( logcap=caplog, instance=decryptor, attribute_name=attribute, error_message="StreamDecryptor.{name} is deprecated and will be removed in {version}".format( name=attribute, version=no_later_than ), no_later_than=no_later_than, ) else: _assert_decrypted_and_removed(instance=decryptor, attribute_name=attribute, removed_in=no_later_than)
def EncryptionSdkCacheDemo(): session = botocore.session.Session(profile=PROFILE_NAME) kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider( botocore_session=session, key_ids=[MASTER_KEY_ARN], region_names=[REGION]) plainText = b"This is my secret data12" encryptionContext = {'type': 'password', 'env': 'dev'} # Create a local cache cache = aws_encryption_sdk.LocalCryptoMaterialsCache(10) # Create a caching CMM caching_cmm = aws_encryption_sdk.CachingCryptoMaterialsManager( master_key_provider=kms_key_provider, cache=cache, max_age=600.0, max_messages_encrypted=10, ) # SDK kullanarak bizim icin olusturulan datakey ile ciphertextimizi elde ediyoruz. cipherText, encryption_header = aws_encryption_sdk.encrypt( source=plainText, encryption_context=encryptionContext, materials_manager=caching_cmm) # print("Encrypted data= ", base64.b64encode(cipherText)) decryptedText, decryption_header = aws_encryption_sdk.decrypt( source=cipherText, materials_manager=caching_cmm) # Encryption contextimiz decryption_header icerisinde. # print(decryption_header.encryption_context) print('Decrypted Text= ', decryptedText) assert plainText == decryptedText
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 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())
class DocumentBucketOperations: """ Operations available for interaction with the Document Bucket. """ def __init__(self, bucket, table, master_key_provider: KMSMasterKeyProvider): """ Initialize a new operations object with the provided arguments. Args: bucket: S3 bucket for storing document objects table: DynamoDB table for storing document pointers and context keys master_key_provider: Encryption SDK Master Key Provider for encryption and decryption operations """ self.bucket = bucket self.table = table self.master_key_provider: KMSMasterKeyProvider = master_key_provider def _write_pointer(self, item: PointerItem): self.table.put_item(Item=item.to_item()) def _write_object(self, data: bytes, item: PointerItem): s3object = self.bucket.put_object( Body=data, Key=item.partition_key, Metadata=item.context ) return s3object def _get_object(self, item: PointerItem) -> bytes: s3object = self.bucket.Object(item.partition_key).get() return s3object["Body"].read() def _populate_key_records(self, pointer: PointerItem) -> Set[ContextItem]: context_items: Set[ContextItem] = pointer.context_items() for context_item in context_items: self.table.put_item(Item=context_item.to_item()) return context_items def _get_pointer_item(self, pointer_query: PointerQuery) -> PointerItem: pointer_items = self.table.query( KeyConditionExpression=pointer_query.expression() )["Items"] if len(pointer_items) != 1: raise ValueError( f"Pointer ID not unique! Expected 1 ID, got {len(pointer_items)}" ) return PointerItem.from_item(pointer_items[0]) def _query_for_context_key(self, query: ContextQuery) -> Set[PointerItem]: result = self.table.query(KeyConditionExpression=query.expression()) pointers: Set[PointerItem] = set() for ddb_context_item in result["Items"]: context_item = ContextItem.from_item(ddb_context_item) pointer_query = PointerQuery.from_context_item(context_item) pointers.add(self._get_pointer_item(pointer_query)) return pointers def _scan_table(self) -> Set[PointerItem]: result = self.table.scan(FilterExpression=PointerItem.filter_for()) pointers = set() for ddb_item in result["Items"]: pointer = PointerItem.from_item(ddb_item) pointers.add(pointer) return pointers def list(self) -> Set[PointerItem]: """ List all of the inventoried items in the Document Bucket system. :returns: the set of pointers to Document Bucket documents """ return self._scan_table() def retrieve( self, pointer_key: str, expected_context_keys: Set[str] = set(), expected_context: Dict[str, str] = {}, ) -> DocumentBundle: """ Retrieves a document from the Document Bucket system. :param pointer_key: the key for the document to retrieve :param expected_context_keys: the set of context keys that document should have :param expected_context: the set of context key-value pairs that document should have :returns: the document, its key, and associated context """ item = self._get_pointer_item(PointerQuery.from_key(pointer_key)) encrypted_data = self._get_object(item) plaintext, header = aws_encryption_sdk.decrypt( source=encrypted_data, key_provider=self.master_key_provider ) # ENCRYPTION-CONTEXT-START: Making Assertions # ENCRYPTION-CONTEXT-START: Use Encryption Context on Decrypt return DocumentBundle.from_data_and_context( plaintext, item.context ) def store(self, data: bytes, context: Dict[str, str] = {}) -> PointerItem: """ Stores a document in the Document Bucket system. :param data: the bytes of the document to store :param context: the context for this document :returns: the pointer reference for this document in the Document Bucket system """ # ENCRYPTION-CONTEXT-START: Set Encryption Context on Encrypt encrypted_data, header = aws_encryption_sdk.encrypt( source=data, key_provider=self.master_key_provider, ) item = PointerItem.generate(context) self._write_pointer(item) self._write_object(encrypted_data, item) self._populate_key_records(item) return item
def _default_encrypter(cls, plaintext, key_id): import aws_encryption_sdk ciphertext, _ = aws_encryption_sdk.encrypt( source=plaintext, key_provider=cls.get_master_key_provider(key_id)) return base64.b64encode(ciphertext)
def run(aws_kms_cmk, source_plaintext): # type: (str, bytes) -> None """Demonstrate configuring a master key provider 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. # 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", } # 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 AWS 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 AWS 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 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 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())
import aws_encryption_sdk kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ 'arn:aws:kms:us-east-1:873559269338:key/1e1a6a81-93e0-4b9a-954b-cc09802bf3ce' ]) my_plaintext = 'This is some super secret data! Yup, sure is!' my_ciphertext, encryptor_header = aws_encryption_sdk.encrypt( source=my_plaintext, key_provider=kms_key_provider) decrypted_plaintext, decryptor_header = aws_encryption_sdk.decrypt( source=my_ciphertext, key_provider=kms_key_provider) assert my_plaintext == decrypted_plaintext.decode('ascii') assert encryptor_header.encryption_context == decryptor_header.encryption_context
def run(source_plaintext): # type: (bytes) -> None """Demonstrate an encrypt/decrypt cycle using separate public and private raw RSA keyrings. :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", } # 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() # The keyring determines how your data keys are protected. # # Create the encrypt keyring that only has access to the public key. public_key_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. private_key_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, ) # Encrypt your plaintext data using the encrypt keyring. ciphertext, _encrypt_header = aws_encryption_sdk.encrypt( source=source_plaintext, encryption_context=encryption_context, keyring=public_key_keyring ) # Demonstrate that the ciphertext and plaintext are different. assert ciphertext != source_plaintext # Try to decrypt your encrypted data using the *encrypt* keyring. # This demonstrates that you cannot decrypt using the public key. try: aws_encryption_sdk.decrypt(source=ciphertext, keyring=public_key_keyring) except AWSEncryptionSDKClientError: # The public key cannot decrypt. # Reaching this point means everything is working as expected. pass else: # Show that the public keyring could not decrypt. raise AssertionError("The public key can never decrypt!") # Decrypt your encrypted data using the 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, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=private_key_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(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. # 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", } # 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())
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())