跳至主要內容

签名授权

大约 6 分钟

数字签名概述

数字签名认证是所有 API 交易(包括代收、代付以及查询操作(查询状态和查询余额))的关键安全机制。该流程通过加密验证确保请求的完整性、真实性和不可否认性。

适用于所有需签名的 API

每个代收、代付和查询请求都必须包含 X-TIMESTAMPX-SIGNATUREX-PARTNER-ID 请求头。本页的签名规则与代收请求参考文档保持一致。

签名生成流程

准备签名数据

收集以下用于生成签名的组成部分:

  • X-TIMESTAMP:ISO 8601 格式的时间戳(例如 2024-12-30T18:30:36+07:00
  • Merchant Secret:分配给您的商户密钥
  • Request Body:JSON 请求体(将被压缩)
  • Merchant Private Key:用于签名的 RSA 私钥(PKCS#8,Base64)

签名参数

压缩请求体

移除 JSON 请求体中所有不必要的空白和格式,以确保签名生成的一致性。

{
    "area": 10,
    "callbackUrl": "https://docs.smilepayz.com/en/",
    "merchant": {
        "merchantId": "20019"
    },
    "money": {
        "amount": 10000,
        "currency": "IDR"
    },
    "orderNo": "20019c5b63c4b-e34a-4855-9b20-d4b",
    "paymentMethod": "QRIS",
    "purpose": "Purpose For Transaction from Java SDK"
}

签名后请勿修改请求体

请使用将在 HTTP 请求体中发送的、完全一致的压缩 JSON 字符串来生成签名。签名后的任何改动都会导致验证失败。

拼接待签名字符串

使用竖线(|)分隔符拼接各组成部分,构造待签名字符串:

X-TIMESTAMP + "|" + merchant_secret + "|" + minify(requestBody)

待签名字符串示例

2024-12-30T18:30:36+07:00|95b57c46b8c2e068982be23fb669a80612cad68e6ce6ba4f5af9ec20d23bb274|{"area":10,"callbackUrl":"https://docs.smilepayz.com/en/","merchant":{"merchantId":"20019"},"money":{"amount":10000,"currency":"IDR"},"orderNo":"20019c5b63c4b-e34a-4855-9b20-d4b","paymentMethod":"QRIS","purpose":"Purpose For Transaction from Java SDK"}

生成数字签名

使用您的 RSA 私钥对待签名字符串应用 SHA256withRSA 算法。

加密公式

SHA256withRSA(stringToSign, merchantPrivateKey)
RSA 私钥示例(PKCS#8,Base64)
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1d8Zaurk/A0WB1X7CrW+SqsVM4v2ZMGTdqWEQOAKQyY/DZEmgSm7whUMLYfqeKxzzAd3lZHvviyO/V/Ht2i8axtiA5tyDa485IgBy+jO9UHxzI+9CRxnIY5jjDAqAgEvRa0HZEfYHLGlH3IQ4A2a7g8Wm3yqn2prIrgY21nO4+33GuBR0qCpGxth0kV7/RSdhWv3Ff1zkb7yUYn9zfuag9stLf1nw+Ui7hjXL+xGiNRYsDCHOLzKynZxk1cBfqyPY5YMTIjQik4cI+YfM4BMN8bmTB1y3z9sdfbHVTI/PD/fbDzvfK7S/XJvY2bj1TwNeYCHcd5Dx6MYiO12rfA5XAgMBAAECggEASRpMJMat042c1ZXM793wYXQ78SJMKQcOyyDtwhveJLeeG4duBY2WrXvxq0c4L7mMevSYkE1vg+tYZj+mbTwE19Oc6iiWUrbkxo5FAgx7BVvEFpo2S0vbPhepQmXaYTPN1zpPF4QdyXwBmXpjJTFxlD6cRAII6/+rdQZt6G9xxOWUq6bbENNUc6y+N+97oPPwrzLZGYADfusM735Mlx3j8xTGLSHTh2d8e5epwKcSW+ZvBbiM30ZeO+M5bh6zgZrHpPQNJAOsEDuVk5vjuC5O+i81YwP702GAE50zI9Ysr4QMS3QQGe2jwpJAmMjvO6/MRv/6De83NJ6Y70+00OONcQKBgQDtP+sQOyKRErCN9ePmu6FBfzMZcTC5vXQpA0kW7vUMKyQR537appYcOb5DPJDgUqqyNcr8vIqadR6OW0thkfRyb+L/elOyS/XnVbFc6RA9DW/KV4xm68LUX9oRqfgy5YOESEEA2igEh8IdqfkIRCZ3g7xrQUmb3gq0BM2/hnxyHwKBgQDDz0Slbw2Pv820CSheZi8HQf3sEFe0JDHKo2KAHX9rTvFEF3xsT+UZh6pcHUcesYlVlJM2Ciejq4dmiOnvGIKtkqtcLfioVbPrDP2HlDtIT3mSC7sL6VdWLxYz/l0EVjsR2KMVwXD42jcjz3e5bDIi98Z8G4Bx6cu6pwlBtfMMyQKBgHJhWoacu/GNPSlz3sgitX/KP2yIsaEawaH0P4ya7/FJ6z3mibYIkl5RSHVKAd7ke/8nCBIJJBmLVYv0X8dOgreZUasx4qAXFxrMqZHNm1KT4819n+cOywNwosXZWBvRozJnU0+B3cExYljC1ZkyogkErhZXh5Fp32ci2b74q46pAoGANWyixSRkpdjmLf3lpsPohulpd3QKCdtqPmiPonbp4gRa7YIe74po7qtGPvW6BtTXrKLp+2+P2yccvUwTz6l3VhZTbMYaNwuTGEmmDszR0+vjNoa+1QQoURpty2fWRy2j1j2uWWw6/5rCOqILf2rWzxWdcRUr2Wi5rkaOzv/uvYkCgYEAlFV6huNhi1NIIg5gkTS0kmxnIUWu8XIT9IgZWMrhR7TvImVISoormE9LdISgMBz+J5YJxhAZbxVxlUJrsl+HVcS81gmUkeoIpTA+iKUzbD9gAPjEdqVylzN8A9uNIkkb5Cf4BWYfEyMaLfBuiHDlKDcBjz4cJbcom3RVGacghKc=
生成的签名示例
BD+qFc0/JOl1grDKSs0zMumPeswzLN/tVj/bms9AYZdVwtzZ3RQJ2gdCxtMeF/fYVTLOZZVLiKnFm0KDjoChl9Kz+6VpgFCMCQt99iUxoJxLtCvPhlCdD1rHVNfrNWKt4mLSNdQWmh//hlwP4AJqidMUKCTDMDm2ivgvQqMIFqdadITAreevKQ+VZs7ukZMzo5BgYfdhCiorAqAb/EUN1/O0pDt8Uv+xcL4QNxxu1dj10UDRbHaox4Qv1tfXbRoiisNa9xAlVkd3l19FW/Mv09jkDEKL+x5uIx3WjGY81NyVW6yxx5jEbsZbdl5KfqA6SwKpc3IOVLwR4J4Q0jaFGA==

代码示例

import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

public class SignatureExample {

    /** Build X-SIGNATURE for Smilepayz APIs. */
    public static String sign(
            String timestamp,
            String merchantSecret,
            String minifiedRequestBody,
            String privateKeyBase64) throws Exception {
        String stringToSign = timestamp + "|" + merchantSecret + "|" + minifiedRequestBody;

        byte[] keyBytes = Base64.getDecoder().decode(privateKeyBase64);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);

        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(stringToSign.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(signature.sign());
    }
}

在 HTTP 请求头中携带签名

将生成的签名连同时间戳和商户 ID 一起添加到 HTTP 请求头中:

Content-Type: application/json
X-TIMESTAMP: 2024-12-30T18:30:36+07:00
X-SIGNATURE: BD+qFc0/JOl1grDKSs0zMumPeswzLN/tVj/bms9AYZdVwtzZ3RQJ2gdCxtMeF/fYVTLOZZVLiKnFm0KDjoChl9Kz+6VpgFCMCQt99iUxoJxLtCvPhlCdD1rHVNfrNWKt4mLSNdQWmh//hlwP4AJqidMUKCTDMDm2ivgvQqMIFqdadITAreevKQ+VZs7ukZMzo5BgYfdhCiorAqAb/EUN1/O0pDt8Uv+xcL4QNxxu1dj10UDRbHaox4Qv1tfXbRoiisNa9xAlVkd3l19FW/Mv09jkDEKL+x5uIx3WjGY81NyVW6yxx5jEbsZbdl5KfqA6SwKpc3IOVLwR4J4Q0jaFGA==
X-PARTNER-ID: 20019

cURL 示例(沙箱代收)

curl -X POST 'https://sandbox-gateway.smilepayz.com/v2.0/transaction/pay-in' \
  -H 'Content-Type: application/json' \
  -H 'X-TIMESTAMP: 2024-12-30T18:30:36+07:00' \
  -H 'X-SIGNATURE: BD+qFc0/JOl1grDKSs0zMumPeswzLN/tVj/bms9AYZdVwtzZ3RQJ2gdCxtMeF/fYVTLOZZVLiKnFm0KDjoChl9Kz+6VpgFCMCQt99iUxoJxLtCvPhlCdD1rHVNfrNWKt4mLSNdQWmh//hlwP4AJqidMUKCTDMDm2ivgvQqMIFqdadITAreevKQ+VZs7ukZMzo5BgYfdhCiorAqAb/EUN1/O0pDt8Uv+xcL4QNxxu1dj10UDRbHaox4Qv1tfXbRoiisNa9xAlVkd3l19FW/Mv09jkDEKL+x5uIx3WjGY81NyVW6yxx5jEbsZbdl5KfqA6SwKpc3IOVLwR4J4Q0jaFGA==' \
  -H 'X-PARTNER-ID: 20019' \
  -d '{"area":10,"callbackUrl":"https://docs.smilepayz.com/en/","merchant":{"merchantId":"20019"},"money":{"amount":10000,"currency":"IDR"},"orderNo":"20019c5b63c4b-e34a-4855-9b20-d4b","paymentMethod":"QRIS","purpose":"Purpose For Transaction from Java SDK"}'

沙箱基础 URL

沙箱:https://sandbox-gateway.smilepayz.com · 生产:https://gateway.smilepayz.com。详见环境 URL

签名验证

使用下方的 RSA 公钥来验证回调,或在集成测试期间校验签名。

用于验证的 RSA 公钥
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtXfGWrq5PwNFgdV+wq1vkqrFTOL9mTBk3alhEDgCkMmPw2RJoEpu8IVDC2H6nisc8wHd5WR774sjv1fx7dovGsbYgObcg2uPOSIAcvozvVB8cyPvQkcZyGOY4wwKgIBL0WtB2RH2ByxpR9yEOANmu4PFpt8qp9qayK4GNtZzuPt9xrgUdKgqRsbYdJFe/0UnYVr9xX9c5G+8lGJ/c37moPbLS39Z8PlIu4Y1y/sRojUWLAwhzi8ysp2cZNXAX6sj2OWDEyI0IpOHCPmHzOATDfG5kwdct8/bHX2x1UyPzw/32w873yu0v1yb2Nm49U8DXmAh3HeQ8ejGIjtdq3wOVwIDAQAB

开发工具

JSON 压缩工具

工具URL用法
JSON Formatterhttps://jsonformatter.org/json-minifyopen in new window选择 "Minify JSON" 选项

安全最佳实践

密钥管理

  • 安全存储:将私钥存储在安全、加密的存储介质中
  • 密钥轮换:定期轮换 RSA 密钥对
  • 访问控制:仅授权人员可访问私钥
  • 备份:妥善保管私钥的安全备份

实现指南

时间戳窗口

请确保 X-TIMESTAMP 与 Smilepayz 服务器时间相差在 ±5 分钟 以内。请使用 NTP 服务器同步您的系统时钟。

请求完整性

签名生成后切勿修改请求体。若任何字段发生变化,请重新生成签名。

  • 错误处理:为签名验证失败实现完善的错误处理
  • 日志记录:记录签名生成尝试以供审计(切勿记录商户密钥或私钥)

常见问题与解决方案

  • 签名不匹配:检查所有组成部分的顺序和格式是否正确
  • 时间戳问题:确保系统时钟已与 NTP 服务器同步
  • 密钥格式:检查 RSA 密钥的格式与编码(PKCS#8 私钥,Base64)
  • 编码问题:确保所有字符串组成部分均使用正确的 UTF-8 编码

合规与标准

加密标准

  • 算法:SHA256withRSA(SHA-256 哈希结合 RSA 签名)
  • 密钥长度:建议使用至少 2048 位的 RSA 密钥
  • 编码:签名输出采用 Base64 编码
  • 格式:私钥采用 PKCS#8 格式

审计要求

  • 签名日志:保留所有签名生成与验证的日志
  • 密钥使用:跟踪密钥的使用情况与轮换计划
  • 访问日志:监控对加密材料的访问
  • 合规报告:生成满足监管要求的合规报告