Public Key Encryption in the Cloud

We are increasingly moving into the public cloud for our security, and often need to use public key encryption (asymmetric key) to encrypt…

Public Key Encryption in the Cloud

We are increasingly moving into the public cloud for our security, and often need to use public key encryption (asymmetric key) to encrypt and decrypt data. Basically, for this, we create a key pair: a public key and a private key. One of these keys is used to encrypt, and the other can decrypt. Normally we use the public key to encrypt and decrypt with the private key.

In the following figure, Bob uses Alice’s public key to encrypt data, and which creates ciphertext. Alice then decrypts this ciphertext with her private key:

If we use asymmetric keys, we typically just have the choice of using RSA to encrypt and decrypt data. This is because elliptic curve cryptography does not naturally support encryption and decryption, and we must use hybrid methods (such as with ECIES).

Creating an RSA key pair in AWS

Now, let’s create an RSA key pair for encrypting a file. Our keys are contained in the KMS:

Initially, we can create a Customer-managed key pair with:

The options are 2K, 3K or 4K RSA key pairs. Next, we can give the key an alias:

Then define the ownership of the keys:

And finally the permissions:

The policy is then:

{
"Id": "key-consolepolicy-3",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow access for Key Administrators",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222:user/asecuritysite"
},
"Action": [
"kms:Create*",
"kms:Describe*",
"kms:Enable*",
"kms:List*",
"kms:Put*",
"kms:Update*",
"kms:Revoke*",
"kms:Disable*",
"kms:Get*",
"kms:Delete*",
"kms:TagResource",
"kms:UntagResource",
"kms:ScheduleKeyDeletion",
"kms:CancelKeyDeletion"
],
"Resource": "*"
},
{
"Sid": "Allow use of the key",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222:user/asecuritysite"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:DescribeKey",
"kms:GetPublicKey"
],
"Resource": "*"
},
{
"Sid": "Allow attachment of persistent resources",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222:user/asecuritysite"
},
"Action": [
"kms:CreateGrant",
"kms:ListGrants",
"kms:RevokeGrant"
],
"Resource": "*",
"Condition": {
"Bool": {
"kms:GrantIsForAWSResource": "true"
}
}
}
]
}

Once created, we cannot access the private key, but will be able to view the public key:

We can download this from the console, or from the command prompt:

% aws kms get-public-key --key-id alias/PublicKeyForDemo
{
"KeyId": "arn:aws:kms:us-east-1:103269750866:key/de30e8e6-c753-4a2c-881a-53c761242644",
"PublicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsXDtHOdCeteObzugPf6ENjeft6CDGjbaR9t40++q4jqtSd5JsdYel1Rn3mYL+oXqKQJz9o+aoXdCcMFkhu6wqqDVbIOPT2nsXIuO3p+0G7uUS93g3cc5RodEAn3jb2yBjHjvfs9OBSBM7bh6Kw21YuN/omU1GaL/d4o7+NYu0mDEAmb0Nh+1Q6lrpf+bu1YZ31gVpbLd78xGlv1dz2nqyBG8VaZW90fr05jDjcpDnWm1O9QXl0pEhwNGcvcsxcHodslAZrlzKUre/nZ5MTNL3uigw8w5l2uQLRFiIBpLlHKpcNBaxZu3Za5Mk2Dvj+1+L2PejLydAPfqQB5N8dsOAQIDAQAB",
"CustomerMasterKeySpec": "RSA_2048",
"KeySpec": "RSA_2048",
"KeyUsage": "ENCRYPT_DECRYPT",
"EncryptionAlgorithms": [
"RSAES_OAEP_SHA_1",
"RSAES_OAEP_SHA_256"
]
}

Encrypting with the public key

We can now create a file (1.txt):

And now encrypt using RSA with OAEP padding (RSAES_OAEP_SHA_1) :

$ aws kms encrypt  --key-id alias/PublicKeyForDemo --plaintext fileb://1.txt  \
--query CiphertextBlob --output text > 1.out \
--encryption-algorithm RSAES_OAEP_SHA_1

This will create a Base64 output of the encrypted file (1.out). We can list the file with:

nORNC8PQotPOpf7R1XlCaz8pQKEn5k6r3VOvLZk9ipzl7mGwV25HVqDc/ocK58eV/3u8IQVZDK81UPxk7D1BSc5LN5lvtxnIx8G7TfePxTDuu2+EM5zavvU2S/2ZS+DOV2yHthHfNRKSDLB8a9oMzKBNcsfZBLGZEeZxEs/Rt5T7NdwWXnQsXbrgBJnvbfnNTzgyY4lPLjNqS4DPjA4UVI/3ICUjsEdKNvOv3XebBFvRaJ1a3flBJM5Bxo73gJSidwEZgTPSvGVdA5KOxoDuFh6gPmr/ztRirrrmkjF6zbdWlRfaNb9pLipvZz4KyDUkkKH0v2iYb+zAWzemuZ47sw==

