OSS客户端加密是在数据上传至OSS之前,由用户在本地对数据进行加密处理,确保只有密钥持有者才能解密数据,增强数据在传输和存储过程中的安全性。
注意事项
本文示例代码以华东1(杭州)的地域ID
cn-hangzhou
为例,默认使用外网Endpoint,如果您希望通过与OSS同地域的其他阿里云产品访问OSS,请使用内网Endpoint。关于OSS支持的Region与Endpoint的对应关系,请参见OSS地域和访问域名。本文以从环境变量读取访问凭证为例。如何配置访问凭证,请参见配置访问凭证。
使用客户端加密功能时,您需要对主密钥的完整性和正确性负责。
在对加密数据进行复制或者迁移时,您需要对加密元数据的完整性和正确性负责。
方法定义
对于主密钥的使用,Go SDK V2目前支持如下两种方式:
使用用户自主管理的主密钥(RSA)
SDK提供了RSA的默认实现,当主密钥信息由用户提供时,用户需要将主密钥的公钥、私钥信息作为参数传递给SDK。
使用用户自定义的主密钥
当RSA主密钥方式无法满足需求时,用户可以自己实现主密钥的加解密行为,本文将以阿里云KMS 3.0为例,介绍如何自定义主密钥加解密。
使用以上两种加密方式能够有效地避免数据泄漏,保护客户端数据安全。即使数据泄漏,其他人也无法解密得到原始数据。
如果您需要了解OSS客户端加密实现的原理,请参考OSS用户指南中的客户端加密。
使用客户端加密,首先您需要实例化加密客户端,然后调用其提供的接口进行操作。您的对象将作为请求的一部分自动加密和解密。
type EncryptionClient struct {
...
}
func NewEncryptionClient(c *Client, masterCipher crypto.MasterCipher, optFns ...func(*EncryptionClientOptions)) (eclient *EncryptionClient, err error)
请求参数列表
参数名 | 类型 | 说明 |
c | *Client | 非加密客户端实例 |
masterCipher | crypto.MasterCipher | 主密钥实例,用于加密和解密数据密钥 |
optFns | ...func(*EncryptionClientOptions) | (可选)加密客户端配置选项 |
其中,EncryptionClientOptions选项说明:
参数 | 类型 | 说明 |
MasterCiphers | []crypto.MasterCipher | 主密钥实例组, 用于解密数据密钥。 |
返回值列表
返回值名 | 类型 | 说明 |
eclient | *EncryptionClient | 加密客户端实例, 当 err 为 nil 时有效 |
err | error | 创建加密客户端的状态,当失败时,err 不为 nil |
其中,EncryptionClient接口列举如下:
基础接口名 | 说明 |
GetObjectMeta | 获取对象的部分元信息 |
HeadObject | 获取对象的部元信息 |
GetObject | 下载对象,并自动解密 |
PutObject | 上传对象,并自动加密 |
InitiateMultipartUpload | 初始化一个分片上传事件 和 分片加密上下文(EncryptionMultiPartContext) |
UploadPart | 初始化一个分片上传事件, 调用该接口上传分片数据,并自动加密。调用该接口时,需要设置 分片加密上下文 |
CompleteMultipartUpload | 在将所有分片数据上传完成后,调用该接口合并成一个文件 |
AbortMultipartUpload | 取消分片上传事件,并删除对应的分片数据 |
ListParts | 列举指定上传事件所属的所有已经上传成功分片 |
高级接口名 | 说明 |
NewDownloader | 创建下载管理器实例 |
NewUploader | 创建上传管理器实例 |
OpenFile | 创建ReadOnlyFile实例 |
辅助接口名 | 说明 |
Unwrap | 获取非加密客户端实例,可以通过该实例访问其它基础接口 |
使用RSA主密钥
使用主密钥RSA简单上传和下载Object
使用主密钥RSA简单上传Object示例代码如下:
package main
import (
"context"
"flag"
"log"
"strings"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/crypto"
)
// 全局变量
var (
region string // 存储区域
bucketName string // 存储空间名称
objectName string // 对象名称
)
// init函数用于初始化命令行参数
func init() {
flag.StringVar(®ion, "region", "", "The region in which the bucket is located.")
flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
flag.StringVar(&objectName, "object", "", "The name of the object.")
}
func main() {
// 解析命令行参数
flag.Parse()
// 检查bucket名称是否为空
if len(bucketName) == 0 {
flag.PrintDefaults()
log.Fatalf("invalid parameters, bucket name required")
}
// 检查region是否为空
if len(region) == 0 {
flag.PrintDefaults()
log.Fatalf("invalid parameters, region required")
}
// 检查对象名称是否为空
if len(objectName) == 0 {
flag.PrintDefaults()
log.Fatalf("invalid parameters, object name required")
}
// 加载默认配置并设置凭证提供者和区域
cfg := oss.LoadDefaultConfig().
WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
WithRegion(region)
// 创建OSS客户端
client := oss.NewClient(cfg)
// 创建一个主密钥的描述信息,创建后不允许修改。主密钥描述信息和主密钥一一对应。
// 如果所有的对象都使用相同的主密钥,主密钥描述信息可以为空,但后续不支持更换主密钥。
// 如果主密钥描述信息为空,解密时无法判断使用的是哪个主密钥。
// 强烈建议为每个主密钥都配置主密钥描述信息,由客户端保存主密钥和描述信息之间的对应关系。
materialDesc := make(map[string]string)
materialDesc["desc"] = "your master encrypt key material describe information"
// 创建只包含 主密钥 的加密客户端
// 如果没有下载操作时,可以不传私钥,即设置为 ""
mc, err := crypto.CreateMasterRsa(materialDesc, "yourRsaPublicKey", "yourRsaPrivateKey")
if err != nil {
log.Fatalf("failed to create master rsa %v", err)
}
// 创建加密客户端
eclient, err := oss.NewEncryptionClient(client, mc)
if err != nil {
log.Fatalf("failed to create encryption client %v", err)
}
// 创建简单上传的请求
putObjRequest := &oss.PutObjectRequest{
Bucket: oss.Ptr(bucketName),
Key: oss.Ptr(objectName),
Body: strings.NewReader("hi, simple put object"),
}
// 使用加密客户端上传对象
putObjRequestResult, err := eclient.PutObject(context.TODO(), putObjRequest)
if err != nil {
log.Fatalf("failed to put object with encryption client %v", err)
}
log.Printf("put object with encryption client result:%#v\n", putObjRequestResult)
// 创建简单下载的请求
getObjRequest := &oss.GetObjectRequest{
Bucket: oss.Ptr(bucketName),
Key: oss.Ptr(objectName),
}
// 使用加密客户端下载对象
getObjRequestResult, err := eclient.GetObject(context.TODO(), getObjRequest)
if err != nil {
log.Fatalf("failed to put object with encryption client %v", err)
}
log.Printf("put object with encryption client result:%#v\n", getObjRequestResult)
}
使用主密钥RSA分片上传Object
使用主密钥RSA分片上传Object示例代码如下:
package main
import (
"bufio"
"context"
"flag"
"io"
"log"
"math/rand"
"sort"
"strings"
"sync"
"time"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/crypto"
)
// 全局变量
var (
region string // 存储区域
bucketName string // 存储空间名称
objectName string // 对象名称
letters = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") // 用于生成随机字符串的字符集
)
// init函数用于初始化命令行参数
func init() {
flag.StringVar(®ion, "region", "", "The region in which the bucket is located.")
flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
flag.StringVar(&objectName, "object", "", "The name of the object.")
}
func main() {
// 解析命令行参数
flag.Parse()
// 检查bucket名称是否为空
if len(bucketName) == 0 {
flag.PrintDefaults()
log.Fatalf("invalid parameters, bucket name required")
}
// 检查region是否为空
if len(region) == 0 {
flag.PrintDefaults()
log.Fatalf("invalid parameters, region required")
}
// 检查对象名称是否为空
if len(objectName) == 0 {
flag.PrintDefaults()
log.Fatalf("invalid parameters, object name required")
}
// 加载默认配置并设置凭证提供者和区域
cfg := oss.LoadDefaultConfig().
WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
WithRegion(region)
// 创建OSS客户端
client := oss.NewClient(cfg)
// 创建一个主密钥的描述信息,创建后不允许修改。主密钥描述信息和主密钥一一对应。
// 如果所有的对象都使用相同的主密钥,主密钥描述信息可以为空,但后续不支持更换主密钥。
// 如果主密钥描述信息为空,解密时无法判断使用的是哪个主密钥。
// 强烈建议为每个主密钥都配置主密钥描述信息,由客户端保存主密钥和描述信息之间的对应关系。
materialDesc := make(map[string]string)
materialDesc["desc"] = "your master encrypt key material describe information"
// 创建只包含 主密钥 的加密客户端
// 如果没有下载操作时,可以不传私钥,即设置为 ""
mc, err := crypto.CreateMasterRsa(materialDesc, "yourRsaPublicKey", "yourRsaPrivateKey")
if err != nil {
log.Fatalf("failed to create master rsa %v", err)
}
// 创建加密客户端
eclient, err := oss.NewEncryptionClient(client, mc)
if err != nil {
log.Fatalf("failed to create encryption client %v", err)
}
// 创建初始化分片上传的请求
initRequest := &oss.InitiateMultipartUploadRequest{
Bucket: oss.Ptr(bucketName),
Key: oss.Ptr(objectName),
}
initResult, err := eclient.InitiateMultipartUpload(context.TODO(), initRequest)
if err != nil {
log.Fatalf("failed to initiate multi part upload %v", err)
}
var wg sync.WaitGroup
var parts oss.UploadParts
count := 3
body := randStr(400000)
reader := strings.NewReader(body)
bufReader := bufio.NewReader(reader)
content, _ := io.ReadAll(bufReader)
partSize := len(body) / count
var mu sync.Mutex
for i := 0; i < count; i++ {
wg.Add(1)
go func(partNumber int, partSize int, i int) {
defer wg.Done()
partRequest := &oss.UploadPartRequest{
Bucket: oss.Ptr(bucketName), // 存储空间名称
Key: oss.Ptr(objectName), // 对象名称
PartNumber: int32(partNumber), // 分片编号
UploadId: oss.Ptr(*initResult.UploadId), // 上传ID
Body: strings.NewReader(string(content[i*partSize : (i+1)*partSize])), // 分片内容
CSEMultiPartContext: initResult.CSEMultiPartContext, // 多部分上下文
}
partResult, err := eclient.UploadPart(context.TODO(), partRequest)
if err != nil {
log.Fatalf("failed to upload part %d: %v", partNumber, err)
}
part := oss.UploadPart{
PartNumber: partRequest.PartNumber, // 分片编号
ETag: partResult.ETag, // ETag
}
mu.Lock()
parts = append(parts, part)
mu.Unlock()
}(i+1, partSize, i)
}
wg.Wait()
sort.Sort(parts)
request := &oss.CompleteMultipartUploadRequest{
Bucket: oss.Ptr(bucketName), // 存储空间名称
Key: oss.Ptr(objectName), // 对象名称
UploadId: oss.Ptr(*initResult.UploadId), // 上传ID
CompleteMultipartUpload: &oss.CompleteMultipartUpload{
Parts: parts, // 已上传的分片列表
},
}
result, err := eclient.CompleteMultipartUpload(context.TODO(), request)
if err != nil {
log.Fatalf("failed to complete multipart upload %v", err)
}
log.Printf("complete multipart upload result:%#v\n", result)
}
// 生成随机字符串
func randStr(n int) string {
b := make([]rune, n)
randMarker := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := range b {
b[i] = letters[randMarker.Intn(len(letters))]
}
return string(b)
}
使用自定义主密钥
使用自定义主密钥简单上传和下载Object
SDK提供了RSA默认实现, 当这个方式不满足用户的需求时,用户可以自己实现主密钥的加解密行为。以下示例代码以阿里云KMS 3.0为例,演示如何自定义主密钥加解密进行简单上传和下载Object。
package main
import (
"context"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"strings"
kms "github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
kmssdk "github.com/aliyun/alibabacloud-dkms-transfer-go-sdk/sdk"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
osscrypto "github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/crypto"
)
// CreateMasterAliKms3 创建由阿里云 KMS 3.0 实现的主密钥接口
// matDesc 将被转换为 JSON 字符串
func CreateMasterAliKms3(matDesc map[string]string, kmsID string, kmsClient *kmssdk.KmsTransferClient) (osscrypto.MasterCipher, error) {
var masterCipher MasterAliKms3Cipher
if kmsID == "" || kmsClient == nil {
return masterCipher, fmt.Errorf("kmsID is empty or kmsClient is nil")
}
var jsonDesc string
if len(matDesc) > 0 {
b, err := json.Marshal(matDesc)
if err != nil {
return masterCipher, err
}
jsonDesc = string(b)
}
masterCipher.MatDesc = jsonDesc
masterCipher.KmsID = kmsID
masterCipher.KmsClient = kmsClient
return masterCipher, nil
}
// 阿里云 KMS 主密钥接口
type MasterAliKms3Cipher struct {
MatDesc string // 密钥描述信息
KmsID string // KMS 密钥 ID
KmsClient *kmssdk.KmsTransferClient // KMS 客户端
}
// 获取主密钥的包装算法
func (mrc MasterAliKms3Cipher) GetWrapAlgorithm() string {
return "KMS/ALICLOUD"
}
// 获取主密钥的描述信息
func (mkms MasterAliKms3Cipher) GetMatDesc() string {
return mkms.MatDesc
}
// 使用阿里云 KMS 加密数据,主要用于加密对象的对称密钥和 IV
func (mkms MasterAliKms3Cipher) Encrypt(plainData []byte) ([]byte, error) {
base64Plain := base64.StdEncoding.EncodeToString(plainData)
request := kms.CreateEncryptRequest()
request.RpcRequest.Scheme = "https"
request.RpcRequest.Method = "POST"
request.RpcRequest.AcceptFormat = "json"
request.KeyId = mkms.KmsID
request.Plaintext = base64Plain
response, err := mkms.KmsClient.Encrypt(request)
if err != nil {
return nil, err
}
return base64.StdEncoding.DecodeString(response.CiphertextBlob)
}
// 使用阿里云 KMS 解密数据,主要用于解密对象的对称密钥和 IV
func (mkms MasterAliKms3Cipher) Decrypt(cryptoData []byte) ([]byte, error) {
base64Crypto := base64.StdEncoding.EncodeToString(cryptoData)
request := kms.CreateDecryptRequest()
request.RpcRequest.Scheme = "https"
request.RpcRequest.Method = "POST"
request.RpcRequest.AcceptFormat = "json"
request.CiphertextBlob = string(base64Crypto)
response, err := mkms.KmsClient.Decrypt(request)
if err != nil {
return nil, err
}
return base64.StdEncoding.DecodeString(response.Plaintext)
}
var (
region string // 存储区域
bucketName string // 存储空间名称
objectName string // 对象名称
)
// init函数用于初始化命令行参数
func init() {
flag.StringVar(®ion, "region", "", "The region in which the bucket is located.")
flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
flag.StringVar(&objectName, "object", "", "The name of the object.")
}
func main() {
// 解析命令行参数
flag.Parse()
// 检查存储区域是否为空
if len(region) == 0 {
flag.PrintDefaults()
log.Fatalf("invalid parameters, region required")
}
// 检查存储空间名称是否为空
if len(bucketName) == 0 {
flag.PrintDefaults()
log.Fatalf("invalid parameters, bucket name required")
}
// 检查对象名称是否为空
if len(objectName) == 0 {
flag.PrintDefaults()
log.Fatalf("invalid parameters, object name required")
}
// 加载默认配置并设置凭证提供者和区域
cfg := oss.LoadDefaultConfig().
WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
WithRegion(region)
// 创建 OSS 客户端
client := oss.NewClient(cfg)
// 创建 KMS 客户端
kmsRegion := "cn-hangzhou" // KMS 区域
kmsAccessKeyId := "access key id" // KMS 访问密钥 ID
kmsAccessKeySecret := "access key secret" // KMS 访问密钥秘密
kmsKeyId := "kms id" // KMS 密钥 ID
kmsClient, err := kmssdk.NewClientWithAccessKey(kmsRegion, kmsAccessKeyId, kmsAccessKeySecret, nil)
if err != nil {
log.Fatalf("failed to create kms sdk client %v", err)
}
// 创建密钥描述信息
materialDesc := make(map[string]string)
materialDesc["desc"] = "your kms encrypt key material describe information"
// 创建主密钥实例
masterKmsCipher, err := CreateMasterAliKms3(materialDesc, kmsKeyId, kmsClient)
if err != nil {
log.Fatalf("failed to create master AliKms3 %v", err)
}
// 创建加密客户端
eclient, err := oss.NewEncryptionClient(client, masterKmsCipher)
if err != nil {
log.Fatalf("failed to create encryption client %v", err)
}
// 创建上传对象的请求
request := &oss.PutObjectRequest{
Bucket: oss.Ptr(bucketName), // 存储空间名称
Key: oss.Ptr(objectName), // 对象名称
Body: strings.NewReader("hi kms"), // 要上传的数据
}
// 执行上传对象的操作
result, err := eclient.PutObject(context.TODO(), request)
if err != nil {
log.Fatalf("failed to put object with encryption client %v", err)
}
log.Printf("put object with encryption client result:%#v\n", result)
// 创建下载对象的请求
getRequest := &oss.GetObjectRequest{
Bucket: oss.Ptr(bucketName), // 存储空间名称
Key: oss.Ptr(objectName), // 对象名称
}
// 执行下载对象的操作
getResult, err := eclient.GetObject(context.TODO(), getRequest)
if err != nil {
log.Fatalf("failed to get object with encryption client %v", err)
}
defer getResult.Body.Close()
// 读取下载的数据
data, err := io.ReadAll(getResult.Body)
if err != nil {
log.Fatalf("failed to read all %v", err)
}
log.Printf("get object data:%s\n", data)
}