全部產品
Search
文件中心

Object Storage Service:用戶端加密

更新時間:Jun 19, 2025

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

    1. 擷取加密金鑰。

      通過CMK ID,用戶端向KMS請求資料密鑰,KMS返回資料清除金鑰和資料密文密鑰。

    2. 加密資料並上傳至OSS。

      用戶端使用資料清除金鑰加密Object,並將加密後的Object和資料密文密鑰上傳至OSS。

  • 下載並解密Object

    1. 下載Object。

      用戶端從OSS下載加密的Object和資料密文密鑰。

    2. 解密Object。

      用戶端將資料密文密鑰和CMK ID發送至KMS伺服器,KMS解密並返回資料清除金鑰。

說明
  • 用戶端會為每一個上傳的Object擷取一個唯一的資料加密金鑰。

  • 為了保證資料的安全性,建議定期輪換或者更新CMK。

  • 您需要維護CMK ID與Object之間的映射關係。

使用使用者自主管理密鑰

使用使用者自主管理密鑰時,您需要產生並保管加密金鑰。當用戶端加密Object時,您需上傳加密金鑰(對稱或非對稱)至用戶端。具體加密過程如下圖所示。

  • 加密並上傳Object

    1. 使用者向用戶端提供主要金鑰(對稱或非對稱)。

    2. 用戶端產生一次性對稱金鑰(資料密鑰),用於加密單個Object(每個Object產生一個資料密鑰)。

    3. 用戶端使用資料祕密金鑰加密Object,並用主要金鑰加密資料密鑰。

    4. 用戶端將加密的Object和加密的資料密鑰作為Object中繼資料上傳至OSS。

  • 下載並解密Object

    1. 用戶端從OSS下載加密的Object及其中繼資料。

    2. 用戶端使用中繼資料中的資訊,授權確定主要金鑰來解密資料密鑰,然後用解密後的資料密鑰解密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>);

        // 建立加密用戶端。
        // 當加密用戶端不再使用時,調用shutdown方法以釋放資源。
        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();
            }
        }
    }
}
import argparse
import base64
import json
from aliyunsdkkms.request.v20160120.DecryptRequest import DecryptRequest
from aliyunsdkkms.request.v20160120.EncryptRequest import EncryptRequest
from alibabacloud_dkms_transfer.kms_transfer_acs_client import KmsTransferAcsClient
from typing import Optional, Dict
import alibabacloud_oss_v2 as oss
import alibabacloud_oss_v2.crypto
from alibabacloud_oss_v2.encryption_client import EncryptionClient, EncryptionMultiPartContext

# 建立命令列參數解析器,用於接收使用者輸入的參數
parser = argparse.ArgumentParser(description="encryption kms sample")

# 添加命令列參數 --region,表示儲存空間所在的地區,必填項
parser.add_argument('--region', help='The region in which the bucket is located.', required=True)

# 添加命令列參數 --bucket,表示儲存空間的名稱,必填項
parser.add_argument('--bucket', help='The name of the bucket.', required=True)

# 添加命令列參數 --endpoint,表示其他服務訪問 OSS 時使用的網域名稱,可選項
parser.add_argument('--endpoint', help='The domain names that other services can use to access OSS')

# 添加命令列參數 --key,表示對象的名稱(檔案路徑),必填項
parser.add_argument('--key', help='The name of the object.', required=True)

# 添加命令列參數 --kms_id,表示使用者的 CMK(Customer Master Key)ID,必填項
parser.add_argument('--kms_id', help='The id of the your CMK ID.', required=True)


# 自訂主要金鑰加密器類,繼承自 oss.crypto.MasterCipher
class MasterKmsCipher(oss.crypto.MasterCipher):

    def __init__(
        self,
        mat_desc: Optional[Dict] = None,
        kms_client: Optional[KmsTransferAcsClient] = None,
        kms_id: Optional[str] = None,
    ):
        self.kms_client = kms_client
        self.kms_id = kms_id
        self._mat_desc = None

        # 如果提供了主要金鑰的描述資訊,則將其序列化為 JSON 字串
        if mat_desc is not None and len(mat_desc.items()) > 0:
            self._mat_desc = json.dumps(mat_desc)

    def get_wrap_algorithm(self) -> str:
        # 返回密碼編譯演算法名稱,固定為 'KMS/ALICLOUD'
        return 'KMS/ALICLOUD'

    def get_mat_desc(self) -> str:
        return self._mat_desc or ''

    def encrypt(self, data: bytes) -> bytes:
        """
        使用 KMS 服務加密資料
        :param data: 待加密的未經處理資料(位元組格式)
        :return: 加密後的資料(位元組格式)
        """
        # 將未經處理資料編碼為 Base64 格式
        base64_crypto = base64.b64encode(data)

        # 構造加密請求對象
        request = EncryptRequest()
        request.set_KeyId(self.kms_id)  # 設定 CMK ID
        request.set_Plaintext(base64_crypto)  # 設定待加密的 Base64 資料

        # 調用 KMS 用戶端執行加密操作,並擷取響應
        response = self.kms_client.do_action_with_exception(request)

        # 解析響應中的加密資料欄位,並解碼為位元組格式
        return base64.b64decode(json.loads(response).get('CiphertextBlob'))

    def decrypt(self, data: bytes) -> bytes:
        """
        使用 KMS 服務解密資料
        :param data: 已加密的資料(位元組格式)
        :return: 解密後的未經處理資料(位元組格式)
        """
        # 將加密資料編碼為 Base64 格式
        base64_crypto = base64.b64encode(data)

        # 構造解密請求對象
        request = DecryptRequest()
        request.set_CiphertextBlob(base64_crypto)  # 設定加密資料

        # 調用 KMS 用戶端執行解密操作,並擷取響應
        response = self.kms_client.do_action_with_exception(request)

        # 解析響應中的明文欄位,並解碼為位元組格式
        return base64.b64decode(json.loads(response).get('Plaintext'))


def main():
    # 解析命令列參數
    args = parser.parse_args()

    # 從環境變數中載入憑證資訊(AccessKeyId 和 AccessKeySecret)
    credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()

    # 載入 SDK 的預設配置
    cfg = oss.config.load_default()

    # 設定憑證提供者
    cfg.credentials_provider = credentials_provider

    # 設定儲存空間所在的地區
    cfg.region = args.region

    # 如果使用者提供了自訂的 endpoint,則設定到配置中
    if args.endpoint is not None:
        cfg.endpoint = args.endpoint

    # 使用設定物件初始化 OSS 用戶端
    client = oss.Client(cfg)

    # 初始化 KMS 用戶端,用於與 KMS 服務互動
    kms_client = KmsTransferAcsClient(
        ak=credentials_provider._credentials.access_key_id,  # 從憑證提供者中擷取 AccessKeyId
        secret=credentials_provider._credentials.access_key_secret,  # 從憑證提供者中擷取 AccessKeySecret
        region_id=args.region  # 指定地區資訊
    )

    # 初始化主要金鑰加密器(MasterKmsCipher),用於加密和解密操作
    mc = MasterKmsCipher(
        mat_desc={"desc": "your master encrypt key material describe information"},  # 主要金鑰描述資訊
        kms_client=kms_client,  # KMS 用戶端執行個體
        kms_id=args.kms_id  # 使用者的 CMK ID
    )

    # 建立加密用戶端
    encryption_client = oss.EncryptionClient(client, mc)

    # 定義要上傳的資料
    data = b'hello world'

    # 調用加密用戶端的 put_object 方法上傳加密對象
    result = encryption_client.put_object(
        oss.PutObjectRequest(
            bucket=args.bucket,  # 指定目標儲存空間的名稱
            key=args.key,        # 指定對象的名稱(檔案路徑)
            body=data,           # 指定要上傳的資料
        )
    )

    # 列印上傳加密對象的結果
    print(vars(result))

    # 調用加密用戶端的 get_object 方法擷取加密對象的內容
    result = encryption_client.get_object(
        oss.GetObjectRequest(
            bucket=args.bucket,  # 指定目標儲存空間的名稱
            key=args.key,        # 指定對象的名稱(檔案路徑)
        )
    )

    # 列印擷取加密對象的結果
    print(vars(result))

    # 列印解密後的對象內容
    print(result.body.read())


if __name__ == "__main__":
    # 程式入口,調用 main 函數執行邏輯
    main()
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;
}
<?php
// 引入自動負載檔案 載入依賴庫
require_once 'vendor/autoload.php';

use AlibabaCloud\Dkms\Gcs\Sdk\Client as KmsClient;
use AlibabaCloud\Oss\V2 as Oss;

// 定義命令列參數描述
$optsdesc = [
    "region" => ['help' => 'The region in which the bucket is located.', 'required' => True], // 地區是必填項 儲存空間所在的地區
    "endpoint" => ['help' => 'The domain names that other services can use to access OSS.', 'required' => False], // 終端節點是可選項 其他服務可以用來訪問OSS的網域名稱
    "bucket" => ['help' => 'The name of the bucket', 'required' => True], // 儲存空間名稱是必填項
    "key" => ['help' => 'The name of the object', 'required' => True], // 對象名稱是必填項
];

// 產生長選項列表 用於解析命令列參數
$longopts = \array_map(function ($key) {
    return "$key:"; // 每個參數後面加冒號 表示需要值
}, array_keys($optsdesc));

// 解析命令列參數
$options = getopt("", $longopts);

// 檢查必填參數是否缺失
foreach ($optsdesc as $key => $value) {
    if ($value['required'] === True && empty($options[$key])) {
        $help = $value['help'];
        echo "Error: the following arguments are required: --$key, $help"; // 提示使用者缺少必填參數
        exit(1);
    }
}

// 擷取命令列參數值
$region = $options["region"]; // 儲存空間所在地區
$bucket = $options["bucket"]; // 儲存空間名稱
$key = $options["key"]; // 對象名稱

