
# 服务端加密(C SDK)
![](https://support.huaweicloud.com/sdk-c-devg-obs/public_sys-resources/notice_3.0-zh-cn.png)
开发过程中，您有任何问题可以在GitHub上[提交issue](https://github.com/huaweicloud/huaweicloud-sdk-c-obs/issues)，或者在[华为云对象存储服务论坛](https://bbs.huaweicloud.com/forum/forum-620-1.html)中发帖求助。
#### 功能说明
用户可以使用普通方式上传、下载对象，也可以使用服务端加密方式进行上传、下载对象。
OBS支持服务端加密功能，使加密的行为在服务端进行。
用户可以根据自身的需求，使用不同的密钥管理方式来使用服务端加密功能。当前支持以下两种都采用行业标准的AES256加密算法的服务端加密方式。
- SSE-KMS方式，OBS使用KMS（Key Management Service）服务提供的密钥进行服务端加密。
- SSE-C方式：客户提供加密密钥的服务端加密方式，即OBS使用用户提供的密钥和密钥的MD5值进行服务端加密。
使用服务端加密，返回的ETag值不是对象的MD5值。无论是否使用服务端加密上传对象，请求消息头中加入Content-MD5参数时，OBS均会对对象进行MD5校验。
更多关于服务端加密的内容请参考[服务端加密](https://support.huaweicloud.com/usermanual-obs/obs_03_0088.html)。
#### 接口约束
- 您必须具有执行PutEncryptionConfiguration操作的权限，才能进行服务端加密操作。桶拥有者默认具有此权限，并且可以将此权限授予其他人。相关授权方式介绍可参见[OBS权限控制概述](https://support.huaweicloud.com/perms-cfg-obs/obs_40_0001.html)，配置方式详见[使用IAM自定义策略](https://support.huaweicloud.com/usermanual-obs/obs_03_0121.html)、[配置对象策略](https://support.huaweicloud.com/usermanual-obs/obs_03_0075.html)。
- OBS支持的Region与Endpoint的对应关系，详细信息请参见[地区与终端节点](https://console.huaweicloud.com/apiexplorer/#/endpoint/OBS)。
 
#### 支持接口
OBS C SDK支持服务端加密的接口见下表：
| **OBS C SDK接口方法**                                                                             | **描述**                                                                                                                                                                                                                              | **支持加密类型**                                   |
|:---|:---|:---|
| [put_object](https://support.huaweicloud.com/sdk-c-devg-obs/obs_20_0402.html)                 | 上传对象时设置加密算法、密钥，对对象启用服务端加密。                                                                                                                                                                                                          | SSE-KMS SSE-C |
| [get_object](https://support.huaweicloud.com/sdk-c-devg-obs/obs_20_0501.html)                 | 下载对象时设置解密算法、密钥，用于解密对象。                                                                                                                                                                                                              | SSE-C                                        |
| [copy_object](https://support.huaweicloud.com/sdk-c-devg-obs/obs_20_0605.html)                | 1. 复制对象时设置源对象的解密算法、密钥，用于解密源对象。  2. 复制对象时设置目标对象的加密算法、密钥，对目标对象启用加密算法。   | SSE-KMS SSE-C |
| [get_object_metadata](https://support.huaweicloud.com/sdk-c-devg-obs/obs_20_0601.html)        | 获取对象元数据时设置解密算法、密钥，用于解密对象。                                                                                                                                                                                                           | SSE-C                                        |
| [initiate_multi_part_upload](https://support.huaweicloud.com/sdk-c-devg-obs/obs_20_0405.html) | 初始化分段上传任务时设置加密算法、密钥，对分段上传任务最终生成的对象启用服务端加密。                                                                                                                                                                                          | SSE-KMS SSE-C    |
| [upload_part](https://support.huaweicloud.com/sdk-c-devg-obs/obs_20_1810.html)                | 上传段时设置加密算法、密钥，对分段数据启用服务端加密。                                                                                                                                                                                                         | SSE-C                                        |
| [copy_part](https://support.huaweicloud.com/sdk-c-devg-obs/obs_20_0406.html)                  | 1. 复制段时设置源对象的解密算法、密钥，用于解密源对象。  2. 复制段时设置目标段的加密算法、密钥，对目标段启用加密算法。                     | SSE-C                                        |
   
#### 代码示例一：上传对象加密
以下示例展示如何在上传对象时使用服务端加密功能：
```
#include "eSDKOBS.h"
#include <stdio.h>
#include <sys/stat.h>
// 响应回调函数，可以在这个回调中把properties的内容记录到callback_data(用户自定义回调数据)中
obs_status response_properties_callback(const obs_response_properties *properties, void *callback_data);
int put_file_data_callback(int buffer_size, char *buffer,
    void *callback_data);
void put_file_complete_callback(obs_status status,
    const obs_error_details *error,
    void *callback_data);
typedef struct put_file_object_callback_data
{
    FILE *infile;
    uint64_t content_length;
    obs_status ret_status;
} put_file_object_callback_data;
uint64_t open_file_and_get_length(char *localfile, put_file_object_callback_data *data);
int main()
{
    // 以下示例展示如何在上传对象时使用服务端加密功能：
    // 在程序入口调用obs_initialize方法来初始化网络、内存等全局资源。
    obs_status ret_status = obs_initialize(OBS_INIT_ALL);
    if (OBS_STATUS_OK != ret_status)
    {
        printf("obs_initialize failed(%s).\n", obs_get_status_name(ret_status));
        return -1;
    }
    obs_options options;
    // 创建并初始化options，该参数包括访问域名(host_name)、访问密钥（access_key_id和access_key_secret）、桶名(bucket_name)、桶存储类别(storage_class)等配置信息
    init_obs_options(&options);
    // host_name填写桶所在的endpoint, 此处以华北-北京四为例，其他地区请按实际情况填写。
    options.bucket_options.host_name = "obs.cn-north-4.myhuaweicloud.com";
    // 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险，建议在配置文件或者环境变量中密文存放，使用时解密，确保安全；
    // 本示例以ak和sk保存在环境变量中为例，运行本示例前请先在本地环境中设置环境变量ACCESS_KEY_ID和SECRET_ACCESS_KEY。
    options.bucket_options.access_key = getenv("ACCESS_KEY_ID");
    options.bucket_options.secret_access_key = getenv("SECRET_ACCESS_KEY");
    // 填写Bucket名称，例如example-bucket-name。
    char * bucket_name = "example-bucket-name";
    options.bucket_options.bucket_name = bucket_name;
    // 使用服务端加密功能时，必须使用https协议
    options.bucket_options.protocol = OBS_PROTOCOL_HTTPS;
    // 初始化上传对象属性
    obs_put_properties put_properties;
    init_put_properties(&put_properties);
    // 上传对象名
    char *key = "example_put_file_test";
    // 上传的文件
    char file_name[256] = "./example_local_file_test.txt";
    uint64_t content_length = 0;
    // 初始化存储上传数据的结构体
    put_file_object_callback_data data;
    memset(&data, 0, sizeof(put_file_object_callback_data));
    // 打开文件，并获取文件长度
    content_length = open_file_and_get_length(file_name, &data);
    // 设置回调函数
    obs_put_object_handler putobjectHandler =
    {
        { &response_properties_callback, &put_file_complete_callback },
        &put_file_data_callback
    };
    //服务端加密 SSE加密
    server_side_encryption_params encryption_params;
    memset(&encryption_params, 0, sizeof(server_side_encryption_params));
    encryption_params.ssec_customer_algorithm = "AES256";
    encryption_params.ssec_customer_key =
        "K7QkYpBkM5+hcs27fsNkUnNVaobncnLht/rCB2o/9Cw=";
    put_object(&options, key, content_length, &put_properties, &encryption_params, &putobjectHandler, &data);
    if (OBS_STATUS_OK == data.ret_status) {
        printf("put object from file successfully. \n");
    }
    else
    {
        printf("put object failed(%s).\n",
            obs_get_status_name(data.ret_status));
    }
    if (data.infile != NULL) {
        fclose(data.infile);
    }
    // 释放分配的全局资源
    obs_deinitialize();
}
// 响应回调函数，可以在这个回调中把properties的内容记录到callback_data(用户自定义回调数据)中
obs_status response_properties_callback(const obs_response_properties *properties, void *callback_data)
{
    if (properties == NULL)
    {
        printf("error! obs_response_properties is null!");
        return OBS_STATUS_OK;      
    }
    // 打印响应信息
#define print_nonnull(name, field)                                 \
    do {                                                           \
        if (properties-> field) {                                  \
            printf("%s: %s\n", name, properties->field);          \
        }                                                          \
    } while (0)
    print_nonnull("request_id", request_id);
    print_nonnull("request_id2", request_id2);
    print_nonnull("content_type", content_type);
    if (properties->content_length) {
        printf("content_length: %llu\n", properties->content_length);
    }
    print_nonnull("server", server);
    print_nonnull("ETag", etag);
    print_nonnull("expiration", expiration);
    print_nonnull("website_redirect_location", website_redirect_location);
    print_nonnull("version_id", version_id);
    print_nonnull("allow_origin", allow_origin);
    print_nonnull("allow_headers", allow_headers);
    print_nonnull("max_age", max_age);
    print_nonnull("allow_methods", allow_methods);
    print_nonnull("expose_headers", expose_headers);
    print_nonnull("storage_class", storage_class);
    print_nonnull("server_side_encryption", server_side_encryption);
    print_nonnull("kms_key_id", kms_key_id);
    print_nonnull("customer_algorithm", customer_algorithm);
    print_nonnull("customer_key_md5", customer_key_md5);
    print_nonnull("bucket_location", bucket_location);
    print_nonnull("obs_version", obs_version);
    print_nonnull("restore", restore);
    print_nonnull("obs_object_type", obs_object_type);
    print_nonnull("obs_next_append_position", obs_next_append_position);
    print_nonnull("obs_head_epid", obs_head_epid);
    print_nonnull("reserved_indicator", reserved_indicator);
    int i;
    for (i = 0; i < properties->meta_data_count; i++) {
        printf("x-obs-meta-%s: %s\n", properties->meta_data[i].name,
            properties->meta_data[i].value);
    }
    return OBS_STATUS_OK;
}
int put_file_data_callback(int buffer_size, char *buffer,
    void *callback_data)
{
    put_file_object_callback_data *data =
        (put_file_object_callback_data *)callback_data;
    int ret = 0;
    if (data->content_length) {
        int toRead = ((data->content_length > (unsigned)buffer_size) ?
            (unsigned)buffer_size : data->content_length);
        ret = fread(buffer, 1, toRead, data->infile);
    }
    uint64_t originalContentLength = data->content_length;
    data->content_length -= ret;
    if (data->content_length) {
        printf("%llu bytes remaining ", (unsigned long long)data->content_length);
        printf("(%d%% complete) ...\n",
            (int)(((originalContentLength - data->content_length) * 100) / originalContentLength));
    }
    return ret;
}
void put_file_complete_callback(obs_status status,
    const obs_error_details *error,
    void *callback_data)
{
    put_file_object_callback_data *data = (put_file_object_callback_data *)callback_data;
    data->ret_status = status;
}
uint64_t open_file_and_get_length(char *localfile, put_file_object_callback_data *data)
{
    uint64_t content_length = 0;
    if (!content_length)
    {
        struct stat statbuf;
        if (stat(localfile, &statbuf) == -1)
        {
            fprintf(stderr, "\nERROR: Failed to stat file %s: ",
                localfile);
            return 0;
        }
        content_length = statbuf.st_size;
    }
    if (!(data->infile = fopen(localfile, "rb")))
    {
        fprintf(stderr, "\nERROR: Failed to open input file %s: ",
            localfile);
        return 0;
    }
    data->content_length = content_length;
    return content_length;
}
```
#### 代码示例二：下载对象解密
以下示例展示如何在下载对象时使用服务端解密功能：
```
#include "eSDKOBS.h"
#include <stdio.h>
#include <sys/stat.h>
// 响应回调函数，可以在这个回调中把properties的内容记录到callback_data(用户自定义回调数据)中
obs_status response_properties_callback(const obs_response_properties *properties, void *callback_data);
obs_status get_object_data_callback(int buffer_size, const char *buffer,
    void *callback_data);
void get_object_complete_callback(obs_status status,
    const obs_error_details *error,
    void *callback_data);
typedef struct get_object_callback_data
{
    FILE *outfile;
    obs_status ret_status;
}get_object_callback_data;
FILE * write_to_file(char *localfile);
int main()
{
    // 以下示例展示如何在下载对象时使用服务端解密功能：
    // 在程序入口调用obs_initialize方法来初始化网络、内存等全局资源。
    obs_status ret_status = obs_initialize(OBS_INIT_ALL);
    if (OBS_STATUS_OK != ret_status)
    {
        printf("obs_initialize failed(%s).\n", obs_get_status_name(ret_status));
        return -1;
    }
    obs_options options;
    // 创建并初始化options，该参数包括访问域名(host_name)、访问密钥（access_key_id和access_key_secret）、桶名(bucket_name)、桶存储类别(storage_class)等配置信息
    init_obs_options(&options);
    // host_name填写桶所在的endpoint, 此处以华北-北京四为例，其他地区请按实际情况填写。
    options.bucket_options.host_name = "obs.cn-north-4.myhuaweicloud.com";
    // 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险，建议在配置文件或者环境变量中密文存放，使用时解密，确保安全；
    // 本示例以ak和sk保存在环境变量中为例，运行本示例前请先在本地环境中设置环境变量ACCESS_KEY_ID和SECRET_ACCESS_KEY。
    options.bucket_options.access_key = getenv("ACCESS_KEY_ID");
    options.bucket_options.secret_access_key = getenv("SECRET_ACCESS_KEY");
    // 填写Bucket名称，例如example-bucket-name。
    char * bucket_name = "example-bucket-name";
    options.bucket_options.bucket_name = bucket_name;
    // 使用服务端加密功能时，必须使用https协议
    options.bucket_options.protocol = OBS_PROTOCOL_HTTPS;
    // 设置对象下载到本地的文件名
    char *file_name = "./example_get_file_test";
    obs_object_info object_info;
    // 设置下载的对象
    memset(&object_info, 0, sizeof(obs_object_info));
    object_info.key = "example_get_file_test";
    object_info.version_id = NULL;
    //根据业务需要设置存放下载对象数据的结构
    get_object_callback_data data;
    data.ret_status = OBS_STATUS_BUTT;
    data.outfile = write_to_file(file_name);
    // 定义范围下载参数
    obs_get_conditions getcondition;
    memset(&getcondition, 0, sizeof(obs_get_conditions));
    init_get_properties(&getcondition);
    // 定义下载的回调函数
    obs_get_object_handler get_object_handler =
    {
        { &response_properties_callback,
          &get_object_complete_callback},
        &get_object_data_callback
    };
    //服务端加密 SSEC加密
    server_side_encryption_params encryption_params;
    memset(&encryption_params, 0, sizeof(server_side_encryption_params));
    encryption_params.ssec_customer_algorithm = "AES256";
    encryption_params.ssec_customer_key =
        "K7QkYpBkM5+hcs27fsNkUnNVaobncnLht/rCB2o/9Cw=";
    get_object(&options, &object_info, &getcondition, &encryption_params, &get_object_handler, &data);
    if (OBS_STATUS_OK == data.ret_status) {
        printf("get object successfully. \n");
    }
    else
    {
        printf("get object failed(%s).\n", obs_get_status_name(data.ret_status));
    }
    fclose(data.outfile);
    // 释放分配的全局资源
    obs_deinitialize();
}
// 响应回调函数，可以在这个回调中把properties的内容记录到callback_data(用户自定义回调数据)中
obs_status response_properties_callback(const obs_response_properties *properties, void *callback_data)
{
    if (properties == NULL)
    {
        printf("error! obs_response_properties is null!");
        return OBS_STATUS_OK;      
    }
// 打印响应信息
#define print_nonnull(name, field)                                 \
    do {                                                           \
        if (properties-> field) {                                  \
            printf("%s: %s\n", name, properties->field);          \
        }                                                          \
    } while (0)
    print_nonnull("request_id", request_id);
    print_nonnull("request_id2", request_id2);
    print_nonnull("content_type", content_type);
    if (properties->content_length) {
        printf("content_length: %llu\n", properties->content_length);
    }
    print_nonnull("server", server);
    print_nonnull("ETag", etag);
    print_nonnull("expiration", expiration);
    print_nonnull("website_redirect_location", website_redirect_location);
    print_nonnull("version_id", version_id);
    print_nonnull("allow_origin", allow_origin);
    print_nonnull("allow_headers", allow_headers);
    print_nonnull("max_age", max_age);
    print_nonnull("allow_methods", allow_methods);
    print_nonnull("expose_headers", expose_headers);
    print_nonnull("storage_class", storage_class);
    print_nonnull("server_side_encryption", server_side_encryption);
    print_nonnull("kms_key_id", kms_key_id);
    print_nonnull("customer_algorithm", customer_algorithm);
    print_nonnull("customer_key_md5", customer_key_md5);
    print_nonnull("bucket_location", bucket_location);
    print_nonnull("obs_version", obs_version);
    print_nonnull("restore", restore);
    print_nonnull("obs_object_type", obs_object_type);
    print_nonnull("obs_next_append_position", obs_next_append_position);
    print_nonnull("obs_head_epid", obs_head_epid);
    print_nonnull("reserved_indicator", reserved_indicator);
    int i;
    for (i = 0; i < properties->meta_data_count; i++) {
        printf("x-obs-meta-%s: %s\n", properties->meta_data[i].name,
            properties->meta_data[i].value);
    }
    return OBS_STATUS_OK;
}
obs_status get_object_data_callback(int buffer_size, const char *buffer,
    void *callback_data)
{
    get_object_callback_data *data = (get_object_callback_data *)callback_data;
    size_t wrote = fwrite(buffer, 1, buffer_size, data->outfile);
    return ((wrote < (size_t)buffer_size) ?
        OBS_STATUS_AbortedByCallback : OBS_STATUS_OK);
}
void get_object_complete_callback(obs_status status,
    const obs_error_details *error,
    void *callback_data)
{
    get_object_callback_data *data = (get_object_callback_data *)callback_data;
    data->ret_status = status;
}
FILE * write_to_file(char *localfile)
{
    FILE *outfile = 0;
    if (localfile) {
        struct stat buf;
        if (stat(localfile, &buf) == -1) {
            outfile = fopen(localfile, "wb");
        }
        else {
            outfile = fopen(localfile, "a");
        }
        if (!outfile) {
            fprintf(stderr, "\nERROR: Failed to open output file %s: ",
                localfile);
            return outfile;
        }
    } else {
        fprintf(stderr, "\nERROR: Failed to open output file, it's NULL, write object to stdout.",
            localfile);
        outfile = stdout;
    }
    return outfile;
}
```
#### 相关链接
- 关于服务端加密的API说明，请参见[服务端加密简介](https://support.huaweicloud.com/api-obs/obs_04_0188.html)。
- 更多关于服务端加密的示例代码，请参见[Github示例](https://github.com/huaweicloud/huaweicloud-sdk-c-obs/blob/master/source/eSDK_OBS_API/eSDK_OBS_API_C++_Demo/demo.c)。
- 服务端加密接口返回的错误码含义、问题原因及处理措施可参考[OBS错误码](https://support.huaweicloud.com/api-obs/obs_04_0115.html#section1)。
 
