一、背景
在一些对数据安全有较高要求的场景中,需要对S3上的数据进行加密。有时候可能会遇到以下需求:
- 合规要求,必须加密
- 强度要高,例如AES256
- 加密要简单,最好是对称密钥,加密和解密都是同一个密码
- 与IDC、多云等兼容性问题,不能使用AWS KMS服务来管理密钥,用户自管理密钥
- 不希望编写加密代码
- 不希望应用层承担加密运算的开销
在以上情况下,可使用S3服务的SSE-C功能来满足以上加密需求。
此时可能有同学有疑问,在创建S3存储桶界面上选择加密算法的位置,并没有SSE-C加密方式的选项,那么它是如何运作的?这要从S3的几种加密方式说起。
二、几种S3加密方式介绍
1、可用加密方式
S3的加密方式可分成服务器端加密和客户端加密。服务器端加密包括:
- SSE-S3
- SSE-KMS
- DSSE-KMS(Dual-layer SSE-KMS)
- SSE-C(自管理密钥)
客户端加密是指在业务代码中包含加密算法(一系列数学运算),由客户端代码完成加密,而非S3服务器端负责加密。
服务器端加密和客户端加密的主要区别是发给S3 API接口的是明文还是密文。在服务器加密场景中,用户的代码调用API时候发送的是明文,加密过程由S3执行,加密后以密文保存。由此用户代码不存在运行加密算法的开销,相对简单省事。客户端加密则是用户代码承担加密算法的开销,然后将加密后的密文发给S3的API。
2、不同场景下选择合适的加密方式
以上几种加密方式应该如何选择呢,下边简单归类如下:
最懒,什么也不想做:
- 如果什么也不想做,创建S3存储桶时候在加密选项中选第一项即默认的SSE-S3,由S3服务自己管理密钥。此密钥由S3管理,不涉及KMS,用户自己也不需要管理密钥。代码也不用传输密钥。此为S3默认选项。
可以接受使用KMS管理密钥:
- 如果希望自己管理KMS密钥轮换授权,并通过KMS进行密钥权限管理,可使用S3提供的SSE-KMS加密方式。本方法需要事先在KMS内创建对应密钥。
- 在上一步的基础上,如果希望S3上的Meta信息也被加密,可选择Dual-layer SSE-KMS,是在SSE-KMS基础上再加一层加密。此时密钥也是经过KMS管理的。
不希望使用KMS管理密钥的场景:
- 如果不希望通过KMS管理密钥,并且又不想在EC2和容器内自己写加密算法代码。那么可以试试SSE-C,将密钥和要加密的文件明文都发给S3,S3自动实施加密(AES256),然后S3会把加密后的文件保存到S3上。S3不会保存用户原始明文和密钥明文,加密完成就清空内存相关记录。
- 如果不希望通过KMS管理密钥,而且又不希望S3来执行加密,用户希望通过API发送给S3的文件已经是加密的密文,那么此时就需要用户做客户端加密。用户需要在客户端如EC2/容器内的应用代码上自己编写加密算法的代码,然后执行加密获得密文,最后通过S3的API把加密后的文件写入S3。此方案是完全的客户端自建的加密方案,S3的API接受到的就是密文,用户完全自己掌握,密钥管理和加密过程与云服务完全无关。此方案不依赖KMS/S3,逻辑可兼容任何一个云,开发和管理相对复杂。
以上推荐场景可以看出,每种场景都有特定的优势也有特定的要求,因此可结合实际业务场景进行选择。
3、使用SSE-C加密方式的密钥管理问题
在服务器端加密的四种场景中,其中前三种(SSE-S3,SSE-KMS,DSSE-KMS)是由S3或KMS自动管理密钥,只要为使用者配置调用密钥的正确权限即可自动获得解密的文件,API使用中不需要传输密钥本身。使用这几种方式,理论上不存在忘记密钥的可能,并且从KMS删除密钥也有一个密钥保留周期而不是立刻删除的,因此理论上只有权限配置不正确无法访问密钥。
服务器端加密方式的第四种方式,SSE-C因为是客户自管理密钥,不涉及云端KMS服务,且S3服务器端完成加密时候运算后即清空缓存,即云上不会保存密钥,同时S3日志和审计也不含SSE-C传入的密钥。由此,用户使用SSE-C时候,每次调用API不管是上传还是下载,都自己显式声明参数的方式输入密钥。对于已经上传到云端被SSE-C加密好的文件,如果调用API试图获取文件时候没有发送正确的密钥,那将无权限访问。
因此使用SSE-C自管理密钥的方式,需要特别注意自己保管好密钥,不要丢失、不要忘记密钥。一旦丢失密钥,在任何已被SSE-C加密好的文件都再也无法打开和下载了。此外,AES256加密强度是不可逆的,无法破解,丢失密钥就等于数据丢失了。
三、使用SSE-C
在确保以上使用场景和密钥管理安全能力后,下边来看下如何使用SSE-C。
1、正常创建存储桶
在创建存储桶阶段,无需额外设置,在默认存储规则位置,选择第一项SSE-S3即可。如下截图。