This can be transmitted or stored. But, if we want to decrypt this, we need to convert the Base64 encoded data into binary:

$ base64 -i 1.out  --decode > 1.enc

Now, if we list 1.enc we see that it has binary data:

$ cat 1.enc
M
ТΥyBk?)@'NS-=aWnGV
Ǖ{!Y
5Pd=AIK7oM0o3ھ6KKWl5
|k
̠Mrqѷ5^t,]mO82cO.3jKόT %#GJ6w[hZA$AƎw3Ҽe]ƀ>jb1zͷV5i.*og>
5

Decrypting with the private key

Now to decrypt the file (1.enc) with the associated private key. For this, we use:

$ aws kms decrypt --key-id alias/PublicKeyForDemo --output text --query Plaintext \
--ciphertext-blob fileb://1.enc --encryption-algorithm RSAES_OAEP_SHA_1 \
> 2.out

This produces an output file of 2.out. Again, this is in a Base64 format:

$ cat 2.out
VGhpcyBpcyBteSBzZWNyZXQgZmlsZS4K

so we need to decode this with:

$ base64 -i 2.out  --decode
This is my secret file.

And, that’s it. Note that the two main encryption methods we can use (with padding) are OEAP SHA-1 and OAEP SHA-256:

Using Python

We can use the same type of approach with Python. In the following case we use boto3, select an RSA key pair, and add the option of EncryptionAlgorithm=’RSAES_OAEP_SHA_1' for the encryption and decryption:

import base64
import binascii
import boto3

AWS_REGION = 'us-east-1'

def enable_kms_key(key_ID):
try:
response = kms_client.enable_key(KeyId=key_ID)

except ClientError:
print('KMS Key not working')
raise
else:
return response


def encrypt(secret, alias):
try:
ciphertext = kms_client.encrypt(KeyId=alias,
EncryptionAlgorithm='RSAES_OAEP_SHA_1',
Plaintext=bytes(secret, encoding='utf8'),
)
except ClientError:
print('Problem with encryption.')
raise
else:
return base64.b64encode(ciphertext["CiphertextBlob"])


def decrypt(ciphertext, alias):
try:
plain_text = kms_client.decrypt(KeyId=alias,EncryptionAlgorithm='RSAES_OAEP_SHA_1',CiphertextBlob=bytes(base64.b64decode(ciphertext)))
except ClientError:
print('Problem with decryption.')
raise
else:
return plain_text['Plaintext']

kms_client = boto3.client("kms", region_name=AWS_REGION)

KEY_ID = '68ded69b-6c19-4b34-9f91-f8c2628ee612'
kms = enable_kms_key(KEY_ID)
print(f'Public Key KMS ID {KEY_ID} ')
msg='Hello'
print(f"Plaintext: {msg}")

cipher=encrypt(msg,KEY_ID)
print(f"Cipher {cipher}")
plaintext=decrypt(cipher,KEY_ID)
print(f"Plain: {plaintext.decode()}")

A sample run gives:

KMS key ID 68ded69b-6c19-4b34-9f91-f8c2628ee612 
Plaintext: Hello
Cipher b'SvUOFgRLjpekJn1ZDuivW7YP3mCz3dCGwiWzaekrmcKhDyQbAh7wkBlr0ShC5xjJyC+jJ/0SdcXlKkbzWe8W/EfmKgo8zGcHsiil2F1d6fT9veGxO75ySWz9uwVuoqnsJ0Z32dJG/7nlrGECNU9z984r2cLwiIidgKtqKm2bo48EguVUrU/GuNntxOV0u88r7GShpn6oZV3NPaPOhGEBTpCMGq8nXbv81H6fMWsG92kbVW8PcOqM7cSw0z+XSaj/ndiKzD3yostib+drVtLPOJJ/idBXtOnKPMPEyiKAhMFUxYn+qk104egf5xn6Swh9nU1sogP4Xg0yBT6TdWQACg=='
Plain: Hello

Conclusions

And, that’s it. RSA can be used to encrypt and decrypt data, and where we encrypt with the public key and decrypt with the private key. Thus, anyone who has our public key can encrypt data for us, and for us to decrypt it with our private key. Normally RSA is not used when we have large amounts of data, and a typical use case is to encrypt a symmetric key.

One thing to watch is that the usage of the keys needs to be locked down to certain users and that the owner of the keys needs to be carefully controlled, as, if someone deletes your keys, you will possibly not be able to decrypt files that have been encrypted with those keys. Luckily, there is a 7–30 day time window for a key to be deleted — just in case you have deleted it by mistake, or if someone has maliciously deleted it: