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發送一個請求,申請1個用於加密Object的資料密鑰(Data Key)。作為響應,KMS會返回一個隨機產生的資料清除金鑰(Data Key)以及一個資料密文密鑰(Encrypted Data Key)給用戶端。
加密資料並上傳至OSS。
用戶端接收到KMS返回的資料清除金鑰以及資料密文密鑰後,將使用資料清除金鑰對Object進行本地加密,並且將加密後的Object以及資料密文密鑰上傳至OSS。
下載並解密Object
下載Object。
用戶端從OSS服務端下載加密的Object以及作為Object中繼資料存放區的資料密文密鑰。
解密Object。
用戶端將資料密文密鑰以及CMK ID發送至KMS伺服器。作為響應,KMS將使用指定的CMK解密,並且將資料清除金鑰返回給用戶端。
用戶端會為每一個上傳的Object擷取一個唯一的資料加密金鑰。
為了保證資料的安全性,建議定期輪換或者更新CMK。
您需要維護CMK ID與Object之間的映射關係。
使用使用者自主管理密鑰
使用使用者自主管理密鑰時,需要您自主產生並保管加密金鑰。當用戶端加密Object時,由使用者自主上傳加密金鑰(對稱式加密密鑰或者非對稱式加密密鑰)至用戶端。具體加密過程如下圖所示。
加密並上傳Object
使用者向用戶端提供1個使用者主要金鑰(對稱金鑰或者非對稱金鑰)。
用戶端在本地產生一個一次性的對稱金鑰,即資料密鑰(Data Key)。資料密鑰將用於加密單個Object(針對每個Object,用戶端都會隨機產生1個資料密鑰)。
用戶端使用資料祕密金鑰加密Object,並使用使用者提供的主要金鑰來加密資料密鑰。
用戶端將加密後的Object以及加密的資料密鑰(Encrypted Data Key)作為Object中繼資料的一部分上傳至OSS。
下載並解密Object
用戶端從OSS服務端下載加密的Object以及Object中繼資料。
通過使用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.cryptoimportAliKMSProvider
# 從環境變數中擷取訪問憑證。運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
kms_provider=AliKMSProvider(auth, 'yourRegion', 'yourCMKID')
bucket = oss2.CryptoBucket(auth, 'yourEndpoint', 'yourBucketName', crypto_provider = kms_provider)
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"
"fmt"
"io/ioutil"
"os"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"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 {
fmt.Println("Error:", err)
os.Exit(-1)
}
// 建立OSSClient執行個體。
// yourEndpoint填寫Bucket對應的Endpoint,以華東1(杭州)為例,填寫為https://oss-cn-hangzhou.aliyuncs.com。其它Region請按實際情況填寫。
client, err := oss.New("yourEndpoint", "", "", oss.SetCredentialsProvider(&provider))
if err != nil {
fmt.Println("Error:", err)
os.Exit(-1)
}
// 建立一個主要金鑰的描述資訊,建立後不允許修改。主要金鑰描述資訊和主要金鑰一一對應。
// 如果所有的Object都使用相同的主要金鑰,主要金鑰描述資訊可以為空白,但後續不支援更換主要金鑰。
// 如果主要金鑰描述資訊為空白,解密時無法判斷使用的是哪個主要金鑰。
// 強烈建議為每個主要金鑰都配置主要金鑰描述資訊(json字串),由用戶端儲存主要金鑰和描述資訊之間的對應關係(服務端不儲存兩者之間的對應關係)。
// 由主要金鑰描述資訊(json字串)轉換的map。
materialDesc := make(map[string]string)
materialDesc["desc"] = "your master encrypt key material describe information"
// 根據主要金鑰描述資訊建立一個主要金鑰對象。
// yourRsaPublicKey填寫您自主管理的主要金鑰公開金鑰資訊,yourRsaPrivateKey填寫您自主管理的主要金鑰私密金鑰資訊。
masterRsaCipher, err := osscrypto.CreateMasterRsa(materialDesc, "yourRsaPublicKey", "yourRsaPrivateKey")
if err != nil {
fmt.Println("Error:", err)
os.Exit(-1)
}
// 根據主要金鑰對象建立一個用於加密的介面, 使用aes ctr模式加密。
contentProvider := osscrypto.CreateAesCtrCipher(masterRsaCipher)
// 擷取一個用於用戶端加密的已建立Bucket。
// 用戶端加密Bucket和普通Bucket具有相似的用法。
cryptoBucket, err := osscrypto.GetCryptoBucket(client, "yourBucketName", contentProvider)
if err != nil {
fmt.Println("Error:", err)
os.Exit(-1)
}
// PutObject時自動加密。
err = cryptoBucket.PutObject("yourObjectName", bytes.NewReader([]byte("yourObjectValueByteArrary")))
if err != nil {
fmt.Println("Error:", err)
os.Exit(-1)
}
// GetObject時自動解密。
body, err := cryptoBucket.GetObject("yourObjectName")
if err != nil {
fmt.Println("Error:", err)
os.Exit(-1)
}
defer body.Close()
data, err := ioutil.ReadAll(body)
if err != nil {
fmt.Println("Error:", err)
os.Exit(-1)
}
fmt.Println("data:", 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";
/* 填寫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;
/* 從環境變數中擷取訪問憑證。運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。*/
auto credentialsProvider = std::make_shared<EnvironmentVariableCredentialsProvider>();
OssClient client(Endpoint, credentialsProvider, conf);
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;
}