Skip to main content

What's SSE and how to use it

· 4 min read

Data protection includes protecting data while in transit (as it travels to and from the server) and at rest (while it is stored on disk). You can do the following to protect your data when using storage services:

  • Secure Socket Layer/Transport Layer Security (SSL/TLS): Protect data in transit.
  • Client-Side Encryption: You manage the encryption keys, encryption process, and related tools all yourself. This can protect data both in transit and at rest, and this is transparent to the server.
  • Server-Side Encryption: Request the server to encrypt your data before it is persisted to disk.

Recently, we implemented SSE support in go-storage, and let's talk about Server-Side Encryption (shorten to SSE) here.

What is SSE

Generally, the server will use AES-256, a symmetric-key algorithm, to encrypt your data. (Exceptionally, Aliyun Object Storage supports SM-4.) AES-256 uses a 32-byte binary key, and the same key is used in encryption and decryption. You have three mutually exclusive options, depending on how you choose to manage the encryption keys.

  • SSE with Keys Managed by Cloud Service Provider
  • SSE with Customer Master Keys (CMKs) Stored in Key Management Service, often called SSE-KMS
  • SSE with Customer-Provided Keys, often called SSE-C

Note: not all service providers provide all of the above.

SSE with Keys Managed by Cloud Service Provider

In this way, the server will manage the keys, and the encryption is transparent to you. Some service providers (e.g., Google Cloud Storage and Azure) set this as default behavior and require no setup or configuration (and no extra charge), so your data is protected silently at rest by SSE! The others require you to request SSE explicitly at the time of object creation by adding a header, or configure a bucket-level policy. Generally, reading an encrypted object requires no special configuration.

SSE with Customer Master Keys (CMKs) Stored in Key Management Service (SSE-KMS)

In this way, you ask the service provider's Key Management Service to generate and store CMKs which can be managed by you (e.g., establishing and maintaining their key policies, enabling and disabling them). Then you can request SSE with the CMK (by specifying its key-id in the header) at the time of object creation, or configure a bucket-level policy. When reading an encrypted object, the client doesn't need to give the key-id, but must have access to the CMK.

SSE with Customer-Provided Keys (SSE-C)

In this way, you create and store encryption keys yourself. Different from Client-Side Encryption, in SSE-C you send unencrypted data along with the encryption key to the server, and then the server uses the key to encrypt your data, but doesn't store the key. When reading an encrypted object, you will have to carry the encryption key again to let the server decrypt your data with the key. In other words, you manage the encryption keys, and the server manages the encryption process and related tools.

How to Use SSE in go-storage

Since different services support different SSE options and have different behaviors, SSE-related pairs are considered system pairs. So you will have to check which options are provided for your specific service first. We listed supported options and related pairs in our service docs. You'd better first choose the SSE option you want to use, and then ignore other pairs in case of being confused with the usage of the pairs.

We recommend using SSE with DefaultPairs, where you only provide SSE-related options during NewStorager and can write the same logic for different services. Take s3 for example. The code snippets come from go-storage-example

func NewS3SseS3() (types.Storager, error) {
return s3.NewStorager(
append(S3Pairs(),
s3.WithDefaultStoragePairs(s3.DefaultStoragePairs{
Write: []types.Pair{
// Required, must be AES256
s3.WithServerSideEncryption(s3.ServerSideEncryptionAes256),
},
}))...,
)
}

func NewS3SseKms(keyId string, context map[string]string, bucketKeyEnabled bool) (types.Storager, error) {
ctx, _ := json.Marshal(context)

return s3.NewStorager(
append(S3Pairs(),
s3.WithDefaultStoragePairs(s3.DefaultStoragePairs{
Write: []types.Pair{
// Required, must be aws:kms
s3.WithServerSideEncryption(s3.ServerSideEncryptionAwsKms),
// Required
//
// Example value: 1234abcd-12ab-34cd-56ef-1234567890ab
s3.WithServerSideEncryptionAwsKmsKeyID(keyId),
// Optional
//
// An encryption context is an optional set of key-value pairs that can contain additional contextual information about the data. https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html#encryption-context
s3.WithServerSideEncryptionContext(base64.StdEncoding.EncodeToString(ctx)),
// Optional, S3 Bucket Key settings will be used if this is not specified.
//
// S3 Bucket Keys can reduce your AWS KMS request costs by decreasing the request traffic from Amazon S3 to AWS KMS. https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html#sse-kms-bucket-keys
s3.WithServerSideEncryptionBucketKeyEnabled(bucketKeyEnabled),
},
}))...,
)
}

func NewS3SseC(key []byte) (types.Storager, error) {
return s3.NewStorager(
append(S3Pairs(),
s3.WithDefaultStoragePairs(s3.DefaultStoragePairs{
Write: []types.Pair{
// Required, must be AES256
s3.WithServerSideEncryptionCustomerAlgorithm(s3.ServerSideEncryptionAes256),
// Required, your AES-256 key, a 32-byte binary value
s3.WithServerSideEncryptionCustomerKey(key),
},
// Now you have to provide customer key to read encrypted data
Read: []types.Pair{
// Required, must be AES256
s3.WithServerSideEncryptionCustomerAlgorithm(s3.ServerSideEncryptionAes256),
// Required, your AES-256 key, a 32-byte binary value
s3.WithServerSideEncryptionCustomerKey(key),
},
}))...,
)
}