> ## 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.

# HaiPay 配置与签名指南

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

## 1. 概述

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

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

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

<Tip>
  如果您是首次接入 HaiPay，建议先阅读 [集成步骤指南](/docs/zh/guide/integration_guide) 了解完整接入流程。
</Tip>

### 公私钥的作用

在 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;
```

<div class="frame-caption">图1：商户与平台之间的公私钥签名/验签流程</div>

## 2. 前置准备

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

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

  <Step title="登录商户管理平台">
    使用激活后的管理员账号登录 HaiPay 商户管理自助平台。
  </Step>

  <Step title="进入开发配置">
    在商户端主菜单中，找到新增的「**开发配置**」主菜单，点击进入密钥配置面板。
  </Step>
</Steps>

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

## 3. 面板总览

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

<div class="frame">
  <img src="https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/merchant-main.png?fit=max&auto=format&n=s1806vdqkUj3xbR6&q=85&s=24240c5b8f9451ef6254f05aae08c0bd" alt="密钥配置面板主页面" width="1062" height="514" data-path="images/rsa/zh/merchant-main.png" />

  <div class="frame-caption">图2：密钥配置面板主页面 — 您的公钥标签页</div>
</div>

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

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

### 密钥展示与复制

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

<div class="frame">
  <img src="https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-haipay-key.png?fit=max&auto=format&n=s1806vdqkUj3xbR6&q=85&s=c87cbbdf540e185b9f120a4963393dfd" alt="Haipay公钥标签页" width="1524" height="590" data-path="images/rsa/zh/wireframe-haipay-key.png" />

  <div class="frame-caption">图3：Haipay公钥标签页 — 切换至平台公钥查看和搜索</div>
</div>

## 4. 关联业务ID筛选

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

<div class="frame">
  <img src="https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-main-page.png?fit=max&auto=format&n=s1806vdqkUj3xbR6&q=85&s=9b2d4ef4d1915ed8915b66e20e918af8" alt="关联业务ID筛选" width="1488" height="540" data-path="images/rsa/zh/wireframe-main-page.png" />

  <div class="frame-caption">图4：关联业务ID搜索筛选 — 支持下拉搜索特定 appId</div>
</div>

<Info>
  **关于“所有”业务ID**

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

## 5. 新增/更换密钥

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

<div class="frame">
  <img src="https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/replace-key-dialog.png?fit=max&auto=format&n=s1806vdqkUj3xbR6&q=85&s=f1573773ab6d10278a0ee6172c9c0284" alt="新增/更换密钥弹窗" width="1232" height="568" data-path="images/rsa/zh/replace-key-dialog.png" />

  <div class="frame-caption">图5：新增/更换密钥弹窗 — 选择生成方式和映射范围</div>
</div>

弹窗包含以下配置项：

<Steps>
  <Step title="选择密钥替换方式">
    提供两种方式（详见下一章）：

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

  <Step title="选择映射业务ID范围">
    提供两个选项：

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

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

  <Step title="确认替换">
    点击底部按钮提交，系统将进行二次确认弹窗。确认后新密钥立即生效，旧密钥失效。
  </Step>
</Steps>

## 6. 密钥生成方式

### 方式一：由 HaiPay 平台一键生成 <span class="badge-recommended">推荐</span>

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

<div class="frame">
  <img src="https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-replace-dialog.png?fit=max&auto=format&n=s1806vdqkUj3xbR6&q=85&s=aebb11cf651d7692e03f78eae3cc4cbe" alt="HaiPay一键生成密钥" width="1161" height="544" data-path="images/rsa/zh/wireframe-replace-dialog.png" />

  <div class="frame-caption">图6：选择“由 HaiPay 一键生成”后的密钥配置界面</div>
</div>

<Steps>
  <Step title="选择生成方式">
    在弹窗中选择“由 HaiPay 一键生成所有公私钥”（默认选中，标注“推荐”）。
  </Step>

  <Step title="点击“生成”按钮">
    系统将在平台侧生成一对 RSA 2048 公私钥。公钥显示在“您的公钥”字段，私钥显示在“您的私钥”字段。
  </Step>

  <Step title="点击“新增/替换并复制私钥”">
    提交替换后，**私钥将自动复制到您的剪贴板**。请立即保存到安全位置。
  </Step>

  <Step title="二次确认">
    系统弹出二次确认弹窗，确认后新密钥立即生效。
  </Step>
</Steps>

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

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

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

<div class="frame">
  <img src="https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-user-key.png?fit=max&auto=format&n=s1806vdqkUj3xbR6&q=85&s=8d31a80e301d2902499b7e2e8bdb9dba" alt="自行创建公私钥" width="1153" height="605" data-path="images/rsa/zh/wireframe-user-key.png" />

  <div class="frame-caption">图7：选择“使用自行创建的公私钥” — 需手动录入公钥</div>
</div>

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

<Columns cols={3}>
  <Card title="OpenSSL 生成" icon="wrench" iconColor="#1565C0" iconBackground="#E3F2FD">
    使用 openssl 命令行工具生成 RSA 2048 位密钥对，需安装 openssl，密钥位数 2048 位，可参考网上示例。
  </Card>

  <Card title="在线生成工具" icon="globe" iconColor="#2E7D32" iconBackground="#E8F5E9" href="/docs/zh/rsa/rsa-generate">
    使用 HaiPay 提供的在线 RSA 密钥生成工具（纯 JS 实现，不会与服务器交互，不会泄露商户密钥信息）。
  </Card>

  <Card title="代码生成" icon="laptop" iconColor="#7B1FA2" iconBackground="#F3E5F5">
    使用 Java、PHP 等 SDK 代码生成 RSA 2048 位密钥对并转为 PEM 格式。
  </Card>
</Columns>

<Expandable title="查看代码生成示例（Java / PHP）">
  <CodeGroup>
    ```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
        );
    }
    ```
  </CodeGroup>
</Expandable>

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

<Info>
  **与方式一的区别**

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

## 7. 业务ID映射

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

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

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

<div class="frame">
  <img src="https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-replace-no-change.png?fit=max&auto=format&n=s1806vdqkUj3xbR6&q=85&s=76a610e0a36b74f7cfa7ee3653622bcb" alt="不更改映射ID — 选择全部" width="1154" height="605" data-path="images/rsa/zh/wireframe-replace-no-change.png" />

  <div class="frame-caption">图8：映射业务ID选择“全部” — 公钥应用于所有业务ID</div>
</div>

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

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

<div class="frame">
  <img src="https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-transfer.png?fit=max&auto=format&n=s1806vdqkUj3xbR6&q=85&s=e80b4bfed5226b2037c668752db9c59d" alt="穿梭框 — 业务ID映射" width="1159" height="809" data-path="images/rsa/zh/wireframe-transfer.png" />

  <div class="frame-caption">图9：穿梭框界面 — 左侧为未映射/全部业务ID，右侧为已选定业务ID</div>
</div>

穿梭框的结构如下：

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

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

<div class="frame">
  <img src="https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-transfer-detail.png?fit=max&auto=format&n=s1806vdqkUj3xbR6&q=85&s=92f2a8ba2b4c144d1b7ac89086ea46a5" alt="穿梭框详细视图" width="998" height="433" data-path="images/rsa/zh/wireframe-transfer-detail.png" />

  <div class="frame-caption">图10：穿梭框详细视图 — 含搜索、分页、穿梭按钮</div>
</div>

## 8. 密钥覆盖提醒

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

<Info>
  **什么时候会触发覆盖提醒？**

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

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

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

<div class="frame-caption">图11：密钥覆盖提醒完整流程 — 从提交到二次确认到生效</div>

### 8.1 部分映射覆盖

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

弹窗内容如下：

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

<div class="frame">
  <img src="https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/overwrite-warning.png?fit=max&auto=format&n=s1806vdqkUj3xbR6&q=85&s=ecbb02d9a0e27d80b70d8897f4989575" alt="密钥覆盖提醒 — 部分业务ID" width="512" height="426" data-path="images/rsa/zh/overwrite-warning.png" />

  <div class="frame-caption">图12：密钥覆盖提醒（部分映射覆盖） — 列出受影响的具体业务ID</div>
</div>

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

### 8.2 全部映射覆盖

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

弹窗内容如下：

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

<div class="frame">
  <img src="https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/overwrite-warning-all.png?fit=max&auto=format&n=s1806vdqkUj3xbR6&q=85&s=f3505bccd15047fb45809e925012c200" alt="密钥覆盖提醒 — 所有业务ID" width="512" height="324" data-path="images/rsa/zh/overwrite-warning-all.png" />

  <div class="frame-caption">图13：密钥覆盖提醒（全部映射覆盖） — 警告所有业务ID都将受影响</div>
</div>

<Danger title="高危场景">
  “全部映射覆盖”意味着**商户名下所有业务ID的密钥将一次性全部替换**。这是影响范围最大的操作，请务必确认：

  * 新私钥已安全保存并配置到您的所有业务系统中
  * 选择在业务低峰期执行此操作
  * 已通知相关开发/运维团队做好配合准备
</Danger>

### 8.3 二次确认

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

二次确认弹窗内容如下：

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

<div class="frame">
  <img src="https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/confirm-replace.png?fit=max&auto=format&n=s1806vdqkUj3xbR6&q=85&s=555404a36ac89909dcc80e84e4026f75" alt="确定替换新密钥 — 二次确认" width="512" height="324" data-path="images/rsa/zh/confirm-replace.png" />

  <div class="frame-caption">图14：二次确认弹窗 — 提醒替换密钥是高危操作</div>
</div>

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

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

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

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

<Info>
  **关于“返回/取消”按钮**

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

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

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

<div class="frame">
  <img src="https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-single-ops.png?fit=max&auto=format&n=s1806vdqkUj3xbR6&q=85&s=ba76873475bd44f5fea1929a585861e3" alt="单独更换密钥和更换业务ID映射" width="1524" height="592" data-path="images/rsa/zh/wireframe-single-ops.png" />

  <div class="frame-caption">图15：操作列 — “替换密钥”和“更换业务id映射”</div>
</div>

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

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

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

<div class="frame">
  <img src="https://mintcdn.com/haipay-3385f6e8/s1806vdqkUj3xbR6/images/rsa/zh/wireframe-change-mapping.png?fit=max&auto=format&n=s1806vdqkUj3xbR6&q=85&s=844a5a6aab85b3441d3508b43953ac76" alt="更换业务ID映射界面" width="1157" height="898" data-path="images/rsa/zh/wireframe-change-mapping.png" />

  <div class="frame-caption">图16：更换业务ID映射 — 穿梭框中选择新的映射范围</div>
</div>

## 10. 安全最佳实践

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

### 签名安全规则

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

**签名生成规则：**

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

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

<Expandable title="查看签名工具代码（Java / PHP / JavaScript）">
  <CodeGroup>
    ```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}`;
    }
    ```
  </CodeGroup>
