全部產品
Search
文件中心

Object Storage Service:用戶端加密(PHP SDK V2)

更新時間:Aug 05, 2025

OSS用戶端加密是在資料上傳至OSS之前,由使用者在本地對資料進行加密處理,確保只有密鑰持有人才能解密資料,增強資料在傳輸和預存程序中的安全性。

注意事項

  • 本文範例程式碼以華東1(杭州)的地區IDcn-hangzhou為例,預設使用外網Endpoint,如果您希望通過與OSS同地區的其他阿里雲產品訪問OSS,請使用內網Endpoint。關於OSS支援的Region與Endpoint的對應關係,請參見OSS地區和訪問網域名稱

  • 使用用戶端加密功能時,您需要對主要金鑰的完整性和正確性負責。

  • 在對加密資料進行複製或者遷移時,您需要對加密中繼資料的完整性和正確性負責。

使用RSA主要金鑰

使用主要金鑰RSA簡單上傳和下載Object

使用主要金鑰RSA簡單上傳和下載Object範例程式碼如下:

<?php

// 引入自動負載檔案 載入依賴庫
require_once __DIR__ . '/../vendor/autoload.php';

use AlibabaCloud\Oss\V2 as Oss;

// 定義RSA公開金鑰 用於加密操作
const RSA_PUBLIC_KEY = <<<BBB
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCokfiAVXXf5ImFzKDw+XO/UByW
6mse2QsIgz3ZwBtMNu59fR5zttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC
5MFO1PByrE/MNd5AAfSVba93I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MmR
1EKib1Id8hpooY5xaQIDAQAB
-----END PUBLIC KEY-----
BBB;

// 定義RSA私密金鑰 用於解密操作
const RSA_PRIVATE_KEY = <<<BBB
-----BEGIN PRIVATE KEY-----
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKiR+IBVdd/kiYXM
oPD5c79QHJbqax7ZCwiDPdnAG0w27n19HnO21LH7x8Hu9HgI3dtPO2s/0DpuOg3Q
UWeGVDe80kLkwU7U8HKsT8w13kAB9JVtr3cjqzHw1KTkzNQIDg0nMBSpg4RYa0YF
yibqQQXoyZHUQqJvUh3yGmihjnFpAgMBAAECgYA49RmCQ14QyKevDfVTdvYlLmx6
kbqgMbYIqk+7w611kxoCTMR9VMmJWgmk/Zic9mIAOEVbd7RkCdqT0E+xKzJJFpI2
ZHjrlwb21uqlcUqH1Gn+wI+jgmrafrnKih0kGucavr/GFi81rXixDrGON9KBE0FJ
cPVdc0XiQAvCBnIIAQJBANXu3htPH0VsSznfqcDE+w8zpoAJdo6S/p30tcjsDQnx
l/jYV4FXpErSrtAbmI013VYkdJcghNSLNUXppfk2e8UCQQDJt5c07BS9i2SDEXiz
byzqCfXVzkdnDj9ry9mba1dcr9B9NCslVelXDGZKvQUBqNYCVxg398aRfWlYDTjU
IoVVAkAbTyjPN6R4SkC4HJMg5oReBmvkwFCAFsemBk0GXwuzD0IlJAjXnAZ+/rIO
ItewfwXIL1Mqz53lO/gK+q6TR585AkB304KUIoWzjyF3JqLP3IQOxzns92u9EV6l
V2P+CkbMPXiZV6sls6I4XppJXX2i3bu7iidN3/dqJ9izQK94fMU9AkBZvgsIPCot
y1/POIbv9LtnviDKrmpkXgVQSU4BmTPvXwTJm8APC7P/horSh3SVf1zgmnsyjm9D
hO92gGc+4ajL
-----END PRIVATE KEY-----
BBB;

// 定義命令列參數描述
$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();

// 使用SDK的預設配置
$cfg = Oss\Config::loadDefault();

// 設定憑證提供者
$cfg->setCredentialsProvider($credentialsProvider);

// 設定地區
$cfg->setRegion($region);

// 如果提供了終端節點 則設定終端節點
if (isset($options["endpoint"])) {
    $cfg->setEndpoint($options["endpoint"]);
}

// 建立OSS用戶端執行個體
$client = new Oss\Client($cfg);

// 建立主要金鑰加密器 使用RSA公開金鑰和私密金鑰進行加密解密
$masterCipher = new Oss\Crypto\MasterRsaCipher(
    publicKey: RSA_PUBLIC_KEY, // RSA公開金鑰
    privateKey: RSA_PRIVATE_KEY, // RSA私密金鑰
    matDesc: ['tag' => 'value'] // 加密附加標籤
);

