全部產品
Search
文件中心

Object Storage Service:用戶端加密(Go SDK V2)

更新時間:Aug 02, 2025

OSS用戶端加密是在資料上傳至OSS之前,由使用者在本地對資料進行加密處理,確保只有密鑰持有人才能解密資料,增強資料在傳輸和預存程序中的安全性。

注意事項

  • 本文範例程式碼以華東1(杭州)的地區IDcn-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(&region, "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(&region, "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 金鑰識別碼
	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(&region, "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 金鑰識別碼

	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)
}

相關文檔

  • 關於OSS用戶端加密實現的原理,請參見用戶端加密

  • 關於用戶端加密的Go SDK操作指南,請參見操作指南

  • 關於使用主要金鑰RSA簡單上傳和下載Object的完整程式碼範例,請參見Github樣本

  • 關於使用主要金鑰KMS簡單上傳和下載Object的完整程式碼範例,請參見Github樣本