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