Recently I need to design a way to store and transfer sensitive data in a secure way, as the data is sensitive, no one could see the data except the owner, our system is only granted to get the plain text at run time, when storing or transferring the data, it must remains encrypted and the password for encryption should be encrypted as well, we also requested not use any hard coded password to do encryption and decryption

Encryption-Decryption

there are 2 types of encryption mechanisms Symmetric encryption and Asymmetric encryption, Symmetric encryption uses a private key to encrypt and decrypt, Asymmetric encryption uses the public key of the recipient to encrypt the message. Then if the recipient wants to decrypt the message the recipient will have to use his/her private key to decrypt, we could tell, Asymmetric encryption is safer than Symmetric encryption, but it's not as efficient as Symmetric encryption and only suitable for small amount of data (e.g: key or password) encryption. https protocol is using the same way , it's using Asymmetric encryption to exchange the password, then use Symmetric encryption to exchange the actual content. I would like to do the same.

for Asymmetric encryption, we use RSA algorithm, for Symmetric encryption, we use AES-256 algorithm. end user could provide us a pubkey and we could use the pubkey to encrypt any AES password and iv, for the actual content , for actual content, we could use AES-256 algorithm to encrypt the actual content and deliver "rsa_encrypted_aes_password , rsa_encrypted_aes_iv , aes_encrypted_data" to end user, when end user get the data, they could use their private key to decrypt rsa_encrypted_aes_password and rsa_encrypted_aes_iv, then use them to decrypt aes_encrypted_data.

pycryptodome provide out of box support for both RSA and AES algorithm

for RSA encryption and decryption, we could use code like below

    from Crypto.Cipher import PKCS1_OAEP
    from Crypto.PublicKey import RSA
    
    def rsa_encrypt(original_data: bytes) -> bytes:
        """
        use cust_key to encrypt the data
        :return: base64 encoded encrypted str
        """
        rsa_public_key = RSA.import_key(get_pubkey())
        cipher_rsa = PKCS1_OAEP.new(rsa_public_key)
        return cipher_rsa.encrypt(original_data)

    def rsa_decrypt(encrypted_content: bytes, private_key: bytes) -> bytes:
        rsa_private_key = RSA.import_key(private_key)
        cipher_rsa = PKCS1_OAEP.new(rsa_private_key)
        return cipher_rsa.decrypt(encrypted_content)

for AES encryption and decryption, we could use code like below

    from Crypto.Cipher import AES
    from Crypto.Util.Padding import pad, unpad
    def aes_encrypt(
        original_data: bytes,
        aes_secret: Optional[bytes] = None,
        aes_iv: Optional[bytes] = None,
    ) -> Tuple[bytes, bytes, bytes]:
        """
        use aes encrypt to encrypt the data
        :param original_data:
        :return: turple of (encrypted data in bytes, rsa encrypted aes_secret, rsa encrypted aes_iv
        """
        # use aes-256-cbc 32*8=256 bit
        if not aes_secret:
            aes_secret = os.urandom(32)
        if not aes_iv:
            aes_iv = os.urandom(16)
        cipher = AES.new(aes_secret, AES.MODE_CBC, iv=aes_iv)
        # use default pkcs7 padding
        return (
            cipher.encrypt(pad(original_data, AES.block_size)),
            rsa_encrypt(aes_secret),
            rsa_encrypt(aes_iv),
        )

    def aes_decrypt(
        encrypted_content: bytes, aes_secret: bytes, aes_iv: bytes
    ) -> bytes:
        cipher = AES.new(aes_secret, AES.MODE_CBC, iv=aes_iv)
        return unpad(cipher.decrypt(encrypted_content), AES.block_size)    

wait a second, there is 1 misleading point, we got user's pubkey and we need to store it somewhere in a secure way, store pubkey in a clear text may not be a good idea, so we decide to use KMS to encrypt the pubkey and put it into s3, with this we do not have any hard coded password and everything we store/transfer is encrypted by default
for KMS encryption and decryption we could use boto3 kms client

to test kms, we could use moto , sample kms test could be found https://github.com/spulec/moto/blob/master/tests/test_kms/test_kms_boto3.py