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 金鑰識別碼
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 金鑰識別碼
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)
}