// 建立加密用戶端執行個體
$eclient = new Oss\EncryptionClient(client: $client, masterCipher: $masterCipher);

// 建立簡單上傳對象的請求對象
$putObjRequest = new Oss\Models\PutObjectRequest(bucket: $bucket, key: $key);

// 調用putObject方法上傳對象
$putObjResult = $eclient->putObject(request: $putObjRequest);

// 列印返回結果
printf(
    'put object status code:' . $putObjResult->statusCode . PHP_EOL . // HTTP響應狀態代碼
    'request id:' . $putObjResult->requestId . PHP_EOL // 請求的唯一標識
);

使用主要金鑰RSA分區上傳Object

使用主要金鑰RSA分區上傳Object範例程式碼如下:

<?php

// 引入自動負載檔案 載入依賴庫
require_once __DIR__ . '/../vendor/autoload.php';

use AlibabaCloud\Oss\V2 as Oss;

// 定義RSA公開金鑰 用於加密操作
const RSA_PUBLIC_KEY = <<<BBB
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCokfiAVXXf5ImFzKDw+XO/UByW
6mse2QsIgz3ZwBtMNu59fR5zttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC
5MFO1PByrE/MNd5AAfSVba93I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MmR
1EKib1Id8hpooY5xaQIDAQAB
-----END PUBLIC KEY-----
BBB;

// 定義RSA私密金鑰 用於解密操作
const RSA_PRIVATE_KEY = <<<BBB
-----BEGIN PRIVATE KEY-----
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKiR+IBVdd/kiYXM
oPD5c79QHJbqax7ZCwiDPdnAG0w27n19HnO21LH7x8Hu9HgI3dtPO2s/0DpuOg3Q
UWeGVDe80kLkwU7U8HKsT8w13kAB9JVtr3cjqzHw1KTkzNQIDg0nMBSpg4RYa0YF
yibqQQXoyZHUQqJvUh3yGmihjnFpAgMBAAECgYA49RmCQ14QyKevDfVTdvYlLmx6
kbqgMbYIqk+7w611kxoCTMR9VMmJWgmk/Zic9mIAOEVbd7RkCdqT0E+xKzJJFpI2
ZHjrlwb21uqlcUqH1Gn+wI+jgmrafrnKih0kGucavr/GFi81rXixDrGON9KBE0FJ
cPVdc0XiQAvCBnIIAQJBANXu3htPH0VsSznfqcDE+w8zpoAJdo6S/p30tcjsDQnx
l/jYV4FXpErSrtAbmI013VYkdJcghNSLNUXppfk2e8UCQQDJt5c07BS9i2SDEXiz
byzqCfXVzkdnDj9ry9mba1dcr9B9NCslVelXDGZKvQUBqNYCVxg398aRfWlYDTjU
IoVVAkAbTyjPN6R4SkC4HJMg5oReBmvkwFCAFsemBk0GXwuzD0IlJAjXnAZ+/rIO
ItewfwXIL1Mqz53lO/gK+q6TR585AkB304KUIoWzjyF3JqLP3IQOxzns92u9EV6l
V2P+CkbMPXiZV6sls6I4XppJXX2i3bu7iidN3/dqJ9izQK94fMU9AkBZvgsIPCot
y1/POIbv9LtnviDKrmpkXgVQSU4BmTPvXwTJm8APC7P/horSh3SVf1zgmnsyjm9D
hO92gGc+4ajL
-----END PRIVATE KEY-----
BBB;

// 定義命令列參數描述
$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();

// 使用SDK的預設配置
$cfg = Oss\Config::loadDefault();

// 設定憑證提供者
$cfg->setCredentialsProvider($credentialsProvider);

// 設定地區
$cfg->setRegion($region);

// 如果提供了終端節點 則設定終端節點
if (isset($options["endpoint"])) {
    $cfg->setEndpoint($options["endpoint"]);
}

// 建立OSS用戶端執行個體
$client = new Oss\Client($cfg);

// 建立主要金鑰加密器 使用RSA公開金鑰和私密金鑰進行加密解密
$masterCipher = new Oss\Crypto\MasterRsaCipher(
    RSA_PUBLIC_KEY, // RSA公開金鑰
    RSA_PRIVATE_KEY, // RSA私密金鑰
    ['tag' => 'value'] // 加密附加標籤
);

// 建立加密用戶端執行個體
$eclient = new Oss\EncryptionClient($client, $masterCipher);

// 初始化分區上傳請求 設定分區大小和資料總大小
$initRequest = new Oss\Models\InitiateMultipartUploadRequest(bucket: $bucket, key: $key);
$initRequest->cseDataSize = 500 * 1024; // 資料總大小為500KB
$initRequest->csePartSize = 200 * 1024; // 每個分區大小為200KB

