OSS用戶端加密是在資料上傳至OSS之前,使用者在本地加密資料,確保資料在傳輸和預存程序中的安全性。
免責聲明
使用用戶端加密功能時,您需要對主要金鑰的完整性和正確性負責。因您維護不當導致主要金鑰用錯或丟失,從而導致加密資料無法解密所引起的一切損失和後果均由您自行承擔。
在對加密資料進行複製或者遷移時,您需要對加密中繼資料的完整性和正確性負責。因您維護不當導致加密中繼資料出錯或丟失,從而導致加密資料無法解密所引起的一切損失和後果均由您自行承擔。
使用情境
高度敏感性資料:對於高度敏感性資料(如PII、金融交易記錄、醫學資料),使用者可以在資料離開本地前加密,確保傳輸過程中的資料安全。
合規要求:某些法規(如HIPAA、GDPR)要求嚴格的加密控制,用戶端加密滿足這些要求,因為密鑰由使用者管理,不通過網路傳遞。
更強的自主控制權:企業或開發人員希望完全控制加密過程(選擇演算法、管理密鑰),用戶端加密確保只有授權使用者能解密和訪問資料。
跨地區資料移轉安全性:在跨地區資料移轉中,用戶端加密確保資料始終加密,增強公網傳輸安全性。
背景資訊
使用用戶端加密時,會為每個Object產生一個隨機資料加密金鑰,用該隨機資料加密金鑰明文對Object的資料進行對稱式加密。主要金鑰用於產生隨機的資料加密金鑰,加密後的內容會作為Object的meta資訊儲存在服務端。解密時先用主要金鑰將加密後的隨機密鑰解密出來,再用解密出來的隨機資料加密金鑰明文解密Object的資料。主要金鑰只參與用戶端本地計算,不會在網路上進行傳輸或儲存在服務端,以保證主要金鑰的資料安全。
用戶端加密支援分區上傳超過5 GB的檔案。上傳時需指定檔案總大小和分區大小,除最後一個分區外,其他分區大小需一致,且必須是16的整數倍。
使用用戶端加密上傳檔案後,加密中繼資料受保護,無法通過CopyObject修改Object meta資訊。
對於主要金鑰的使用,目前支援如下兩種方式:
完整的範例程式碼請參見GitHub。
使用KMS託管使用者主要金鑰
當使用KMS託管使用者主要金鑰用於用戶端資料加密時,無需向OSS加密用戶端提供任何加密金鑰,只需要在上傳Object時指定KMS使用者主要金鑰ID(即CMK ID)。具體工作原理如下圖所示。
加密並上傳Object
擷取加密金鑰。
通過CMK ID,用戶端向KMS請求資料密鑰,KMS返回資料清除金鑰和資料密文密鑰。
加密資料並上傳至OSS。
用戶端使用資料清除金鑰加密Object,並將加密後的Object和資料密文密鑰上傳至OSS。
下載並解密Object
下載Object。
用戶端從OSS下載加密的Object和資料密文密鑰。
解密Object。
用戶端將資料密文密鑰和CMK ID發送至KMS伺服器,KMS解密並返回資料清除金鑰。
用戶端會為每一個上傳的Object擷取一個唯一的資料加密金鑰。
為了保證資料的安全性,建議定期輪換或者更新CMK。
您需要維護CMK ID與Object之間的映射關係。
使用使用者自主管理密鑰
使用使用者自主管理密鑰時,您需要產生並保管加密金鑰。當用戶端加密Object時,您需上傳加密金鑰(對稱或非對稱)至用戶端。具體加密過程如下圖所示。
加密並上傳Object
使用者向用戶端提供主要金鑰(對稱或非對稱)。
用戶端產生一次性對稱金鑰(資料密鑰),用於加密單個Object(每個Object產生一個資料密鑰)。
用戶端使用資料祕密金鑰加密Object,並用主要金鑰加密資料密鑰。
用戶端將加密的Object和加密的資料密鑰作為Object中繼資料上傳至OSS。
下載並解密Object
用戶端從OSS下載加密的Object及其中繼資料。
用戶端使用中繼資料中的資訊,授權確定主要金鑰來解密資料密鑰,然後用解密後的資料密鑰解密Object。
用戶端不會將使用者主要金鑰以及未加密的資料發送至OSS。請妥善保管加密金鑰,若密鑰丟失,將無法解密資料。
資料密鑰由用戶端隨機產生。
使用阿里雲SDK
以下僅列舉常見SDK用戶端加密的程式碼範例。關於其他SDK用戶端加密的程式碼範例,請參見SDK簡介。
import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.crypto.SimpleRSAEncryptionMaterials;
import com.aliyun.oss.model.OSSObject;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;
public class Demo {
public static void main(String[] args) throws Throwable {
// Endpoint以華東1(杭州)為例,其它Region請按實際情況填寫。
String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
// 從環境變數中擷取訪問憑證。運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
// 填寫Bucket名稱,例如examplebucket。
String bucketName = "examplebucket";
// 填寫Object完整路徑,例如exampleobject.txt。Object完整路徑中不能包含Bucket名稱。
String objectName = "exampleobject.txt";
String content = "Hello OSS!";
// 填寫您的RSA私密金鑰字串,可以使用OpenSSL工具產生。以下為RSA私密金鑰字串的樣本值。
final String PRIVATE_PKCS1_PEM =
"-----BEGIN RSA PRIVATE KEY-----\n" +
"MIICWwIBAAKBgQCokfiAVXXf5ImFzKDw+XO/UByW6mse2QsIgz3ZwBtMNu59fR5z\n" +
"ttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC5MFO1PByrE/MNd5AAfSVba93\n" +
"I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MmR1EKib1Id8hpooY5xaQIDAQAB\n" +
"AoGAOPUZgkNeEMinrw31U3b2JS5sepG6oDG2CKpPu8OtdZMaAkzEfVTJiVoJpP2Y\n" +
"nPZiADhFW3e0ZAnak9BPsSsySRaSNmR465cG9tbqpXFKh9Rp/sCPo4Jq2n65yood\n" +
"JBrnGr6/xhYvNa14sQ6xjjfSgRNBSXD1XXNF4kALwgZyCAECQQDV7t4bTx9FbEs5\n" +
"36nAxPsPM6aACXaOkv6d9LXI7A0J8Zf42FeBV6RK0q7QG5iNNd1WJHSXIITUizVF\n" +
"6aX5NnvFAkEAybeXNOwUvYtkgxF4s28s6gn11c5HZw4/a8vZm2tXXK/QfTQrJVXp\n" +
"VwxmSr0FAajWAlcYN/fGkX1pWA041CKFVQJAG08ozzekeEpAuByTIOaEXgZr5MBQ\n" +
"gBbHpgZNBl8Lsw9CJSQI15wGfv6yDiLXsH8FyC9TKs+d5Tv4Cvquk0efOQJAd9OC\n" +
"lCKFs48hdyaiz9yEDsc57PdrvRFepVdj/gpGzD14mVerJbOiOF6aSV19ot27u4on\n" +
"Td/3aifYs0CveHzFPQJAWb4LCDwqLctfzziG7/S7Z74gyq5qZF4FUElOAZkz123E\n" +
"yZvADwuz/4aK0od0lX9c4Jp7Mo5vQ4TvdoBnPuGo****\n" +
"-----END RSA PRIVATE KEY-----";
// 填寫您的RSA公開金鑰字串,可以使用OpenSSL工具產生。以下為RSA公開金鑰字串的樣本值。
final String PUBLIC_X509_PEM =
"-----BEGIN PUBLIC KEY-----\n" +
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCokfiAVXXf5ImFzKDw+XO/UByW\n" +
"6mse2QsIgz3ZwBtMNu59fR5zttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC\n" +
"5MFO1PByrE/MNd5AAfSVba93I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MnR\n" +
"1EKib1Id8hpooY5xaQID****\n" +
"-----END PUBLIC KEY-----";
// 建立一個RSA金鑰組。
RSAPrivateKey privateKey = SimpleRSAEncryptionMaterials.getPrivateKeyFromPemPKCS1(PRIVATE_PKCS1_PEM);
RSAPublicKey publicKey = SimpleRSAEncryptionMaterials.getPublicKeyFromPemX509(PUBLIC_X509_PEM);
KeyPair keyPair = new KeyPair(publicKey, privateKey);
// 建立主要金鑰RSA的描述資訊。建立後不允許修改。主要金鑰描述資訊和主要金鑰一一對應。
// 如果所有的object都使用相同的主要金鑰,主要金鑰描述資訊可以為空白,但後續不支援更換主要金鑰。
// 如果主要金鑰描述資訊為空白,解密時無法判斷檔案使用的是哪個主要金鑰進行加密。
// 強烈建議為每個主要金鑰都配置描述資訊,由用戶端儲存主要金鑰和描述資訊之間的對應關係(服務端不儲存兩者之間的對應關係)。
Map<String, String> matDesc = new HashMap<String, String>();
matDesc.put("desc-key", "desc-value");
// 建立RSA加密材料。
SimpleRSAEncryptionMaterials encryptionMaterials = new SimpleRSAEncryptionMaterials(keyPair, matDesc);
// 如果要下載並解密其他RSA祕密金鑰加密的檔案,請將其他主要金鑰及其描述資訊添加到加密材料中。
// encryptionMaterials.addKeyPairDescMaterial(<otherKeyPair>, <otherKeyPairMatDesc>);
// 建立加密用戶端。
OSSEncryptionClient ossEncryptionClient = new OSSEncryptionClientBuilder().
build(endpoint, credentialsProvider, encryptionMaterials);
try {
// 加密上傳檔案。
ossEncryptionClient.putObject(bucketName, objectName, new ByteArrayInputStream(content.getBytes()));
// 下載檔案時自動解密。
OSSObject ossObject = ossEncryptionClient.getObject(bucketName, objectName);
BufferedReader reader = new BufferedReader(new InputStreamReader(ossObject.getObjectContent()));
StringBuffer buffer = new StringBuffer();
String line;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
reader.close();
// 查看解密後的內容是否與上傳的明文一致。
System.out.println("Put plain text: " + content);
System.out.println("Get and decrypted text: " + buffer.toString());
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossEncryptionClient != null) {
ossEncryptionClient.shutdown();
}
}
}
}
# -*- coding: utf-8 -*-
import os
import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider
from oss2.crypto import RsaProvider
from oss2.crypto import AliKMSProvider
# 從環境變數中擷取訪問憑證。運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
auth = oss2.ProviderAuthV4(EnvironmentVariableCredentialsProvider())
kms_provider = AliKMSProvider(auth, 'yourRegion', 'yourCMKID')
# 填寫Bucket所在地區對應的Endpoint。以華東1(杭州)為例,Endpoint填寫為https://oss-cn-hangzhou.aliyuncs.com。
endpoint = "https://oss-cn-hangzhou.aliyuncs.com"
# 填寫Endpoint對應的Region資訊,例如cn-hangzhou。注意,v4簽名下,必須填寫該參數
region = "cn-hangzhou"
bucket = oss2.CryptoBucket(auth, endpoint, 'yourBucketName', crypto_provider=kms_provider, region=region)
key = 'motto.txt'
content = b'a' * 1024 * 1024
filename = 'download.txt'
# 上傳檔案。
bucket.put_object(key, content, headers={'content-length': str(1024 * 1024)})
# 下載OSS檔案到本地記憶體。
result = bucket.get_object(key)
# 驗證擷取到的檔案內容跟上傳時的檔案內容是否一致。
content_got = b''
for chunk in result:
content_got += chunk
assert content_got == content
# 下載OSS檔案到本地檔案。
result = bucket.get_object_to_file(key, filename)
# 驗證擷取到的檔案內容跟上傳時的檔案內容是否一致。
with open(filename, 'rb') as fileobj:
assert fileobj.read() == content
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))
}
#include <alibabacloud/oss/OssEncryptionClient.h>
using namespace AlibabaCloud::OSS;
int main(void)
{
/* 初始化OSS帳號資訊。*/
/* yourEndpoint填寫Bucket所在地區對應的Endpoint。以華東1(杭州)為例,Endpoint填寫為https://oss-cn-hangzhou.aliyuncs.com。*/
std::string Endpoint = "yourEndpoint";
/* yourRegion填寫Bucket所在地區對應的Region。以華東1(杭州)為例,Region填寫為cn-hangzhou。*/
std::string Region = "yourRegion";
/* 填寫Bucket名稱,例如examplebucket。*/
std::string BucketName = "examplebucket";
/* 填寫Object完整路徑,完整路徑中不能包含Bucket名稱,例如exampledir/exampleobject.txt。*/
std::string ObjectName = "exampledir/exampleobject.txt";
/* 主要金鑰及描述資訊。*/
std::string RSAPublicKey = "your rsa public key";
std::string RSAPrivateKey = "your rsa private key";
std::map<std::string, std::string> desc;
desc["comment"] = "your comment";
/* 初始化網路等資源。*/
InitializeSdk();
ClientConfiguration conf;
conf.signatureVersion = SignatureVersionType::V4;
/* 從環境變數中擷取訪問憑證。運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。*/
auto credentialsProvider = std::make_shared<EnvironmentVariableCredentialsProvider>();
OssClient client(Endpoint, credentialsProvider, conf);
client.SetRegion(Region);
CryptoConfiguration cryptoConf;
auto materials = std::make_shared<SimpleRSAEncryptionMaterials>(RSAPublicKey, RSAPrivateKey, desc);
OssEncryptionClient client(Endpoint, credentialsProvider, conf, materials, cryptoConf);
/* 上傳檔案。*/
auto outcome = client.PutObject(BucketName, ObjectName, "yourLocalFilename");
if (!outcome.isSuccess()) {
/* 異常處理。*/
std::cout << "PutObject fail" <<
",code:" << outcome.error().Code() <<
",message:" << outcome.error().Message() <<
",requestId:" << outcome.error().RequestId() << std::endl;
return -1;
}
/* 釋放網路等資源。*/
ShutdownSdk();
return 0;
}