---
title: "AK/SK 签名生成规则 | OceanBase 文档中心"
description: AK/SK 签名生成规则 概述 您将 HTTP 请求发送到 OCP 时，OCP 需要对您的请求进行签名计算并生成认证字符串，以便识别请求者身份、保护传输过程数据安全性并防止重复攻击。 系统将使用 OCP 的访问密钥来进行签名计算，该访问密钥包含访问密钥 ID（Access Key Id，简称 AK）和秘密访问密钥（S…
image: https://mdn.alipayobjects.com/huamei_22khvb/afts/img/A*OSPzQ6GUQF4AAAAAQHAAAAgAeiGDAQ/original
---
切换语言

- 中文站 - 简体中文
- International - English
- 日本站 - 日本語

文档反馈![](https://mdn.alipayobjects.com/huamei_22khvb/afts/img/A*lJTmRZ61jSUAAAAAAAAAAAAADiGDAQ/original) 云平台 OCPV 4.0.3 社区版

# AK/SK 签名生成规则

更新时间：2025-07-02 15:21:03

[编辑](https://github.com/oceanbase/ocp-doc/edit/V4.0.3/zh-CN/600.api-reference/300.1.ak-sk-sign-roles.md)  

## 概述

您将 HTTP 请求发送到 OCP 时，OCP 需要对您的请求进行签名计算并生成认证字符串，以便识别请求者身份、保护传输过程数据安全性并防止重复攻击。

系统将使用 OCP 的访问密钥来进行签名计算，该访问密钥包含访问密钥 ID（Access Key Id，简称 AK）和秘密访问密钥（Secret Access Key，简称 SK）。

## 使用说明

### 步骤一：获取 AK/SK

1. 登录 OCP 。
 2. 将光标悬停于右上角用户名上，在弹出的菜单栏中单击 **个人设置**。
 3. 进入 **个人设置** 页面，在 **AccessKey** 模块单击 **一键创建 AccessKey**，详情可参考 [设置个人信息](https://www.oceanbase.com/docs/community-ocp-cn-10000000001832599)。

   创建成功后请妥善保存 AK/SK。

### 步骤二：使用 AK/SK 发起请求

您将 HTTP 请求发送至 OCP 时，需保证 HTTP Header 中包含 Authorization 字段及 Date 字段，以便 OCP 服务端对请求进行认证。

- **Authorization**：OCP-ACCESS-KEY-{签名算法} {AK}:{Signature} 。
 - **Date**：Tue, 17 Jan 2023 03:36:01 GMT 。
 - **签名算法**：目前仅支持 `HMACSHA1`，需注意此处 `HMACSHA1` 为大写形式。
 - **AK**：密钥 ID，请注意签名算法与 AK 之间存在空格。
 - **Signature**：计算得到的签名，具体计算规则请参考 **签名生成**。
 - **Date**：请求发起时间。其遵循 RFC1123 格式，且发起时间至到达服务器时间需 **<15** 分钟。同时需注意时区、时间一致性问题。

OCP Server 端在接受到您的请求后，会解析出 Authorization 及 Date ，并加以校验。如果校验通过，则继续处理请求，否则将报错。目前仅支持 HTTP Header 中包含认证字符串。

#### 注意

AK/SK 属于某个用户，具备的权限等同于此用户所享有的权限。

## 签名生成

Signature 的获取流程如下：

![1](https://obbusiness-private.oss-cn-shanghai.aliyuncs.com/doc/img/ocp/402-cn/aksk.png)

生成公式为：$\mathrm{signature} = Base64(H(K, \mathrm{message}))$，其中：

- **Base64**：代表用 Base64 编码。
 - **H**：代表 Hash 函数，目前仅支持 HMACSHA1。
 - **K**：AccessKey Secret 即 SK。
 - **message**：签名消息体，具体请参考 **签名消息体 message**。

## 签名消息体 message

message 计算公式如下：

```Java
message = Http Method + "\n" + Content-md5 + "\n" + Content-Type + "\n" + Date+ "\n" + Host+ "\n" + x-ocp-headers + "\n" + uri + query 参数

```

#### 注意

uri 与 query 参数拼接是通过 “+” 而非 “\n”。另外某项内容可能为空（例如: Content-md5），当为空时需注意连接符 “\n” 是存在的。

从公式可以看出 message 由 **Http Method**、**Content-md5**、**Content-Type**、**Date**、**Host**、**x-ocp-headers**、**uri** 和 **query** 参数八部分组成，它们的具体含义及获取方式如下：

| 名称 | 内容格式 | 示例 |
| --- | --- | --- |
| Http Method | HTTP 请求方法，大写英文。包括：GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS 和 TRACE。 | GET |
| Content-md5 | HTTP 请求消息 Body 的 MD5 哈希值，转为十六进制大写英文字符串。 | 1572A15D7DE7EE9E7BB86461FFEA9499 |
| Content-Type | HTTP 请求中 Body 部分的类型。 | application/json |
| Date | 请求发起时间，其遵循 RFC1123 格式。 | Mon, 3 Jan 2010 08:33:47 GMT |
| Host | HTTP 请求 Host。 | xxx.xxx.xxx.xxx:8080 |
| x-ocp-headers | header 中以 `x-ocp-` 为前缀的键值对，按 key 升序排序，以 “:” 连接键值（多个 value 逗号分隔升序排列），以换行符分隔键值对。 | x-ocp-date:Tue, 17 Jan 2023 03:36:01 GMT |
| uri | 请求的路径，不包括域名与请求参数部分。 | /api/v2/iam/users |
| query 参数 | 请求的查询参数。以 “?” 开头，按 key 升序排序，以 “=” 连接键值（多个 value 时逗号分隔升序排列），以 “&” 分隔键值对。key、value 需进行 URL 编码，编码规则遵照 RFC3986 规定。 | ?a=1&b=2   > **注意**   >  对于 a=1&a=2&a=3 这种，需转为 a=1,2,3 值升序排列且逗号分隔。 |

#### 注意

对于 **x-ocp-headers**、**query 参数** 中涉及的排序规则为：按照按字典顺序比较两个字符串，比较基于字符串中每个字符的 Unicode 值。如果 Unicode 值都相等，则较短的字符串按字典顺序排在较长字符串的前面。对于 **query 参数** 中的 URL 编码，需注意特殊字符，例如 “+” 号转义为 “%20”。

完整的 message 如下（除最后一行外，其他行末尾都有换行）：

```SQL
POST
186974DB33A090A16D3E2CA35F547B56
application/json
Tue, 17 Jan 2023 03:36:01 GMT
ocp.alibaba.net:8080
x-ocp-data:1,2,A,J,Z
/api/v2/compute/idcs?userId=1001

```

## 请求签名过程示例

上述介绍了签名算法及签名消息体的生成，下面将通过实例，为您演示生成的详细步骤。

假设当前获取到的 AK/SK 如下：

```shell
AK：cqammmxBpfGjFlto
SK：2fc0c299cc94c6be266f2ceece765d4d

```

### 案例一

我们以调用 OCP 创建 IDC 为例，假设将要发起的 HTTP 请求如下（省略部分无关信息）：

```shell
POST /api/v2/compute/idcs HTTP/1.1
Content-Type	application/json
x-ocp-data	A,1
Host	ocp.alibaba.net:8080

请求体：{"name":"test01","description":"test","regionId":1} 请求发起时间：Tue, 17 Jan 2023 09:13:57 GMT

```

#### 注意

上述请求体其 MD5 结算结果为 `186974DB33A090A16D3E2CA35F547B56`。

根据 **签名消息题 message** 构造规则，上述 API 构造出的 message 如下：

```SQL
POST\n + 186974DB33A090A16D3E2CA35F547B56\n + application/json\n + Tue, 17 Jan 2023 09:13:57 GMT\n + ocp.alibaba.net:8080\n + x-ocp-data:A,1\n + /api/v2/compute/idcs

```

根据 SK 及 message 最终计算出的签名如下：

```SQL
XN8P+O+v3vUabB16ZCooq5wMJoY=

```

最终得到的 Authorizaton 为：

```Java
OCP-ACCESS-KEY-HMACSHA1 cqammmxBpfGjFlto:XN8P+O+v3vUabB16ZCooq5wMJoY=

```

将 Authorizaton、Date 设置进请求 Header，最终发起的 HTTP 请求内容如下：

```SQL
POST /api/v2/compute/idcs HTTP/1.1
Content-Type	application/json
x-ocp-data	A,1
Authorization	OCP-ACCESS-KEY-HMACSHA1 cqammmxBpfGjFlto:XN8P+O+v3vUabB16ZCooq5wMJoY=
Date	Tue, 17 Jan 2023 09:13:57 GMT
Host	ocp.alibaba.net:8080

```

### 案例二

我们以分页查询 IDC 列表为列，假设将要发起的 HTTP 请求如下（省略部分无关信息）：

```SQL
GET /api/v2/compute/idcs?size=100 HTTP/1.1
Content-Type	application/json;charset=utf-8
Host	ocp.alibaba.net:8080

请求发起时间：Tue, 17 Jan 2023 04:14:02 GMT

```

#### 注意

由于请求 body 为空，因此 MD5 值为空字符串。由于无 `x-ocp` 开头的 header 数据，因此 `x-ocp-header` 也是空字符串。

上述 API 构造出的 message 如下：

```SQL
GET\n + \n + application/json;charset=utf-8\n + Tue, 17 Jan 2023 04:14:02 GMT\n + ocp.alibaba.net:8080\n + \n + /api/v2/compute/idcs?size=100

```

同样，以前面示例中的 SK 做签名运算，得到的签名为：

```SQL
TsQD6HDOuZuJ409m0wdnZPmijlc=

```

```Java
OCP-ACCESS-KEY-HMACSHA1 cqammmxBpfGjFlto:TsQD6HDOuZuJ409m0wdnZPmijlc=

```

```SQL
GET /api/v2/compute/idcs?size=100 HTTP/1.1
Content-Type	application/json;charset=utf-8
Authorization	OCP-ACCESS-KEY-HMACSHA1 cqammmxBpfGjFlto:TsQD6HDOuZuJ409m0wdnZPmijlc=
Date	Tue, 17 Jan 2023 04:14:02 GMT
Host	ocp.alibaba.net:8080
Connection	keep-alive

```

## Java 签名示例代码

```Java
import com.google.common.collect.Lists;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Collectors;

public class ExampleTest {
    public static void main(String[] args) {
        String sk = "2fc0c299cc94c6be266f2ceece765d4d";
        String httpMethod = "POST";
        byte[] bodyByte = "{\"name\":\"test01\",\"description\":\"test\",\"regionId\":1}".getBytes(StandardCharsets.UTF_8);
        String contentType = "application/json";
        String rfc1123Date = "Tue, 17 Jan 2023 09:13:57 GMT";
        String host = "ocp.alibaba.net:8080";
        Map<String, List<String>> xOcpHeaders = new HashMap<>();
        ArrayList<String> list = Lists.newArrayList("A", "1");
        xOcpHeaders.put("x-ocp-data", list);
        String path = "/api/v2/compute/idcs";
        Map<String, List<String>> queryParams = new HashMap<>();
        String generator = generator(sk, httpMethod, bodyByte, contentType, rfc1123Date, host, xOcpHeaders,
                path, queryParams);
        // XN8P+O+v3vUabB16ZCooq5wMJoY=
        System.out.println(generator);
    }

    /**
     *  生成签名
     */
    public static String generator(String accessKeySecret,
                                   String httpMethod,
                                   byte[] body,
                                   String contentType,
                                   String rfc1123Date,
                                   String host,
                                   Map<String, List<String>> headers,
                                   String path,
                                   Map<String, List<String>> queryParams) {
        String message = buildMessage(
                httpMethod,
                md5Crypt(body),
                contentType,
                rfc1123Date,
                host,
                buildXOcpHeader(headers),
                buildUrlAndParam(path, queryParams));
        System.out.println(message);
        // encode
        return Base64.getEncoder().encodeToString(
                hmacSha1(accessKeySecret, message.getBytes(StandardCharsets.UTF_8)));
    }

    /**
     * 对参数排序，构造新的请求 URL 用于签名
     *
     * @param requestUri 请求 URL
     * @param paramMap 请求参数
     * @return 待加签的 URL + param
     */
    public static String buildUrlAndParam(String requestUri, Map<String, List<String>> paramMap) {
        if (MapUtils.isEmpty(paramMap)) {
            return requestUri;
        }
        String urlParam = paramMap.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .map(e -> {
                    String value = e.getValue().stream()
                            .filter(StringUtils::isNotEmpty).sorted().collect(Collectors.joining(","));
                    return String.format("%s=%s", encoder(e.getKey()), encoder(value));
                })
                .collect(Collectors.joining("&"));
        urlParam = urlParam.replace("%2B", "%20");
        return requestUri + "?" + urlParam;
    }

    /**
     * 构建待加签的 header
     *
     * @param headers 请求 header
     * @return 待加签的 header
     */
    public static String buildXOcpHeader(Map<String, List<String>> headers) {
        return headers.entrySet().stream()
                .filter(e -> StringUtils.startsWithIgnoreCase(e.getKey(), "x-ocp"))
                .sorted(Map.Entry.comparingByKey())
                .map(e -> String.format("%s:%s", e.getKey(), String.join(",", e.getValue())))
                .collect(Collectors.joining("\n"));
    }

    /**
     * MD5 加密
     *
     * @param content 待 MD5 加密的 byte content
     * @return MD5 加密结果
     */
    private static String md5Crypt(byte[] content) {
        try {
            if (content == null || content.length == 0) {
                return null;
            }
            MessageDigest digest = MessageDigest.getInstance("MD5");
            digest.update(content);
            String hex = new BigInteger(1, digest.digest()).toString(16).toUpperCase();
            return StringUtils.leftPad(hex, 32, "0");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Not supported signature method MD5", e);
        }
    }

    /**
     * HMAC-SHA1 加密
     *
     * @param accessKeySecret accessKeySecret
     * @param content 待 HMAC-SHA1 加密的 byte 数组
     * @return 加密结果
     */
    private static byte[] hmacSha1(String accessKeySecret, byte[] content) {
        try {
            Validate.notEmpty(accessKeySecret, "cannot be null or empty.");
            Validate.notNull(content, "content");
            Validate.isTrue(content.length != 0, "content cannot be empty.");

            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(new SecretKeySpec(accessKeySecret.getBytes(StandardCharsets.UTF_8), "HmacSHA1"));
            return mac.doFinal(content);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Not supported signature method HmacSHA1", e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException("Failed to calculate the signature", e);
        }
    }

    private static String buildMessage(String... contents) {
        StringJoiner joiner = new StringJoiner("\n");
        for (String content : contents) {
            if (StringUtils.isNotEmpty(content)) {
                joiner.add(content);
            } else {
                joiner.add("");
            }
        }
        return joiner.toString();
    }
    private static final BitSet URI_UNRESERVED_CHARACTERS = new BitSet();
    private static final String[] PERCENT_ENCODED_STRINGS = new String[256];
    static {
        for (int i = 'a'; i <= 'z'; i++) {
            URI_UNRESERVED_CHARACTERS.set(i);
        }
        for (int i = 'A'; i <= 'Z'; i++) {
            URI_UNRESERVED_CHARACTERS.set(i);
        }
        for (int i = '0'; i <= '9'; i++) {
            URI_UNRESERVED_CHARACTERS.set(i);
        }
        URI_UNRESERVED_CHARACTERS.set('-');
        URI_UNRESERVED_CHARACTERS.set('.');
        URI_UNRESERVED_CHARACTERS.set('_');
        URI_UNRESERVED_CHARACTERS.set('~');

        for (int i = 0; i < PERCENT_ENCODED_STRINGS.length; ++i) {
            PERCENT_ENCODED_STRINGS[i] = String.format("%%%02X", i);
        }
    }
    /**
     * URLEncoder 遵照 RFC3986规定。"URI 非保留字符" 包括以下字符：字母（A-Z，a-z）、数字（0-9）、连字号（-）、点号（.）、下划线（_)、波浪线（~），
     * 算法实现如下： 1.将字符串转换成UTF-8编码的字节。2.保留所有“URI 非保留字符”原样不变。3.对其余字节做一次 RFC3986 中规定的百分号编码（Percent-encoding）， 即一个“%”后面跟着两个表示该字节值的十六进制字母，字母一律采用大写形式。
     */
    public static String encoder(String value) {
        if (StringUtils.isEmpty(value)) {
            return StringUtils.EMPTY;
        }
        StringBuilder builder = new StringBuilder();
        for (byte b : value.getBytes(StandardCharsets.UTF_8)) {
            if (URI_UNRESERVED_CHARACTERS.get(b & 0xFF)) {
                builder.append((char) b);
            } else {
                builder.append(PERCENT_ENCODED_STRINGS[b & 0xFF]);
            }
        }
        return builder.toString();
    }
}

```

 上一篇 下一篇 ![有帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*y6ocSqN8cqsAAAAAAAAAAAAAARQnAQ)![无帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*BG9IQJyLHF8AAAAAAAAAAAAAARQnAQ)![反馈](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*eTWdQKCRKHwAAAAAAAAAAAAAARQnAQ)[AI](https://www.oceanbase.com/obi) 咨询热线
