AWS Encryption with GoAs we increasing move to the public cloud, the usage of encryption keys becomes ever more important. One of the key elements of this is to control the access to keys. In AWS, we have KMS (Key Management Service), and which allows access policies to be defined for our encryption keys. Overall, the keys stay in AWS, and have an access policy defined. Initially, we can create either a symmetric key (to be used for encryption or to generate HMAC) or an asymmetric key (to be used for encryption or digital signing). |
Theory
As we increasing move to the public cloud, the usage of encryption keys becomes ever more important. This includes encrypting data and for digital signing. Some advantages include:
- Encryption in the cloud. This allows us to encrypt data in the cloud, and not have it processed on a client machine.
- Access policies. With a cloud-based system, it is possible to define a strict access policy on the usage of encryption keys and strongly based on cloud-based roles.
- Auditing. Through AWS CloudTrail, it is possible to log the access to encryption keys, and which can support regulatory and compliance needs.
- BYOK (Bring Your Own Keys). This allows users to create their own keys, and then upload them to the cloud.
In AWS, we have KMS (Key Management Service), and which generates and stores encryption keys. These keys never leave Amazon's HSM (Hardware Security Modules) (HSMs) and which have been validated under FIPS 140–2. When exported, they are then only available in an encrypted form. Overall, it costs $1 per month to store a key (but keys that are generated for AWS services are stored for free). The usage of the key is free up to certain access thresholds. Also, the keys can only be used in the geographical region they are setup for (such as for "us-east-1").
Symmetric or asymmetric keys
Overall, the encryption keys stay within a trusted hardware environment within AWS and are locked to a specific region and have a strict access policy applied related to AWS IAM (Identity and Access Management) identifier. These are related to least-privilege permissions - and where no permission is granted unless it is defined in the access policy.
Initially, we can create either a symmetric key (to be used for encryption/decryption or to generate an HMAC) or an asymmetric key (to be used for encryption or digital signing). With HMAC, we create a symmetric key which can be used to sign a message and then use the same key to verify the signature. With digital signing, we use a private (secret) key to sign a message, which is then verified by a public key.
In the AWS control, we can create a symmetric key with:
The generated key then has a Key ID, and where we define its usage (such as for encryption and decryption):
The user then defines the key alias and an ARN (Amazon Resource Names) identity. Next, we can define a policy for the usage of the key:
{ "Id": "key-consolepolicy-3", "Version": "2012-10-17", "Statement": [ { "Sid": "Enable IAM User Permissions", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::960039751898:root" }, "Action": "kms:*", "Resource": "*" }, { "Sid": "Allow access for Key Administrators", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::960039751898:user/bill" }, "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::960039751898:user/bill" }, "Action": [ "kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey" ], "Resource": "*" }, { "Sid": "Allow attachment of persistent resources", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::960039751898:user/bill" }, "Action": [ "kms:CreateGrant", "kms:ListGrants", "kms:RevokeGrant" ], "Resource": "*", "Condition": { "Bool": { "kms:GrantIsForAWSResource": "true" } } } ] }
In this case we can see that the user (bill) is allowed to encrypt and decrypt, but no other user is allowed to do these operations. The created key is then described by a Key ID and an ARN:
Now we can write a Golang program to use this key to encrypt plaintext, and then decrypt it. We first need to define the AWS region and the ARN. Along with this we also define the AWS credentials - for AWS_Access_Key_ID and AWS_Secret_Access_Key. These can either be defined in the program or within the ~/.aws/credentials folder [here]:
package main import ( "fmt" "os" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/kms" "github.com/aws/aws-sdk-go/aws/credentials" ) func main() { message := "This is a secret" const keyID = "arn:aws:kms:us-east-1:904269751807:key/9954354-f122-45f9-b6c6-29e571562a22" argCount := len(os.Args[1:]) if argCount > 0 { message = os.Args[1] } creds := credentials.NewStaticCredentials("AWS_Access_Key_ID", "AWS_Secret_Access_Key", "") sess, _ := session.NewSession(&aws.Config{Region: aws.String("us-east-1"), Credentials: creds}) svc := kms.New(sess) encMethod := &kms.EncryptInput{KeyId: aws.String(keyID), Plaintext: []byte(message)} cipherText, _:= svc.Encrypt(encMethod) fmt.Printf("Input: %s\n", message) fmt.Printf("Encrypted: %x\n", cipherText.CiphertextBlob) inputDecrypt := &kms.DecryptInput{CiphertextBlob: cipherText.CiphertextBlob} respDecrypt, _ := svc.Decrypt(inputDecrypt) fmt.Printf("Decrypted: %s\n", string(respDecrypt.Plaintext)) }
A sample run is:
Input: Testing 12345 Encrypted: 0102020078eba286059ff61e64dc8414c5fc8ff716dda2c8c47903983373a262fcc3a1503a01bfe78037a671257e78e683cd5483b3f00000006b306906092a864886f70d010706a05c305a020100305506092a864886f70d010701301e060960864801650304012e3011040c0516bdc01f1edea632e7fd6102011080282531e65f367891af4ac6c418bf804bbe0bed2e008e30cb93670089c83dc44e7e94950770b8a70488 Decrypted: Testing 12345
AWS CLI Encryption and Decryption
Along with using a programming language, we can also perform the operations with the CLI. To create a key:
aws kms create-key --tags TagKey=Purpose, TagValue=MyKey --description "For testing"
We can list our key from an alias:
> aws kms list-aliases { "Aliases": [ { "AliasName": "alias/Test01", "AliasArn": "arn:aws:kms:us-east-1:904269751807:alias/Test01", "TargetKeyId": "9954354-f122-45f9-b6c6-29e571562a22", "CreationDate": 1657877917.647, "LastUpdatedDate": 1657877917.647 },
We can then encrypt a plaintext message of "Hello 123" with:
aws kms encrypt --plaintext hello.txt --key-id="9954354-f122-45f9-b1c3-29e571562a22" --encryption-context purpose=test { "CiphertextBlob": "AQICAHjrooYFn/YeZNyEFMX8j/cW3aLIxHkDmDNzomL8w6FQOgE+rt03vmCwrYR5fP153zPjAAAAZzBlBgkqhkiG9w0BBwagWDBWAgEAMFEGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMNvsXMqFmyq7j5tMeAgEQgCSrzEA0EJEgrstoNvnbVfH/EaptXzqeOwADaRLMvhDo7F9G6nM=", "KeyId": "arn:aws:kms:us-east-1:904269751807:key/9954354-f122-45f9-b6c6-29e571562a22", "EncryptionAlgorithm": "SYMMETRIC_DEFAULT" }
We can see we have ciphertext of "AQICAH … m4sE=". Next, we can decrypt with:
aws kms decrypt --ciphertext-blob "AQICAHjrooYFn/YeZNyEFMX8j/cW3aLIxHkDmDNzomL8w6FQOgEnx2HXcpIIObK9qjHFGit3AAAAZzBlBgkqhkiG9w0BBwagWDBWAgEAMFEGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM/qzr60lyQS5A5yZ8AgEQgCQueKSLpCjdQiYHKWPX3NUT87cOvcmrT/RDYlBcDge7Ymzm4sE=" --key-id "9954354-d28-45f9-b1c3-29e571562a22"
Asymmetric keys and HMAC
With asymmetric keys, we can select RSA 2K, 3K or 4K:
For digital signing, we can add the elliptic curve methods of P256, P521 and sepc256k1: