文档

Go客户端加密

更新时间:
重要

本文中含有需要您注意的重要提示信息,忽略该信息可能对您的业务造成影响,请务必仔细阅读。

OSS客户端加密是在数据上传至OSS之前,由用户在本地对数据进行加密处理,确保只有密钥持有者才能解密数据,增强数据在传输和存储过程中的安全性。

免责声明

  • 使用客户端加密功能时,您需要对主密钥的完整性和正确性负责。因您维护不当导致主密钥用错或丢失,从而导致加密数据无法解密所引起的一切损失和后果均由您自行承担。

  • 在对加密数据进行复制或者迁移时,您需要对加密元数据的完整性和正确性负责。因您维护不当导致加密元数据出错或丢失,从而导致加密数据无法解密所引起的一切损失和后果均由您自行承担。

使用场景

  • 高度敏感数据:对于包含极高敏感度信息的数据,如个人身份信息(PII)、金融交易记录、医疗健康数据等,用户可能希望在数据离开本地环境之前就对其进行加密处理,确保即使数据在传输过程中被截获,原始数据仍能得到有效保护。

  • 合规要求:某些行业和法规(例如HIPAA、GDPR等)要求对存储在第三方平台上的数据进行严格的加密控制,客户端加密能够满足这些合规性要求,因为密钥由用户自己管理,不通过网络传递,也不由云服务商直接掌握。

  • 更强的自主控制权:企业或者开发者可能希望对加密过程有完全的控制权,包括选择加密算法、管理和轮换密钥。通过客户端加密,可以实现这一目标,确保只有合法授权的用户才能解密和访问数据。

  • 跨区域数据迁移安全性:在将数据从一个地区迁移到另一个地区的过程中,使用客户端加密可以在数据迁移前后保持数据始终处于加密状态,增强了数据在公网传输的安全性。

注意事项

  • 本文以华东1(杭州)外网Endpoint为例。如果您希望通过与OSS同地域的其他阿里云产品访问OSS,请使用内网Endpoint。关于OSS支持的Region与Endpoint的对应关系,请参见访问域名和数据中心

  • 本文以从环境变量读取访问凭证为例。如何配置访问凭证,请参见配置访问凭证

  • 本文以OSS域名新建OSSClient为例。如果您希望通过自定义域名、STS等方式新建OSSClient,请参见初始化

背景信息

使用客户端加密时,会为每个Object生成一个随机数据加密密钥,用该随机数据加密密钥明文对Object的数据进行对称加密。主密钥用于生成随机的数据加密密钥,加密后的内容会当作Object的meta信息保存在服务端。解密时先用主密钥将加密后的随机密钥解密出来,再用解密出来的随机数据加密密钥明文解密Object的数据。主密钥只参与客户端本地计算,不会在网络上进行传输或保存在服务端,以保证主密钥的数据安全。

重要
  • 客户端加密支持分片上传超过5 GB的文件。在使用分片方式上传文件时,需要指定上传文件的总大小和分片大小, 除了最后一个分片外,每个分片的大小要一致,且分片大小目前必须是16的整数倍。

  • 调用客户端加密上传文件后,加密元数据会被保护,无法通过CopyObject修改Object meta信息。

加密方式

对于主密钥的使用,目前支持如下两种方式:

  • 使用KMS托管用户主密钥

    当使用KMS托管用户主密钥用于客户端数据加密时,需要将KMS用户主密钥ID(即CMK ID)传递给SDK。

  • 使用用户自主管理的主密钥(RSA)

    主密钥信息由用户提供,需要用户将主密钥的公钥、私钥信息当做参数传递给SDK。

使用以上两种加密方式能够有效地避免数据泄漏,保护客户端数据安全。即使数据泄漏,其他人也无法解密得到原始数据。

加密元数据

参数

描述

是否必需

x-oss-meta-client-side-encryption-key

加密后的密钥。 经过主密钥加密后再经过base64编码的字符串。

x-oss-meta-client-side-encryption-start

随机产生的用于加密数据的初始值 。经过主密钥加密后再经过base64编码的字符串。

x-oss-meta-client-side-encryption-cek-alg

数据的加密算法。

x-oss-meta-client-side-encryption-wrap-alg

数据密钥的加密算法。

x-oss-meta-client-side-encryption-matdesc

主密钥的描述信息。JSON格式。

警告

强烈建议为每个主密钥都配置描述信息,并保存好主密钥和描述信息之间的对应关系。否则不支持更换主密钥进行加密。

x-oss-meta-client-side-encryption-unencrypted-content-length

加密前的数据长度。如未指定content-length则不生成该参数。

x-oss-meta-client-side-encryption-unencrypted-content-md5

加密前数据的MD5。如未指定MD5,则不生成该参数。

x-oss-meta-client-side-encryption-data-size

若加密Multipart文件需要在init_multipart时传入整个大文件的总大小。

是(分片上传)

x-oss-meta-client-side-encryption-part-size

若加密Multipart文件需要在init_multipart时传入分片大小。

说明

目前分片大小必须是16的整数倍。

是(分片上传)

示例代码

使用主密钥RSA普通上传和下载Object

使用主密钥RSA普通上传和下载Object示例代码如下:

package main

import (
	"bytes"
	"io"
	"log"

	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	osscrypto "github.com/aliyun/aliyun-oss-go-sdk/oss/crypto"
)

func main() {
	// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
	provider, err := oss.NewEnvironmentVariableCredentialsProvider()
	if err != nil {
		log.Fatalf("Error creating credentials provider: %v", err)
	}

	// 创建OSSClient实例。
	// yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。
	client, err := oss.New("yourEndpoint", "", "", oss.SetCredentialsProvider(&provider))
	if err != nil {
		log.Fatalf("Error creating OSS client: %v", err)
	}

	// 创建一个主密钥的描述信息,创建后不允许修改。主密钥描述信息和主密钥一一对应。
	// 如果所有的Object都使用相同的主密钥,主密钥描述信息可以为空,但后续不支持更换主密钥。
	// 如果主密钥描述信息为空,解密时无法判断使用的是哪个主密钥。
	// 强烈建议为每个主密钥都配置主密钥描述信息(json字符串),由客户端保存主密钥和描述信息之间的对应关系(服务端不保存两者之间的对应关系)。

	// 由主密钥描述信息(json字符串)转换的map。
	materialDesc := map[string]string{
		"desc": "your master encrypt key material describe information",
	}

	// 根据主密钥描述信息创建一个主密钥对象。
	// yourRsaPublicKey填写您自主管理的主密钥公钥信息,yourRsaPrivateKey填写您自主管理的主密钥私钥信息。
	masterRsaCipher, err := osscrypto.CreateMasterRsa(materialDesc, "yourRsaPublicKey", "yourRsaPrivateKey")
	if err != nil {
		log.Fatalf("Error creating master RSA cipher: %v", err)
	}

	// 根据主密钥对象创建一个用于加密的接口,使用aes ctr模式加密。
	contentProvider := osscrypto.CreateAesCtrCipher(masterRsaCipher)

	// 获取一个用于客户端加密的已创建Bucket。
	// 客户端加密Bucket和普通Bucket具有相似的用法。
	cryptoBucket, err := osscrypto.GetCryptoBucket(client, "yourBucketName", contentProvider)
	if err != nil {
		log.Fatalf("Error getting crypto bucket: %v", err)
	}

	// PutObject时自动加密。
	err = cryptoBucket.PutObject("yourObjectName", bytes.NewReader([]byte("yourObjectValueByteArrary")))
	if err != nil {
		log.Fatalf("Error putting object: %v", err)
	}

	// GetObject时自动解密。
	body, err := cryptoBucket.GetObject("yourObjectName")
	if err != nil {
		log.Fatalf("Error getting object: %v", err)
	}
	defer body.Close()

	data, err := io.ReadAll(body)
	if err != nil {
		log.Fatalf("Error reading object data: %v", err)
	}
	log.Printf("Data: %s", string(data))
}

使用主密钥RSA分片上传Object

使用主密钥RSA分片上传Object示例代码如下:

package main

import (
	"log"
	"os"

	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	osscrypto "github.com/aliyun/aliyun-oss-go-sdk/oss/crypto"
)

func main() {
	// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
	provider, err := oss.NewEnvironmentVariableCredentialsProvider()
	if err != nil {
		log.Fatalf("Error creating credentials provider: %v", err)
	}

	// 创建OSSClient实例。
	// yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。
	client, err := oss.New("yourEndpoint", "", "", oss.SetCredentialsProvider(&provider))
	if err != nil {
		log.Fatalf("Error creating OSS client: %v", err)
	}

	// 创建一个主密钥的描述信息,创建后不允许修改。主密钥描述信息和主密钥一一对应。
	// 如果所有的Object都使用相同的主密钥,主密钥描述信息可以为空,但后续不支持更换主密钥。
	// 如果主密钥描述信息为空,解密时无法判断使用的是哪个主密钥。
	// 强烈建议为每个主密钥都配置主密钥描述信息(json字符串),由客户端保存主密钥和描述信息之间的对应关系(服务端不保存两者之间的对应关系)。

	// 由主密钥描述信息(json字符串)转换的map。
	materialDesc := map[string]string{
		"desc": "your master encrypt key material describe information",
	}

	// 根据主密钥描述信息创建一个主密钥对象。
	// yourRsaPublicKey填写您自主管理的主密钥公钥信息,yourRsaPrivateKey填写您自主管理的主密钥私钥信息。
	masterRsaCipher, err := osscrypto.CreateMasterRsa(materialDesc, "yourRsaPublicKey", "yourRsaPrivateKey")
	if err != nil {
		log.Fatalf("Error creating master RSA cipher: %v", err)
	}

	// 根据主密钥对象创建一个用于加密的接口,使用aes ctr模式加密。
	contentProvider := osscrypto.CreateAesCtrCipher(masterRsaCipher)

	// 获取一个用于客户端加密的已创建Bucket。
	// 客户端加密Bucket和普通Bucket具有相似的用法。
	cryptoBucket, err := osscrypto.GetCryptoBucket(client, "yourBucketName", contentProvider)
	if err != nil {
		log.Fatalf("Error getting crypto bucket: %v", err)
	}

	fileName := "yourLocalFilePath"
	fileInfo, err := os.Stat(fileName)
	if err != nil {
		log.Fatalf("Error getting file info: %v", err)
	}
	fileSize := fileInfo.Size()

	// 加密上下文信息。
	var cryptoContext osscrypto.PartCryptoContext
	cryptoContext.DataSize = fileSize

	// 期望的分片数,实际分片数以后续计算出来的为准。
	expectPartCount := int64(10)

	// 目前aes ctr加密分片大小需16个字节对齐。
	cryptoContext.PartSize = (fileSize / expectPartCount / 16) * 16

	// 初始化分片上传。
	imur, err := cryptoBucket.InitiateMultipartUpload("yourObjectName", &cryptoContext)
	if err != nil {
		log.Fatalf("Error initiating multipart upload: %v", err)
	}

	// 分割文件。
	chunks, err := oss.SplitFileByPartSize(fileName, cryptoContext.PartSize)
	if err != nil {
		log.Fatalf("Error splitting file: %v", err)
	}

	var partsUpload []oss.UploadPart
	for _, chunk := range chunks {
		part, err := cryptoBucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, int(chunk.Number), cryptoContext)
		if err != nil {
			log.Fatalf("Error uploading part: %v", err)
		}
		partsUpload = append(partsUpload, part)
	}

	// 完成分片上传。
	_, err = cryptoBucket.CompleteMultipartUpload(imur, partsUpload)
	if err != nil {
		log.Fatalf("Error completing multipart upload: %v", err)
	}

	log.Println("Multipart upload completed successfully")
}

解密使用主密钥为不同的RSA加密的Object

解密使用主密钥为不同的RSA加密的Object示例代码如下:

package main

import (
	"bytes"
	"io"
	"log"

	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	osscrypto "github.com/aliyun/aliyun-oss-go-sdk/oss/crypto"
)

// 根据主密钥描述信息查询主密钥。如果需要解密不同的主密钥加密的Object,需要提供此接口。
type MockRsaManager struct{}

func (mg *MockRsaManager) GetMasterKey(matDesc map[string]string) ([]string, error) {
	keyList := []string{"yourRsaPublicKey", "yourRsaPrivateKey"}
	return keyList, nil
}