注意:这里不管选择哪一项,在上传一个新文件到S3时候,都可以在API上人为的指定SSE-C算法。因此如果不打算使用KMS而是转而用SSE-C,这里选默认的SSE-S3即可(不需要进入KMS服务界面做配置)。
创建完毕。
2、使用Python代码上传文件并使用SSE-C加密
假设手里没有密钥,本次上传时候生成一个新密钥,生成后本密钥由用户人工保存,云端S3等服务将不会保存这个密钥。
编写Python代码如下,生成密钥并上传。
import boto3
import os
key = os.urandom(32)
readable_key = key.hex()
print("Key in Binary: ", key)
print("Key convert from Binary to HEX: ", key.hex())
print("Key convert from HEX to Binary: ", bytes.fromhex(readable_key))
localfile='/Users/lxy/Downloads/text01.txt'
filename='text01.txt'
bucketname='sse-c-demo'
s3 = boto3.client('s3')
with open(localfile, 'rb') as data:
s3.upload_fileobj(data, bucketname, filename,
ExtraArgs={
'SSECustomerAlgorithm': 'AES256',
'SSECustomerKey': key
}
)
print('upload complete')
这段代码首先调用随机数生成一个密钥,运行后会打出这个随机数(包括二进制和十六进制),然后将一个已经存放在本地的文本文件text01.txt
上传到S3。上传时候使用SSE-C加密方式,并传入二进制格式的密钥。接下来可看到上传成功。
请务必保存好密钥,为了保存方便,可保存十六进制的这个字符串。如果丢失密钥,那么数据不可恢复。
返回结果如下:
Key in Binary: b'\xdc\xf4F\xd3\xeb\x9f\x15#\xc5H\xff7\x85\xf6\xe7\x0b~R\\\x0c\x12VSt\x95\xbe\x03\x80\xeb.\xd2\xfb'
Key convert from Binary to HEX: dcf446d3eb9f1523c548ff3785f6e70b7e525c0c1256537495be0380eb2ed2fb
Key convert from HEX to Binary: b'\xdc\xf4F\xd3\xeb\x9f\x15#\xc5H\xff7\x85\xf6\xe7\x0b~R\\\x0c\x12VSt\x95\xbe\x03\x80\xeb.\xd2\xfb'
为了验证加密成功,我们可通过AWS S3控制台来尝试查看这个文件。在控制台对应的存储桶中,已经可以看到刚才用SSE-C加密的文件。如下截图。

现在试图获取这个文件。点击右上角Download按钮。如下截图。

由于S3控制台界面是没有文件加密的密钥的,因此点击后浏览器会直接报错,提示文件被执行了服务器端加密。没有正确的参数无法获取。

此外,如果通过AWSCLI或者API访问这个文件,访问时候不传入密钥,会看到400错误。
#> aws s3 ls s3://sse-c-demo/text01.txt
2025-02-19 21:31:29 36 text01.txt
#> aws s3 cp s3://sse-c-demo/text01.txt .
fatal error: An error occurred (400) when calling the HeadObject operation: Bad Request
#>
这验证了没有输入正确的密钥是不能获取本文件的。
注意:不是说AWSCLI就不能访问SSE-C加密的文件,AWSCLI的S3命令也是支持使用SSE-C的,只要传入正确的密钥参数,从AWSCLI上也可以正常访问加密的文件。
3、使用Python代码下载文件并传入SSE-C加密的密钥
准备如下一个Python代码执行解密,其中的readable_key
是上一步执行结果保存下来的十六进制密钥。代码中会将其转换为二进制并提交给S3服务。
import boto3
readable_key = 'dcf446d3eb9f1523c548ff3785f6e70b7e525c0c1256537495be0380eb2ed2fb'
print("Key convert from HEX to Binary: ", bytes.fromhex(readable_key))
localfile='/Users/lxy/Downloads/text01_download.txt'
filename='text01.txt'
bucketname='sse-c-demo'
s3 = boto3.client('s3')
with open(localfile, 'wb') as data:
s3.download_fileobj(bucketname, filename, data,
ExtraArgs={
'SSECustomerAlgorithm': 'AES256',
'SSECustomerKey': bytes.fromhex(readable_key)
}
)
print('download complete')
运行后打印结果如下:
Key convert from HEX to Binary: b'\xdc\xf4F\xd3\xeb\x9f\x15#\xc5H\xff7\x85\xf6\xe7\x0b~R\\\x0c\x12VSt\x95\xbe\x03\x80\xeb.\xd2\xfb'
download complete
这其中首先将原先输入的十六进制密钥转换为二进制,然后发给S3服务,并成功将文件解密下载到本地。
四、小结
通过以上可以看出,SSE-C是完全由客户自行管理的加密方式,其加密强度为AES256。使用SSE-C,客户端代码不需要承担执行加密的开销,S3服务会自动完成加密和解密。当然,使用SSE-C一定要管理要密钥,因为云端S3等服务不保存密钥,密钥一旦丢失,已加密的数据将被锁死无人能打开。
五、参考文档
Using server-side encryption with customer-provided keys (SSE-C)
https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html
Python Boto3 SDK
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html
Can anybody please share a working example of Server Side Encryption using Customer Provided Encryption Keys.