// 調用initiateMultipartUpload方法初始化分區上傳
$initResult = $eclient->initiateMultipartUpload(request: $initRequest);

// 建立上傳分區請求對象
$uploadPartRequest = new Oss\Models\UploadPartRequest(bucket: $bucket, key: $key);

// 定義臨時檔案名稱和分區大小
$bigFileName = "upload.tmp"; // 臨時檔案名稱
$partSize = 200 * 1024; // 每個分區大小為200KB

// 產生一個500KB的臨時檔案用於測試
generateFile($bigFileName, 500 * 1024);

// 開啟臨時檔案準備讀取
$file = fopen(filename: $bigFileName, mode: 'r');

// 用於儲存分區資訊的數組
$parts = array();

// 如果檔案成功開啟 則逐片讀取並上傳
if ($file) {
    $i = 1; // 分區編號從1開始
    while (!feof(stream: $file)) {
        // 讀取一個分區的資料
        $chunk = fread(stream: $file, length: $partSize);

        // 建立上傳分區請求對象 並設定分區編號和上傳ID
        $uploadPartRequest = new Oss\Models\UploadPartRequest(
            bucket: $bucket,
            key: $key,
            partNumber: $i,
            uploadId: $initResult->uploadId,
            contentLength: null,
            contentMd5: null,
            trafficLimit: null,
            requestPayer: null,
            body: Oss\Utils::streamFor(resource: $chunk) // 將分區資料轉換為流
        );

        // 設定加密上下文
        $uploadPartRequest->encryptionMultipartContext = $initResult->encryptionMultipartContext;

        // 調用uploadPart方法上傳分區
        $partResult = $eclient->uploadPart($uploadPartRequest);

        // 建立分區資訊對象 並添加到分區數組中
        $part = new Oss\Models\UploadPart(
            partNumber: $i,
            etag: $partResult->etag, // 擷取分區的ETag
        );
        array_push(array: $parts, values: $part);

        $i++; // 分區編號遞增
    }
    fclose(stream: $file); // 關閉檔案
}

// 調用completeMultipartUpload方法完成分區上傳
$comResult = $eclient->completeMultipartUpload(
    request: new Oss\Models\CompleteMultipartUploadRequest(
        bucket: $bucket,
        key: $key,
        uploadId: $initResult->uploadId,
        acl: null,
        completeMultipartUpload: new Oss\Models\CompleteMultipartUpload(
            parts: $parts
        ),
    )
);

// 刪除臨時檔案
unlink($bigFileName);

// 列印返回結果
printf(
    'complete multipart upload status code:' . $comResult->statusCode . PHP_EOL . // HTTP響應狀態代碼
    'complete multipart upload request id:' . $comResult->requestId . PHP_EOL . // 請求的唯一標識
    'complete multipart upload result:' . var_export($comResult, true) // 完成分區上傳的結果
);

// 產生指定大小的檔案 用於測試分區上傳
function generateFile($filename, $size)
{
    // 如果檔案已存在且大小符合要求 則直接返回
    if (
        file_exists($filename) &&
        $size == sprintf('%u', filesize($filename))
    ) {
        return;
    }

    $part_size = 32; // 每次寫入的塊大小為32位元組
    $fp = fopen($filename, "w"); // 開啟檔案以寫入模式
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; // 字元集

    $charactersLength = strlen($characters); // 字元集長度

    // 如果檔案成功開啟 則迴圈寫入資料直到達到指定大小
    if ($fp) {
        while ($size > 0) {
            // 計算本次寫入的大小
            if ($size < $part_size) {
                $write_size = $size;
            } else {
                $write_size = $part_size;
            }

            $size -= $write_size; // 更新剩餘需要寫入的大小

            // 隨機播放一個字元並重複產生內容
            $a = $characters[rand(0, $charactersLength - 1)];
            $content = str_repeat($a, $write_size);

            // 將內容寫入檔案
            $flag = fwrite($fp, $content);
            if (!$flag) {
                break; // 如果寫入失敗 則退出迴圈
            }
        }
    }

    fclose($fp); // 關閉檔案
}

使用自訂主要金鑰

使用自訂主要金鑰簡單上傳和下載Object

SDK提供了RSA預設實現, 當這個方式不滿足使用者的需求時,使用者可以自己實現主要金鑰的加解密行為。以下範例程式碼以阿里雲KMS 3.0為例,示範如何自訂主要金鑰加解密進行簡單上傳和下載Object。

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

相關文檔

  • 關於OSS用戶端加密實現的原理,請參見用戶端加密

  • 關於使用主要金鑰RSA簡單上傳和下載Object的完整程式碼範例,請參見Github樣本