// 解密不同主密钥加密的Object。
func main() {
	// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
	provider, err := oss.NewEnvironmentVariableCredentialsProvider()
	if err != nil {
		log.Fatalf("Error creating credentials provider: %v", err)
	}

	// 创建OSSClient实例。
	// yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。
	client, err := oss.New("yourEndpoint", "", "", oss.SetCredentialsProvider(&provider))
	if err != nil {
		log.Fatalf("Error creating OSS client: %v", err)
	}

	// 创建一个主密钥的描述信息,创建后不允许修改。主密钥描述信息和主密钥一一对应。
	// 如果所有的Object都使用相同的主密钥,主密钥描述信息可以为空,但后续不支持更换主密钥。
	// 如果主密钥描述信息为空,解密时无法判断使用的是哪个主密钥。
	// 强烈建议为每个主密钥都配置主密钥描述信息(json字符串),由客户端保存主密钥和描述信息之间的对应关系(服务端不保存两者之间的对应关系)。

	// 由主密钥描述信息(json字符串)转换的map。
	materialDesc := map[string]string{
		"desc": "your master encrypt key material describe information",
	}

	// 根据主密钥描述信息创建一个主密钥对象。
	// yourRsaPublicKey填写您自主管理的主密钥公钥信息,yourRsaPrivateKey填写您自主管理的主密钥私钥信息。
	masterRsaCipher, err := osscrypto.CreateMasterRsa(materialDesc, "yourRsaPublicKey", "yourRsaPrivateKey")
	if err != nil {
		log.Fatalf("Error creating master RSA cipher: %v", err)
	}

	// 根据主密钥对象创建一个用于加密的接口,使用aes ctr模式加密。
	contentProvider := osscrypto.CreateAesCtrCipher(masterRsaCipher)

	// 如果需要解密不同主密钥加密的Object,需要提供此接口。
	var mockRsaManager MockRsaManager
	var options []osscrypto.CryptoBucketOption
	options = append(options, osscrypto.SetMasterCipherManager(&mockRsaManager))

	// 获取一个用于客户端加密的已创建Bucket。
	// 客户端加密Bucket和普通Bucket具有相似的用法。
	cryptoBucket, err := osscrypto.GetCryptoBucket(client, "yourBucketName", contentProvider, options...)
	if err != nil {
		log.Fatalf("Error getting crypto bucket: %v", err)
	}

	// PutObject时自动加密。
	err = cryptoBucket.PutObject("yourObjectName", bytes.NewReader([]byte("yourObjectValueByteArrary")))
	if err != nil {
		log.Fatalf("Error putting object: %v", err)
	}

	// GetObject时自动解密。
	body, err := cryptoBucket.GetObject("otherObjectNameEncryptedWithOtherRsa")
	if err != nil {
		log.Fatalf("Error getting object: %v", err)
	}
	defer body.Close()

	data, err := io.ReadAll(body)
	if err != nil {
		log.Fatalf("Error reading object data: %v", err)
	}
	log.Printf("Data: %s", string(data))
}

使用主密钥KMS普通上传和下载Object

使用主密钥KMS普通上传和下载Object示例代码如下:

package main

import (
	"bytes"
	"io"
	"log"

	"github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	crypto "github.com/aliyun/aliyun-oss-go-sdk/oss/crypto"
)

func main() {
	// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
	provider, err := oss.NewEnvironmentVariableCredentialsProvider()
	if err != nil {
		log.Fatalf("Error creating credentials provider: %v", err)
	}

	// 创建OSSClient实例。
	// yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。
	client, err := oss.New("yourEndpoint", "", "", oss.SetCredentialsProvider(&provider))
	if err != nil {
		log.Fatalf("Error creating OSS client: %v", err)
	}

	// 创建KMS Client实例。
	kmsClient, err := kms.NewClientWithAccessKey("yourKmsRegion", "yourKmsAccessKeyId", "yourKmsAccessKeySecret")
	if err != nil {
		log.Fatalf("Error creating KMS client: %v", err)
	}

	// 创建一个主密钥的描述信息,创建后不允许修改。主密钥描述信息和主密钥一一对应。
	// 如果所有的Object都使用相同的主密钥,主密钥描述信息可以为空,但后续不支持更换主密钥。
	// 如果主密钥描述信息为空,解密时无法判断使用的是哪个主密钥。
	// 强烈建议为每个主密钥都配置主密钥描述信息(json字符串),由客户端保存主密钥和描述信息之间的对应关系(服务端不保存两者之间的对应关系)。

	// 由主密钥描述信息(json字符串)转换的map。
	materialDesc := map[string]string{
		"desc": "your kms encrypt key material describe information",
	}

	// 根据主密钥描述信息创建一个主密钥对象。
	// yourKmsId填写KMS用户主密钥ID,即CMK ID。
	masterkmsCipher, err := crypto.CreateMasterAliKms(materialDesc, "yourKmsId", kmsClient)
	if err != nil {
		log.Fatalf("Error creating master KMS cipher: %v", err)
	}

	// 根据主密钥对象创建一个用于加密的接口,使用aes ctr模式加密。
	contentProvider := crypto.CreateAesCtrCipher(masterkmsCipher)

	// 获取一个用于客户端加密的已创建Bucket。
	// 客户端加密Bucket和普通Bucket具有相似的用法。
	cryptoBucket, err := crypto.GetCryptoBucket(client, "yourBucketName", contentProvider)
	if err != nil {
		log.Fatalf("Error getting crypto bucket: %v", err)
	}

	// PutObject时自动加密。
	err = cryptoBucket.PutObject("yourObjectName", bytes.NewReader([]byte("yourObjectValueByteArrary")))
	if err != nil {
		log.Fatalf("Error putting object: %v", err)
	}

	// GetObject时自动解密。
	body, err := cryptoBucket.GetObject("yourObjectName")
	if err != nil {
		log.Fatalf("Error getting object: %v", err)
	}
	defer body.Close()

	data, err := io.ReadAll(body)
	if err != nil {
		log.Fatalf("Error reading object data: %v", err)
	}
	log.Printf("Data: %s", string(data))
}

相关文档