本文中含有需要您注意的重要提示信息,忽略該信息可能對您的業務造成影響,請務必仔細閱讀。
OSS客戶端加密是在數據上傳至OSS之前,由用戶在本地對數據進行加密處理,確保只有密鑰持有者才能解密數據,增強數據在傳輸和存儲過程中的安全性。
免責聲明
使用客戶端加密功能時,您需要對主密鑰的完整性和正確性負責。因您維護不當導致主密鑰用錯或丟失,從而導致加密數據無法解密所引起的一切損失和后果均由您自行承擔。
在對加密數據進行復制或者遷移時,您需要對加密元數據的完整性和正確性負責。因您維護不當導致加密元數據出錯或丟失,從而導致加密數據無法解密所引起的一切損失和后果均由您自行承擔。
使用場景
高度敏感數據:對于包含極高敏感度信息的數據,如個人身份信息(PII)、金融交易記錄、醫療健康數據等,用戶可能希望在數據離開本地環境之前就對其進行加密處理,確保即使數據在傳輸過程中被截獲,原始數據仍能得到有效保護。
合規要求:某些行業和法規(例如HIPAA、GDPR等)要求對存儲在第三方平臺上的數據進行嚴格的加密控制,客戶端加密能夠滿足這些合規性要求,因為密鑰由用戶自己管理,不通過網絡傳遞,也不由云服務商直接掌握。
更強的自主控制權:企業或者開發者可能希望對加密過程有完全的控制權,包括選擇加密算法、管理和輪換密鑰。通過客戶端加密,可以實現這一目標,確保只有合法授權的用戶才能解密和訪問數據。
跨區域數據遷移安全性:在將數據從一個地區遷移到另一個地區的過程中,使用客戶端加密可以在數據遷移前后保持數據始終處于加密狀態,增強了數據在公網傳輸的安全性。
注意事項
本文以華東1(杭州)外網Endpoint為例。如果您希望通過與OSS同地域的其他阿里云產品訪問OSS,請使用內網Endpoint。關于OSS支持的Region與Endpoint的對應關系,請參見OSS訪問域名、數據中心、開放端口。
本文以從環境變量讀取訪問憑證為例。如何配置訪問憑證,請參見配置訪問憑證。
本文以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請按實際情況填寫。
// yourRegion填寫Bucket所在地域,以華東1(杭州)為例,填寫為cn-hangzhou。其它Region請按實際情況填寫。
clientOptions := []oss.ClientOption{oss.SetCredentialsProvider(&provider)}
clientOptions = append(clientOptions, oss.Region("yourRegion"))
// 設置簽名版本
clientOptions = append(clientOptions, oss.AuthVersion(oss.AuthV4))
client, err := oss.New("yourEndpoint", "", "", clientOptions...)
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請按實際情況填寫。
// yourRegion填寫Bucket所在地域,以華東1(杭州)為例,填寫為cn-hangzhou。其它Region請按實際情況填寫。
clientOptions := []oss.ClientOption{oss.SetCredentialsProvider(&provider)}
clientOptions = append(clientOptions, oss.Region("yourRegion"))
// 設置簽名版本
clientOptions = append(clientOptions, oss.AuthVersion(oss.AuthV4))
client, err := oss.New("yourEndpoint", "", "", clientOptions...)
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請按實際情況填寫。
// yourRegion填寫Bucket所在地域,以華東1(杭州)為例,填寫為cn-hangzhou。其它Region請按實際情況填寫。
clientOptions := []oss.ClientOption{oss.SetCredentialsProvider(&provider)}
clientOptions = append(clientOptions, oss.Region("yourRegion"))
// 設置簽名版本
clientOptions = append(clientOptions, oss.AuthVersion(oss.AuthV4))
client, err := oss.New("yourEndpoint", "", "", clientOptions...)
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請按實際情況填寫。
// yourRegion填寫Bucket所在地域,以華東1(杭州)為例,填寫為cn-hangzhou。其它Region請按實際情況填寫。
clientOptions := []oss.ClientOption{oss.SetCredentialsProvider(&provider)}
clientOptions = append(clientOptions, oss.Region("yourRegion"))
// 設置簽名版本
clientOptions = append(clientOptions, oss.AuthVersion(oss.AuthV4))
client, err := oss.New("yourEndpoint", "", "", clientOptions...)
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))
}
相關文檔
關于配置服務器端加密的API接口說明,請參見SetBucketEncryption。
關于獲取服務器端加密配置的API接口說明,請參見GetBucketEncryption。