// 使用環境變數載入憑證資訊 AccessKeyId 和 AccessKeySecret
$credentialsProvider = new Oss\Credentials\EnvironmentVariableCredentialsProvider();

// 自訂KMS加密解密類 實現MasterCipherInterface介面
class KmsCipher implements Oss\Crypto\MasterCipherInterface
{
    private $matDesc;
    private ?KmsClient $kmsClient;
    private ?string $keyId;
    private ?string $algorithm;

    public function __construct(
        $matDesc = null,
        ?string $keyId = null,
        ?KmsClient $kmsClient = null,
        ?string $algorithm = null
    )
    {
        $this->keyId = $keyId;
        $this->matDesc = null;
        if (\is_array($matDesc)) {
            $val = json_encode($matDesc);
            if ($val !== false) {
                $this->matDesc = $val;
            }
        } else if (is_string($matDesc)) {
            $this->matDesc = $matDesc;
        }

        $this->kmsClient = $kmsClient;
        $this->algorithm = $algorithm;
    }

    // 加密方法
    public function encrypt(string $data): string
    {
        $encryptRequest = new \AlibabaCloud\Dkms\Gcs\Sdk\Models\AdvanceEncryptRequest();
        $encryptRequest->algorithm = $this->algorithm;
        $encryptRequest->keyId = $this->keyId;
        $encryptRequest->plaintext = \AlibabaCloud\Tea\Utils\Utils::toBytes($data);
        $runtimeOptions = new \AlibabaCloud\Dkms\Gcs\OpenApi\Util\Models\RuntimeOptions();
        $encryptResponse = $this->kmsClient->advanceEncryptWithOptions($encryptRequest, $runtimeOptions);
        return base64_decode((string)$encryptResponse->ciphertextBlob);
    }

    // 解密方法
    public function decrypt(string $data): string
    {
        $decryptRequest = new \AlibabaCloud\Dkms\Gcs\Sdk\Models\AdvanceDecryptRequest();
        $decryptRequest->keyId = $this->keyId;
        $decryptRequest->ciphertextBlob = $data;
        $decryptRequest->algorithm = $this->algorithm;
        $runtimeOptions = new \AlibabaCloud\Dkms\Gcs\OpenApi\Util\Models\RuntimeOptions();
        $decryptResponse = $this->kmsClient->advanceDecryptWithOptions($decryptRequest, $runtimeOptions);
        return base64_decode((string)$decryptResponse->plaintext);
    }

    // 擷取封裝演算法
    public function getWrapAlgorithm(): string
    {
        return "KMS/ALICLOUD";
    }

    public function getMatDesc(): string
    {
        return $this->matDesc;
    }
}

/**
 * 構建專屬KMS SDK Client對象
 * @return KmsClient
 */
function getDkmsGcsSdkClient()
{
    global $clientKeyFile, $password, $endpoint;
    // 構建專屬KMS SDK Client配置
    $config = new \AlibabaCloud\Dkms\Gcs\OpenApi\Models\Config();
    $config->protocol = 'https';
    $config->clientKeyFile = $clientKeyFile;
    $config->password = $password;
    $config->endpoint = $endpoint;
    // 驗證服務端認證
    $config->caFilePath = 'path/to/caCert.pem';
    // 構建專屬KMS SDK Client對象
    return new \AlibabaCloud\Dkms\Gcs\Sdk\Client($config);
}

// 填寫您在KMS應用管理擷取的ClientKey檔案路徑
$clientKeyFile = '<your client key file path>';
// 填寫您在KMS應用管理建立ClientKey時輸入的加密口令
$password = '<your dkms client passowrd>';
// 填寫您的專屬KMS執行個體服務地址
$endpoint = '<your dkms instance service address>';
// 填寫您在KMS建立的主要金鑰Id
$kmsKeyId = '<your cmk id>';
// 加解密演算法
$algorithm = '<your encrypt algorithm>';

// 專屬KMS SDK Client對象
$kmsClient = getDkmsGcsSdkClient();

$cfg = Oss\Config::loadDefault();
$cfg->setCredentialsProvider($credentialsProvider);
$cfg->setRegion($region);
if (isset($options["endpoint"])) {
    $cfg->setEndpoint($options["endpoint"]);
}

$client = new Oss\Client($cfg);
$materialDesc = ['desc' => 'your kms encrypt key material describe information'];
$masterKmsCipher = new KmsCipher($materialDesc, $kmsKeyId, $kmsClient, $algorithm);
$eClient = new \AlibabaCloud\Oss\V2\EncryptionClient($client, $masterKmsCipher);

$eClient->putObject(new Oss\Models\PutObjectRequest(
    bucket: $bucket,
    key: $key,
    body: Oss\Utils::streamFor('hi kms')
));

$result = $eClient->getObject(new Oss\Models\GetObjectRequest(
    bucket: $bucket,
    key: $key,
));

$data = $result->body->getContents();
echo "get object data: " . $data;