AWS S3 Client-Side Encryption using KMS

AWS S3 client-side encryption is a very important factor to consider when saving data which contains Personal Information (PI). In this post, I will highlight why you should encrypt data on the client-side rather than relying completely on the SSE encryption. I have also kept an example how to encrypt data on the client-side using the AWS KMS (CMK) in Golang

Why encrypt on the client-side?

There are many advantages of encrypting on the client-side. 

  1. Accidentally making objects public: If the bucket or object is made public by mistake no one can decrypt it. Anyone who wants to decrypt needs to have the right permission to the AWS KMS key. You are applying defence in depth. 
  2. Stricter access policy: Even if a user or role has full access to S3 they still need the authorization of AWS KMS to decrypt the object. If they download it, the file will be of no value. 

What is the difference between client-side encryption and encryption at rest in AWS?

With client-side encryption, the data is encrypted on the client’s side before sending it to AWS. This means only the person who has access to the master key can decrypt the data. This adds another layer of encryption to the file. If the S3 object is exposed to the public, the files will be of no value since the user doesn’t have access to the key.

Encryption at rest means that the data is encrypted on the disk which is being saved at AWS Data Centre.

How to encrypt in Go?


    package main

import (
	"bytes"
	"fmt"
	"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/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3crypto"
	"os"
)

func main() {
	sess, err := session.NewSession(&aws.Config{
		Region: aws.String("eu-west-1")})

	var kmsArn = ""
	keywrap := s3crypto.NewKMSKeyGenerator(kms.New(sess), kmsArn)
	builder := s3crypto.AESGCMContentCipherBuilder(keywrap)
	client := s3crypto.NewEncryptionClient(sess, builder)
	// key name in s3
	key := "foo.png"
	bucket := ""
	// file to upload
	file, err := os.Open(key)
	if err != nil {
		fmt.Printf(err.Error())
		os.Exit(0)
	}
	defer file.Close()
	fileInfo, _ := file.Stat()
	var size = fileInfo.Size()
	buffer := make([]byte, size)
	file.Read(buffer)
	var input = &s3.PutObjectInput{
		Bucket: &bucket,
		Key:    &key,
		Body:   bytes.NewReader(buffer),
	}

	_, err = client.PutObject(input)
	if err != nil {
		fmt.Printf(err.Error())
		os.Exit(0)
	}
}


package main

                                                
import (
	"bytes"
	"fmt"
	"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/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3crypto"
	"os"
)

                                                
func main() {
	sess, err := session.NewSession(&aws.Config{
		Region: aws.String("eu-west-1")})

                                                
	var kmsArn = ""
	keywrap := s3crypto.NewKMSKeyGenerator(kms.New(sess), kmsArn)
	builder := s3crypto.AESGCMContentCipherBuilder(keywrap)
	client := s3crypto.NewEncryptionClient(sess, builder)
	// key name in s3
	key := "foo.png"
	bucket := ""
	// file to upload
	file, err := os.Open(key)
	if err != nil {
		fmt.Printf(err.Error())
		os.Exit(0)
	}
	defer file.Close()
	fileInfo, _ := file.Stat()
	var size = fileInfo.Size()
	buffer := make([]byte, size)
	file.Read(buffer)
	var input = &s3.PutObjectInput{
		Bucket: &bucket,
		Key:    &key,
		Body:   bytes.NewReader(buffer),
	}

                                                
	_, err = client.PutObject(input)
	if err != nil {
		fmt.Printf(err.Error())
		os.Exit(0)
	}
}


How to decrypt in Go?

package main

import (
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3crypto"
	"os"
)

func main() {
	sess, err := session.NewSession(&aws.Config{
		Region: aws.String("eu-west-1")})
  
    // key to download
    key := ""
    // s3 bucket name
	bucket := ""

	clientDecrypt := s3crypto.NewDecryptionClient(sess)

	input := &s3.GetObjectInput{
		Bucket: &bucket,
		Key:    &key,
	}

	_, err = clientDecrypt.GetObject(input)
	if err != nil {
		fmt.Printf(err.Error())
	}

	f, err := os.Create(key)
	defer f.Close()
	if err != nil {
		panic(err)
	}
}


package main

                                                
import (
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3crypto"
	"os"
)

                                                
func main() {
	sess, err := session.NewSession(&aws.Config{
		Region: aws.String("eu-west-1")})
  
    // key to download
    key := ""
    // s3 bucket name
	bucket := ""

                                                
	clientDecrypt := s3crypto.NewDecryptionClient(sess)

                                                
	input := &s3.GetObjectInput{
		Bucket: &bucket,
		Key:    &key,
	}

                                                
	_, err = clientDecrypt.GetObject(input)
	if err != nil {
		fmt.Printf(err.Error())
	}

                                                
	f, err := os.Create(key)
	defer f.Close()
	if err != nil {
		panic(err)
	}
}