</Expandable>

## 11. 密钥格式要求

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

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

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

## 12. 常见问题

<Accordion title="Q: 选择「HaiPay一键生成」后，私钥保存在哪里？">
  私钥**不会保存在 HaiPay 平台**。生成后，私钥会自动复制到您的剪贴板，您需要立即保存到安全的位置。平台仅保存公钥用于验签。

  <Danger title="注意">
    关闭弹窗后私钥将无法再次获取，请务必在操作时立即保存。
  </Danger>
</Accordion>

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

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

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

<Accordion title="Q: 为什么更换密钥时会有多次弹窗确认？">
  密钥替换是**高危操作**，系统设计了多层确认机制以防止误操作：

  * **第一层**：密钥覆盖提醒 — 告知哪些业务ID会受影响
  * **第二层**：二次确认弹窗 — 最终确认是否执行替换

  任意一步点击“返回/取消”都会回到弹窗页面，不会直接关闭。
</Accordion>

<Accordion title="Q: 如何为不同业务ID配置不同公钥？">
  在“新增/更换密钥”弹窗中，映射业务ID选择“特定业务ID”，打开穿梭框。在穿梭框中将需要映射的业务ID从左侧移至右侧，确认后该公钥将仅应用于右侧选中的业务ID。

  您也可以在数据表操作列点击“更换业务id映射”来调整已有公钥的映射范围。
</Accordion>

<Accordion title="Q: 验签失败怎么办？">
  请检查以下几点：

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

## 相关主题

<Columns cols={2}>
  <Card title="集成步骤指南" icon="plug" iconColor="#7B3FF2" iconBackground="#F3EEFF" href="/docs/zh/guide/integration_guide">
    从账号创建到首次 API 调用的完整接入流程。
  </Card>

  <Card title="联调环境与请求地址" icon="clipboard-list" iconColor="#1565C0" iconBackground="#E3F2FD" href="/docs/zh/guide/api_parameters_doc">
    API 联调环境说明与请求地址一览。
  </Card>

  <Card title="接口说明与公共规则" icon="book-open" iconColor="#2E7D32" iconBackground="#E8F5E9" href="/docs/zh/guide/api_description_guide">
    HaiPay 接口说明与公共请求/返回规则。
  </Card>

  <Card title="RSA 在线密钥生成工具" icon="key-round" iconColor="#E65100" iconBackground="#FFF3E0" href="/docs/zh/rsa/rsa-generate">
    纯 JS 实现的 RSA 密钥对在线生成工具，不会与服务器交互。
  </Card>
</Columns>

***

<div class="doc-footer">
  最后更新：2026年9月 · HaiPay 技术文档团队

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