> ## Documentation Index
> Fetch the complete documentation index at: https://doc.haipay.net/llms.txt
> Use this file to discover all available pages before exploring further.

# 密钥配置与签名

> 了解商户平台全新的密钥配置面板，掌握公私钥管理、业务ID映射、密钥替换等核心操作，确保接入配置正确、安全高效。

**签名代码示例**

* [完整加签、验签与报文示例（历史参考）](https://doc.haipay.net/docs/zh/guide/config_settings_and_signature_rules_guide.md#4-签名)

## 1. 概述

HaiPay 全新推出了**密钥配置面板**，旨在为商户提供更便捷、更安全的密钥管理体验。新面板兼容原有配置逻辑，同时增加了以下核心功能：

* **一键生成密钥对** — 商户可直接在平台侧生成 RSA 2048 公私钥对，无需安装额外工具
* **业务ID灵活映射** — 支持为不同业务ID（appId）配置不同公钥，也支持一键配置“所有”业务ID
* **批量密钥替换** — 通过穿梭框直观管理业务ID与密钥的映射关系
* **多维度密钥查看** — 商户公钥、Haipay公钥、加密字段分标签页管理

> [!NOTE]
>
> 签名使用 **SHA256WithRSA** 签名算法，密钥长度为 **2048位**。商户私钥用于请求报文加签，商户公钥上传至 HaiPay 用于验签；HaiPay 公钥用于验证平台返回报文的签名。

> [!TIP]
>
> 如果您是首次接入 HaiPay，建议先阅读 [集成步骤指南](https://doc.haipay.net/zh/history/V20260801/get-started/integration-steps.md) 了解完整接入流程。

### 公私钥的作用

在 HaiPay 的通信架构中，公私钥承担着请求签名和响应验签的核心安全保障：

```mermaid theme={null}
flowchart LR
    %% 节点样式
    classDef entity fill:#f9fbff,stroke:#cbd5e1,stroke-width:1px;
    classDef key fill:#e6f7ff,stroke:#94a3b8,stroke-width:1px;
    classDef arrow stroke:#60a5fa,stroke-width:1.5px;

    %% 分组
    subgraph 商户["商户"]
        A["商户私钥<br/>用于请求加签"]:::key
        B["平台公钥（Haipay公钥）<br/>用于验证返回报文签名"]:::key
    end
    subgraph 平台["Haipay 平台"]
        C["商户公钥<br/>用于验证商户请求签名"]:::key
        D["平台私钥<br/>用于返回报文加签"]:::key
    end

    %% 流程
    A -->|①私钥对 body 进行签章，请求发送到平台| C:::arrow
    D -->|②平台用私钥将回传数据签章后发给商户| B:::arrow
    C -->|③平台用商户公钥进行验签| A:::arrow
    B -->|④商户用平台公钥进行验签| D:::arrow

    %% 每条箭头颜色（可自定义）
    linkStyle 0 stroke:#3b82f6,color:#2563eb,stroke-width:1.5px;
    linkStyle 1 stroke:#22c55e,color:#15803d,stroke-width:1.5px;
    linkStyle 2 stroke:#f97316,color:#c2410c,stroke-width:1.5px;
    linkStyle 3 stroke:#a855f7,color:#7e22ce,stroke-width:1.5px;
```

图1：商户与平台之间的公私钥签名/验签流程

## 2. 前置准备

在使用密钥配置面板之前，请确保您已完成以下准备工作：

**1. 获取商户平台账号**

合作确认后，HaiPay 将根据《商户接入申请表》中填写的“管理员账号信息”创建商户管理平台账号。请注意查收 HaiPay 下发的激活邮件，按指引激活账号。首次登录需更改密码。

**2. 登录商户管理平台**

使用激活后的管理员账号登录 HaiPay 商户管理自助平台。

**3. 进入开发配置**

在商户端主菜单中，找到新增的「**开发配置**」主菜单，点击进入密钥配置面板。

> [!WARNING]
>
> 商户 appId 和密钥是**配套使用**的，区分币种、区分测试环境与正式环境。请确保在正确的环境下配置密钥。

## 3. 面板总览

进入「开发配置」-「密钥配置」后，您将看到如下面板。面板顶部有三个标签页，分别管理不同类型的密钥：

![密钥配置面板主页面](https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/merchant-main.png?fit=max\&auto=format\&n=s1806vdqkUj3xbR6\&q=85\&s=24240c5b8f9451ef6254f05aae08c0bd)

图2：密钥配置面板主页面 — 您的公钥标签页

| 标签页 | 说明 | 操作权限 |
| :- | :- | :- |
| **您的公钥** | 管理商户自己的公钥，用于 HaiPay 验证商户请求签名 | 可新增、替换、修改映射 |
| **Haipay 公钥** | 查看 HaiPay 平台的公钥，用于商户验证返回报文签名 | 仅查看，不可修改 |
| **加密字段** | 查看加密字段配置信息 | 仅查看，不可修改 |

> [!NOTE]
>
> 新商户的平台公钥和加密字段默认配置到“所有”业务ID。若以往的老商户有多个不同公钥的，将展示为多行。

### 密钥展示与复制

面板中的密钥内容较长，为了美观会进行中间省略显示。点击密钥字段即可**复制完整信息**到剪贴板。

![Haipay公钥标签页](https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-haipay-key.png?fit=max\&auto=format\&n=s1806vdqkUj3xbR6\&q=85\&s=c87cbbdf540e185b9f120a4963393dfd)

图3：Haipay公钥标签页 — 切换至平台公钥查看和搜索

## 4. 关联业务ID筛选

面板顶部提供**关联业务ID**筛选项，支持单选下拉搜索。您可以搜索该商户关联的所有 appId 进行筛选查看。

![关联业务ID筛选](https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-main-page.png?fit=max\&auto=format\&n=s1806vdqkUj3xbR6\&q=85\&s=9b2d4ef4d1915ed8915b66e20e918af8)

图4：关联业务ID搜索筛选 — 支持下拉搜索特定 appId

> [!NOTE]
>
> **关于“所有”业务ID**
>
> * 如果该用户的所有 appId 都使用同一个密钥，则 appId 栏会显示为“**所有**”
> * 用户无论搜索什么 appId，都会展示选项为“所有”的行
> * 当 appId 为“所有”时，后续新增业务也会为其**自动配置**该公钥

## 5. 新增/更换密钥

点击面板右上角的「**新增/更换密钥**」按钮，将弹出密钥配置弹窗。在该弹窗中，您可以选择密钥生成方式和业务ID映射范围。

![新增/更换密钥弹窗](https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/replace-key-dialog.png?fit=max\&auto=format\&n=s1806vdqkUj3xbR6\&q=85\&s=f1573773ab6d10278a0ee6172c9c0284)

图5：新增/更换密钥弹窗 — 选择生成方式和映射范围

弹窗包含以下配置项：

**1. 选择密钥替换方式**

提供两种方式（详见下一章）：

* 推荐 **由 HaiPay 一键生成所有公私钥** — 平台侧生成 RSA 2048 密钥对，私钥复制至剪贴板，平台仅保存公钥
* **使用您自行创建的公私钥** — 手动录入已生成的公钥

**2. 选择映射业务ID范围**

提供两个选项：

* **全部** — 该密钥将应用于所有业务ID
* **特定业务ID** — 打开穿梭框，为特定业务ID配置该公钥

**3. 生成或录入密钥**

根据所选方式，点击“生成”按钮生成密钥对，或在文本框中录入您的公钥。

**4. 确认替换**

点击底部按钮提交，系统将进行二次确认弹窗。确认后新密钥立即生效，旧密钥失效。

## 6. 密钥生成方式

### 方式一：由 HaiPay 平台一键生成 推荐

这是最简便的方式。HaiPay 平台将在您点击“生成”后，直接为您生成一对 RSA 2048 公私钥。

![HaiPay一键生成密钥](https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-replace-dialog.png?fit=max\&auto=format\&n=s1806vdqkUj3xbR6\&q=85\&s=aebb11cf651d7692e03f78eae3cc4cbe)

图6：选择“由 HaiPay 一键生成”后的密钥配置界面

**1. 选择生成方式**

在弹窗中选择“由 HaiPay 一键生成所有公私钥”（默认选中，标注“推荐”）。

**2. 点击“生成”按钮**

系统将在平台侧生成一对 RSA 2048 公私钥。公钥显示在“您的公钥”字段，私钥显示在“您的私钥”字段。

**3. 点击“新增/替换并复制私钥”**

提交替换后，**私钥将自动复制到您的剪贴板**。请立即保存到安全位置。

**4. 二次确认**

系统弹出二次确认弹窗，确认后新密钥立即生效。

> [!CAUTION]
>
> * **私钥不会保存在平台**，仅本次复制到剪贴板。请务必立即保存，关闭页面后无法再次获取。
> * 替换密钥后，**原密钥将立即失效**，使用旧密钥签名的请求将无法通过验签。
> * 请在业务低峰期操作，确保新密钥已配置到您的系统中再执行替换。

### 方式二：使用自行创建的公私钥

如果您已有密钥对或希望自行管理密钥生成过程，可以选择此方式。此时会弹出文本框，要求您录入**公钥**。

![自行创建公私钥](https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-user-key.png?fit=max\&auto=format\&n=s1806vdqkUj3xbR6\&q=85\&s=8d31a80e301d2902499b7e2e8bdb9dba)

图7：选择“使用自行创建的公私钥” — 需手动录入公钥

您可以通过以下方式生成密钥对：

> **OpenSSL 生成**
>
> 使用 openssl 命令行工具生成 RSA 2048 位密钥对，需安装 openssl，密钥位数 2048 位，可参考网上示例。

> **[在线生成工具](https://doc.haipay.net/docs/zh/rsa/rsa-generate.md)**
>
> 使用 HaiPay 提供的在线 RSA 密钥生成工具（纯 JS 实现，不会与服务器交互，不会泄露商户密钥信息）。

> **代码生成**
>
> 使用 Java、PHP 等 SDK 代码生成 RSA 2048 位密钥对并转为 PEM 格式。

**查看代码生成示例（Java / PHP）**

```java Java theme={null}
/**
 * 生成2048位的RSA密钥对，并将公钥和私钥转为PEM格式
 */
public static Map<String, String> generateRSAKeyPair() {
    try {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();

        String publicKeyPem = convertPublicKeyToPEM(keyPair.getPublic());
        String privateKeyPem = convertPrivateKeyToPEM(keyPair.getPrivate());

        Map<String, String> keyPairMap = new HashMap<>();
        keyPairMap.put("publicKey", publicKeyPem);
        keyPairMap.put("privateKey", privateKeyPem);
        return keyPairMap;
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Failed to generate RSA key pair", e);
    }
}

// 公钥上传时请去掉 -----BEGIN/END PUBLIC KEY----- 头尾、换行和空格
```

```php PHP theme={null}
/**
 * 初始化RSA算法密钥对
 * @param int $keysize 建议使用 2048
 */
public function initRSAKey($keysize) {
    $config = array(
        "digest_alg" => "sha256",
        "private_key_bits" => $keysize,
        "private_key_type" => OPENSSL_KEYTYPE_RSA,
    );
    $rsaKey = openssl_pkey_new($config);
    openssl_pkey_export($rsaKey, $privateKey);
    $publicKey = openssl_pkey_get_details($rsaKey);
    $publicKey = $publicKey["key"];

    return array(
        'public_key' => $publicKey,
        'private_key' => $privateKey
    );
}
```

> [!WARNING]
>
> 公钥上传至 HaiPay 平台时，请**去掉**前后的 `-----BEGIN PUBLIC KEY-----` 和 `-----END PUBLIC KEY-----`，以及换行和空格。仅保留 Base64 编码内容。

> [!NOTE]
>
> **与方式一的区别**
>
> 选择自行创建方式时，替换仍需二次弹窗确认，但**不会**将私钥复制至剪贴板（因为没有平台生成私钥的操作）。私钥由您自行保管。

## 7. 业务ID映射

在新增/更换密钥弹窗中，“映射业务ID”提供两个选项：**“全部”**和**“特定业务ID”**。不同的选择决定了密钥与业务ID的关联方式。

### 7.1 不更改映射ID（选择“全部”）

当选择“全部”时，新的公钥将应用到该商户下的所有业务ID。后续新增的业务也会自动配置上该公钥。

![不更改映射ID — 选择全部](https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-replace-no-change.png?fit=max\&auto=format\&n=s1806vdqkUj3xbR6\&q=85\&s=76a610e0a36b74f7cfa7ee3653622bcb)

图8：映射业务ID选择“全部” — 公钥应用于所有业务ID

### 7.2 更改密钥映射（选择“特定业务ID”）

当选择“特定业务ID”时，将打开一个**穿梭框**（Transfer Box），用于选择将该公钥映射到哪些业务ID。

![穿梭框 — 业务ID映射](https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-transfer.png?fit=max\&auto=format\&n=s1806vdqkUj3xbR6\&q=85\&s=e80b4bfed5226b2037c668752db9c59d)

图9：穿梭框界面 — 左侧为未映射/全部业务ID，右侧为已选定业务ID

穿梭框的结构如下：

| 区域 | 说明 |
| :- | :- |
| **左侧面板** | 默认展示所有**未映射**的业务ID。点击下方“显示/隐藏已映射”按钮，可切换显示映射到其他密钥的业务ID。顶部名称会相应切换为“映射业务ID”或“全部业务ID”。 |
| **右侧面板** | 展示当前密钥已配置的所有业务ID。若该公钥之前的映射为“所有”，则切换时所有业务ID都会展示在右侧。 |
| **穿梭框列** | 三列：**业务ID**、**币种**、**业务名称** |
| **重置按钮** | 点击“重置为原配置”可恢复到修改前的映射状态 |

> [!WARNING]
>
> 该面板内的业务ID从右侧移至左侧时，**不视作**已经映射了的业务ID。若有其他密钥映射了业务ID，则进行隐藏，但**不针对该密钥的映射进行隐藏**。

![穿梭框详细视图](https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-transfer-detail.png?fit=max\&auto=format\&n=s1806vdqkUj3xbR6\&q=85\&s=92f2a8ba2b4c144d1b7ac89086ea46a5)

图10：穿梭框详细视图 — 含搜索、分页、穿梭按钮

## 8. 密钥覆盖提醒

当您在新增/更换密钥弹窗中提交配置时，如果新的映射范围会覆盖已有公钥的关联关系，系统会**自动弹出密钥覆盖提醒**，告知您哪些业务ID将受到影响。这是为了防止误操作导致线上签名验证失败。

> [!NOTE]
>
> **什么时候会触发覆盖提醒？**
>
> 当您的新密钥映射的业务ID中，**已经有其他公钥在关联**时，就会触发覆盖提醒。系统需要您确认：是否用新公钥替换这些业务ID原有公钥的关联关系。

根据影响范围不同，会有两种警告类型：

```mermaid theme={null}
flowchart TD
    A([提交密钥配置]) --> B{是否覆盖已有公钥映射？}
    B -->|否| C[直接保存]
    B -->|是| D{影响范围？}
    D -->|部分| E[列出受影响的具体业务ID]
    D -->|全部| F[警告所有业务ID都将受影响]
    E --> G[二次确认弹窗]
    F --> G
    G --> H([新密钥生效，旧密钥失效])
```

图11：密钥覆盖提醒完整流程 — 从提交到二次确认到生效

### 8.1 部分映射覆盖

当您的修改仅影响**部分**已映射的业务ID时，系统会弹出“密钥覆盖提醒”弹窗，明确列出**受影响的具体业务ID**。

弹窗内容如下：

* **标题**：密钥覆盖提醒
* **警告文案**：*“此次修改将覆盖以下业务id关联的密钥，旧密钥将立即失效。是否继续？”*
* **受影响的业务ID**：系统逐行列出所有受影响的 appId（如 1122、3344、5566 等）
* **操作按钮**：「取消」（返回弹窗页面） / 「确认替换」（进入二次确认）

![密钥覆盖提醒 — 部分业务ID](https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/overwrite-warning.png?fit=max\&auto=format\&n=s1806vdqkUj3xbR6\&q=85\&s=ecbb02d9a0e27d80b70d8897f4989575)

图12：密钥覆盖提醒（部分映射覆盖） — 列出受影响的具体业务ID

> [!WARNING]
>
> 列表中的业务ID对应的旧公钥在确认替换后将**立即失效**。请确保这些业务ID已在您的系统中配置好新私钥，否则相关请求将验签失败。

### 8.2 全部映射覆盖

当您将一个原本仅映射**部分**业务ID的公钥，更改为映射“**所有**”业务ID时，该操作会覆盖所有业务ID原有的密钥关联。此时弹窗会警告**所有业务ID**都将受影响。

弹窗内容如下：

* **标题**：密钥覆盖提醒
* **警告文案**：*“此次修改将覆盖所有业务id关联的密钥，旧密钥将立即失效。是否继续？”*
* **操作按钮**：「取消」（返回弹窗页面） / 「确认替换」（进入二次确认）

![密钥覆盖提醒 — 所有业务ID](https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/overwrite-warning-all.png?fit=max\&auto=format\&n=s1806vdqkUj3xbR6\&q=85\&s=f3505bccd15047fb45809e925012c200)

图13：密钥覆盖提醒（全部映射覆盖） — 警告所有业务ID都将受影响

> [!CAUTION]
>
> “全部映射覆盖”意味着**商户名下所有业务ID的密钥将一次性全部替换**。这是影响范围最大的操作，请务必确认：
>
> * 新私钥已安全保存并配置到您的所有业务系统中
> * 选择在业务低峰期执行此操作
> * 已通知相关开发/运维团队做好配合准备

### 8.3 二次确认

无论您在覆盖提醒弹窗中点击的是“部分映射覆盖”还是“全部映射覆盖”的「确认替换」按钮，系统都会再弹出**二次确认弹窗**，作为最后一道安全防线。

二次确认弹窗内容如下：

* **标题**：确定替换新密钥?
* **警告文案**：*“替换密钥是高危操作，原密钥失效后您将不能使用，请务必谨慎操作。”*
* **操作按钮**：「取消」（返回覆盖提醒弹窗） / 「确认替换」（执行替换，密钥立即生效）

![确定替换新密钥 — 二次确认](https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/confirm-replace.png?fit=max\&auto=format\&n=s1806vdqkUj3xbR6\&q=85\&s=555404a36ac89909dcc80e84e4026f75)

图14：二次确认弹窗 — 提醒替换密钥是高危操作

> [!TIP]
>
> 两层确认机制的设计目的：**第一层**（密钥覆盖提醒）让您了解影响范围，**第二层**（二次确认）让您最终决定是否执行。请在仔细阅读警告文案后再点击确认。

### 8.4 确认后的执行逻辑

在二次确认点击「确认替换」后，系统将立即执行密钥替换，具体逻辑如下：

| 场景 | 执行结果 |
| :- | :- |
| **部分映射覆盖** | 受影响的业务ID从旧公钥的映射中移除，新公钥自动关联这些业务ID。旧公钥仍保留对其他未受影响业务ID的映射关系。 |
| **全部映射覆盖** | 旧公钥的所有业务ID映射被新公钥取代，旧公钥行从列表中消失，新公钥显示为“所有”业务ID。 |

> [!NOTE]
>
> **关于“返回/取消”按钮**
>
> 无论在第一层覆盖提醒弹窗还是第二层二次确认弹窗中，点击「取消」或「返回」按钮都会**回到密钥配置弹窗页面**，不会直接关闭弹窗。您可以继续修改配置或手动关闭弹窗。

## 9. 单独更换密钥或修改映射

除了使用顶部“新增/更换密钥”按钮进行批量操作外，您还可以针对**特定的公钥**进行单独操作。在数据表的“操作”列中，每行提供两个操作链接：

![单独更换密钥和更换业务ID映射](https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-single-ops.png?fit=max\&auto=format\&n=s1806vdqkUj3xbR6\&q=85\&s=ba76873475bd44f5fea1929a585861e3)

图15：操作列 — “替换密钥”和“更换业务id映射”

| 操作 | 说明 | 固定项 |
| :- | :- | :- |
| **替换密钥** | 替换该行公钥对应的密钥内容 | 业务ID映射关系**固定不变** |
| **更换业务id映射** | 修改该公钥与业务ID的映射关系 | 公钥**固定不变**，可不修改 |

单独操作的逻辑与使用顶部按钮一致，区别在于：

* 替换密钥时，业务ID映射是固定的，仅更换密钥内容
* 更换映射时，公钥是固定的，可不修改，仅调整映射范围

![更换业务ID映射界面](https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-change-mapping.png?fit=max\&auto=format\&n=s1806vdqkUj3xbR6\&q=85\&s=844a5a6aab85b3441d3508b43953ac76)

图16：更换业务ID映射 — 穿梭框中选择新的映射范围

## 10. 安全最佳实践

> [!CAUTION]
>
> * **私钥仅显示一次** — 通过平台一键生成密钥时，私钥仅复制到剪贴板，平台不保存。关闭页面后无法再次获取。
> * **merchantSecretKey 和商户 RSA 私钥**均属于敏感信息，不得写入前端代码、客户端应用、日志或公开仓库。
> * **立即保存私钥** — 生成密钥后请立即将私钥保存到安全的密钥管理系统或加密存储中。
> * **密钥泄露应急** — 若不慎泄露密钥，请及时通过平台更新密钥。

### 签名安全规则

| 项目 | 说明 |
| :- | :- |
| 算法 | RSA |
| 签名算法 | SHA256WithRSA |
| 密钥长度 | 2048 位 |

**签名生成规则：**

将所有字段值不为 `null` 且不为 `""` 的参数，排除 `sign`、`sign_type` 字段后，按字段名 ASCII 升序排序，取 key 和 value 按 `k1=v1&k2=v2&...` 的格式进行拼接，并在结尾追加 `&key=merchantSecretKey`（商户密钥）作为待签名串。商户使用 RSA 私钥按 SHA256WithRSA 算法对待签名串进行签名。

> [!WARNING]
>
> * 如果字段值为 `null` 或 `""`，无需参与验签
> * 接口响应字段可能增加，验证签名时必须将返回报文中的**新增有效字段**一并纳入验签，不能仅按固定字段列表验签
> * 待签名串必须按 **UTF-8 编码**为字节序列后再执行 SHA256WithRSA 签名或验签
> * 不要使用宽松空值判断（如 PHP `empty()` 或 JS `!value`）过滤参数，否则可能错误排除 `0`、`false` 或 `"0"`，导致验签失败

**查看签名工具代码（Java / PHP / JavaScript）**

```java Java theme={null}
public static String getSign(Object obj, String secretKey) {
    Map<String, Object> map;
    if (obj instanceof Map) {
        map = (Map<String, Object>) obj;
    } else {
        map = BeanMapTool.beanToMap(obj);
    }
    Set<String> keys = map.keySet();
    List<String> list = new ArrayList<>(keys);
    Collections.sort(list);

    StringBuilder sb = new StringBuilder();
    for (String key : list) {
        Object val = map.get(key);
        if (!("".equals(val) || val == null
            || List.of("sign_type", "sign").contains(key))) {
            sb.append(key).append("=").append(val).append("&");
        }
    }
    sb.append("key=").append(secretKey);
    return sb.toString();
}
```

```php PHP theme={null}
// 不要使用 empty() 等宽松空值判断
function getSign($array, $merchantSecretKey) {
    $keys = array_keys($array);
    sort($keys, SORT_STRING);
    $str = "";
    foreach ($keys as $key) {
        $val = $array[$key];
        if ($val !== null && $val !== ''
            && $key !== "sign" && $key !== "sign_type") {
            $str .= $key . "=" . $val . "&";
        }
    }
    return $str . "key=" . $merchantSecretKey;
}
```

```js JavaScript theme={null}
// 不要使用 if (!value) 等宽松空值判断
function getSign(map, merchantSecretKey) {
    const keys = Object.keys(map).sort();
    const sb = keys.reduce((acc, key) => {
        const val = map[key];
        if (val !== "" && val !== null
            && key !== "sign" && key !== "sign_type") {
            acc.push(`${key}=${val}`);
        }
        return acc;
    }, []).join('&');
    return `${sb}&key=${merchantSecretKey}`;
}
```

## 11. 密钥格式要求

上传公钥至 HaiPay 平台时，请注意以下格式要求：

| 项目 | 要求 |
| :- | :- |
| 密钥算法 | RSA |
| 密钥长度 | 2048 位 |
| 公钥格式 | PEM 格式（X.509 SubjectPublicKeyInfo） |
| 上传要求 | 去掉 `-----BEGIN PUBLIC KEY-----` 和 `-----END PUBLIC KEY-----` 头尾标记、换行和空格，仅保留 Base64 编码内容 |
| 公钥示例 | `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...` |

> [!TIP]
>
> 如果您选择“HaiPay 一键生成”方式，平台会自动处理格式，您无需手动处理。该注意事项仅适用于“自行创建公私钥”方式。

## 12. 常见问题

> **Q: 选择「HaiPay一键生成」后，私钥保存在哪里？**
>
> 私钥**不会保存在 HaiPay 平台**。生成后，私钥会自动复制到您的剪贴板，您需要立即保存到安全的位置。平台仅保存公钥用于验签。
>
> > [!CAUTION]
> >
> > 关闭弹窗后私钥将无法再次获取，请务必在操作时立即保存。

> **Q: 替换密钥后旧密钥还能用吗？**
>
> 不能。替换密钥后，**原密钥将立即失效**。使用旧私钥签名的请求将无法通过 HaiPay 的验签。请在业务低峰期操作，并确保新私钥已配置到您的系统中后再执行替换。

> **Q: 业务ID显示为「所有」是什么意思？**
>
> 当 appId 栏显示为“所有”时，表示该密钥应用于商户名下的**所有业务ID**。后续新增的业务也会自动配置上该公钥。如果您需要为不同业务ID配置不同密钥，请使用“特定业务ID”映射功能。

> **Q: 穿梭框中「显示/隐藏已映射」按钮的作用是什么？**
>
> 穿梭框左侧面板默认仅展示**未映射**的业务ID。点击“显示/隐藏已映射”按钮可以切换显示已映射到其他密钥的业务ID，方便您在不同密钥之间调整映射关系。顶部名称也会相应切换为“映射业务ID”或“全部业务ID”。

> **Q: 为什么更换密钥时会有多次弹窗确认？**
>
> 密钥替换是**高危操作**，系统设计了多层确认机制以防止误操作：
>
> * **第一层**：密钥覆盖提醒 — 告知哪些业务ID会受影响
> * **第二层**：二次确认弹窗 — 最终确认是否执行替换
>
> 任意一步点击“返回/取消”都会回到弹窗页面，不会直接关闭。

> **Q: 如何为不同业务ID配置不同公钥？**
>
> 在“新增/更换密钥”弹窗中，映射业务ID选择“特定业务ID”，打开穿梭框。在穿梭框中将需要映射的业务ID从左侧移至右侧，确认后该公钥将仅应用于右侧选中的业务ID。
>
> 您也可以在数据表操作列点击“更换业务id映射”来调整已有公钥的映射范围。

> **Q: 验签失败怎么办？**
>
> 请检查以下几点：
>
> * 确认使用的**私钥**与上传至平台的**公钥**是同一对密钥
> * 确认签名串是否按 **ASCII 升序**排序拼接
> * 确认排除了 `sign` 和 `sign_type` 字段
> * 确认字段值为 `null` 或 `""` 的参数未参与签名
> * 确认待签名串使用 **UTF-8 编码**
> * 确认未使用宽松空值判断（如 PHP `empty()` 或 JS `!value`）
> * 验证返回报文时，确认将**所有新增有效字段**纳入验签

## 相关主题

> **[集成步骤指南](https://doc.haipay.net/zh/history/V20260801/get-started/integration-steps.md)**
>
> 从账号创建到首次 API 调用的完整接入流程。

> **[联调环境与请求地址](https://doc.haipay.net/docs/zh/guide/api_parameters_doc.md)**
>
> API 联调环境说明与请求地址一览。

> **[接口说明与公共规则](https://doc.haipay.net/zh/history/V20260801/get-started/api-conventions.md)**
>
> HaiPay 接口说明与公共请求/返回规则。

> **[RSA 在线密钥生成工具](https://doc.haipay.net/docs/zh/rsa/rsa-generate.md)**
>
> 纯 JS 实现的 RSA 密钥对在线生成工具，不会与服务器交互。

***

最后更新：2026年9月 · HaiPay 技术文档团队

如有疑问，请在对接群联系 HaiPay 24小时值班运营。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.