获取签名

本页是请求签名与用户 ID 加密的权威说明;快速入门页与 recipe 均为本页内容的摘要并回链至此。

Do / Don't 速查(UID 与 APP_UID 的定义见 §3.2):

✅ 应该❌ 禁止
服务端 API 的 uid 入参传第一轮 UID把 APP_UID——或任何含 : 的值——当作服务端 uid 传入
每次加密都用 CSPRNG 生成全新的 16 字节随机 IV硬编码或复用 IV
只通过 TBClient.encryptUID(UID) 生成 APP_UID直接调用内部 encrypt() 并拿其裸 base64:base64 输出使用
明文 user_id 只留在自己服务端把明文 user_id 传给 Legend 或传入前端页面
APP_SECRET、APP_PASSPHRASE 仅服务端存储写进前端代码或公开配置

1. 安全参数说明

1.1 核心参数列表

参数名说明
APP_ID交易所指定标识
APP_URL交易所法币交易页对外服务URL,由交易所指定
APP_KEY通讯验证密钥
APP_SECRET核心加密密钥,仅服务端存储
APP_PASSPHRASE密码短语,仅服务端存储

1.2 安全要求

  1. 所有安全参数需妥善保存,严禁对外泄露;
  2. APP_SECRET、APP_PASSPHRASE仅限服务器端存储,禁止传递至前端或公开;
  3. 测试环境与生产环境参数隔离,不可交叉使用。

1.3 示例安全参数

APP_ID = "Bitrue"
APP_URL = "https://www.bitrue.com/"
APP_KEY = "Aw4fHfWZwFFi9q2l"
APP_SECRET = "5z5znf230itm1ygx"
APP_PASSPHRASE = "sdfSEEC293"

本套示例参数与本页所有示例、代码块一致,下列计算结果均可原样复现。

2. 安全参数获取流程

2.1 测试环境

直接联系Legend技术支持团队获取测试环境专属参数。

2.2 生产环境

服务正式上线前,向Legend技术支持团队申请,申请时需提供以下信息(均为交易所侧信息):

  1. 技术/项目负责人的公司邮箱(用于接收参数);
  2. 交易所正式官网URL;
  3. API回调地址(含trade-notify、trade-execute、balance、balances等);
  4. API回调使用的API Key;
  5. Legend服务在交易所的落地页(landing page)URL。

3. 后端核心开发要求

3.1 电子签名生成(必实现)

  1. 基于Legend提供的OTCHandler.java加密算法代码开发;
  2. 每次调用JSSDK前,必须计算生成signature(签名)和timestamp(时间戳,秒级时间戳),如浏览器刷新当前页面,就需要重新生成签名;
  3. 签名生成逻辑:基于timestamp + APP_URL通过HmacSHA256算法结合APP_SECRET计算,结果做Base64编码。

执行示例:

bash-3.2$ java OTCHandler
>>> Exporting these params to HTML: 
APP_ID:Bitrue
signature:uTHvD4z3r+o1se/CVxqvp1E7TU80F/ers7y9wo6Kp1w=
APP_URL:https://www.bitrue.com/
APP_KEY:Aw4fHfWZwFFi9q2l
APP_UID:sdfSEEC293K7cYPMJRF07dBoinizp2pKRGiuQlfwzrfh8wZ9qAbGx3+OAMenykBenxH1lKgFOX:M4QXifPj+nsIumRqthAogw==
timestamp:1587426337

(每次运行的 APP_UID 都不同——每次加密使用全新随机 IV,见 §5.1。)

3.2 用户UID处理(必实现)

Legend 链路中你的用户标识有两种加密形态,先后产生但不可互换——每种形态只有一个合法去向:

名称如何产生用在哪里
user_id你方用户表内部主键仅限你方系统内部——不得以明文传给 Legend 或传入前端页面
UID(第一轮值)你方对 user_id 做不可逆哈希,推荐 sha1(user_id + salt)所有服务端 API 的 uid 入参。Legend 原样落库该字符串作为用户外部 ID,同一用户的后续服务端调用必须原样重传
APP_UID(第二轮产物)TBClient.encryptUID(UID) = APP_PASSPHRASE + base64(密文) + ":" + base64(IV)仅限前端 JSSDK(app-uid 属性)——仅在 SDK 授权请求中提交一次
⚠️

严禁把 APP_UID——或任何含 : 的值——当作服务端 API 的 uid 入参。 : 是第二轮格式中密文段与 IV 段的结构分隔符;一旦进入服务端 uid,落库的用户 ID 即为畸形,后续账户查询将失败。服务端 API 只接受第一轮 UID。

要求:

  1. 第一轮(交易所侧,不可逆):推荐 sha1(user_id + salt)——40 位十六进制字符串;无论用哪种算法,结果不得超过 100 字符(Legend 落库列为 VARCHAR(100));
  2. 第二轮:用 Legend 官方算法(TBClient.encryptUID,见 §5)对 UID 再次加密,得到最终 APP_UID;
  3. 核心要求:唯一性落在第一轮 UID 上——每个用户一个、用户间绝不共用、同一用户各次调用保持一致,因为 Legend 落库并据此匹配的正是 UID。APP_UID 无法承担这一角色:每次加密都使用全新随机 IV,同一个 UID 每次都会算出不同的 APP_UID(见 §5.1)——它的职责只是能被解密还原为该用户的 UID。

APP_UID 的消费方式:它只被解密一次——JSSDK 调用 Legend 授权端点时。Legend 剥离 APP_PASSPHRASE 前缀、从值本身(: 之后的段)取出 IV、用你方 APP_SECRET 解密——还原出 UID,再据此匹配(或建档)用户的外部 ID。授权之后的所有请求——无论前端还是服务端——一律凭 token 或明文 UID 认证,不再携带 APP_UID。

flowchart TD
    A["user_id——交易所内部主键,全程不明文传输"] --> B["UID——第一轮值:sha1(user_id + salt),交易所后端计算"]
    B --> C["APP_UID——第二轮产物:TBClient.encryptUID(UID),每次调用生成全新随机 IV"]
    B --> S["Legend 服务端 API——入参 uid = UID,原样落库为用户外部 ID"]
    C --> AUTH["Legend 授权端点——前端 JSSDK 仅提交一次 APP_UID;Legend 剥 APP_PASSPHRASE、从消息取 IV、解密还原 UID"]
    AUTH --> S
    AUTH --> T["签发访问 token——此后前端所有请求凭 token 认证,不再携带 APP_UID"]

3.3 时间戳timestamp(必实现)

与电子签名配套生成,为秒级Unix时间戳,需与signature生成时间保持一致,不可篡改。

3.4 用户Email

需由前端在页面中配置为APP_EMAIL变量,后端需保证传递至前端的Email为用户真实有效信息。

4. 前后端参数交互

后端完成上述计算后,需将以下参数传递给前端页面,用于JSSDK调用:

APP_ID、APP_KEY、APP_URL、APP_UID、signature、timestamp

5. 加密算法实现(Java)

5.1 UID加密完整流程(可复现示例)

一条端到端自洽的数据链——以下每一步都可复现,便于逐字段核对自己的实现:

// 第一步:第一轮,交易所侧(不可逆)
String user_id = "1";                     // 交易所内部主键
String salt = "sfdsfsXXDD23901";          // 交易所自定义盐值
String UID = sha1(user_id + salt);
// UID = "42fa5da9de51f2caa39217c4b8bece42ff6029cf"
//   可自行验证:printf '1sfdsfsXXDD23901' | sha1sum
//   → 这个字符串原样就是服务端 API 的 uid 入参

// 第二步:第二轮,Legend 官方算法
String APP_UID = TBClient.encryptUID(UID);

一次真实运行的输出(使用 §1.3 示例凭证):

Original UID: 42fa5da9de51f2caa39217c4b8bece42ff6029cf
APP_UID: sdfSEEC293K7cYPMJRF07dBoinizp2pKRGiuQlfwzrfh8wZ9qAbGx3+OAMenykBenxH1lKgFOX:M4QXifPj+nsIumRqthAogw==

你的输出每次调用在两段上都会不同——IV 每次全新随机(见 §5.2)。验证自己的输出:

  1. 对 : 之后的段做 Base64 解码——即你的 IV(M4QXifPj+nsIumRqthAogw== 解码为十六进制 33841789f3e3fa7b08ba646ab6102883);
  2. 用你的 APP_SECRET 和该 IV 对第一段做 AES-128-CBC 解密;
  3. 结果必须等于你的 UID——本例为 42fa5da9de51f2caa39217c4b8bece42ff6029cf。

5.2 核心算法代码

5.2.1 TBClient.java(核心加密/签名工具类)

/**
 * Legend Gateway Java 客户端:包含签名生成、UID加密核心算法
 */
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Mac;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import javax.xml.bind.DatatypeConverter;
import java.util.Base64;
import java.time.Instant;
import java.util.*;

public class TBClient {
    // 需替换为实际获取的安全参数
    protected static final String APP_ID = "Bitrue";
    protected static final String APP_URL = "https://www.bitrue.com/";
    protected static final String APP_KEY = "Aw4fHfWZwFFi9q2l";
    protected static final String APP_SECRET = "5z5znf230itm1ygx";
    protected static final String APP_PASSPHRASE = "sdfSEEC293";

    private static final String CIPHER_NAME = "AES/CBC/PKCS5PADDING";
    private static final int CIPHER_KEY_LEN = 16; //128 bits
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
    // 不使用固定 IV:每次加密生成全新的密码学随机 16 字节 IV。
    // IV 随密文传输(':' 之后的段)——无需保密,
    // 但绝不复用、不硬编码:固定 IV 会让加密变成确定性的,
    // 从而泄露两个 APP_UID 是否属于同一用户。

    /**
     * 生成请求签名(HmacSHA256)
     * @param unixTime 秒级时间戳
     * @return Base64编码后的签名
     */
    public static String sign(long unixTime){
        try {
            String message = Long.toString(unixTime) + TBClient.APP_URL;
            Mac hasher = Mac.getInstance("HmacSHA256");
            hasher.init(new SecretKeySpec(TBClient.APP_SECRET.getBytes(), "HmacSHA256"));
            byte[] hash = hasher.doFinal(message.getBytes());
            return DatatypeConverter.printBase64Binary(hash);
        } catch (NoSuchAlgorithmException | InvalidKeyException ex) {
            ex.printStackTrace();
        }
        return null;
    }

    /**
     * UID 第二轮加密(AES+CBC+PKCS5PADDING)——生成 APP_UID 的唯一受支持方式。
     * @param UID 交易所侧一次加密后的UID
     * @return 最终 APP_UID——仅用于前端 JSSDK 的 app-uid 属性;
     *         严禁作为服务端 API 的 uid 入参
     */
    public static String encryptUID(String UID){
        return TBClient.APP_PASSPHRASE + TBClient.encrypt(TBClient.APP_SECRET, UID);
    }

    /**
     * AES加密底层方法——内部方法,禁止直接调用。
     * 其裸输出(Base64(密文):Base64(IV),无 APP_PASSPHRASE 前缀)
     * 不是 APP_UID,不得传输到任何地方;生成 APP_UID 一律走 encryptUID()。
     * IV 在方法内部每次调用全新生成。
     * @param key 加密密钥(自动补0/截断至16位)
     * @param data 待加密数据
     * @return 加密后Base64串 + : + IV的Base64串
     */
    private static String encrypt(String key, String data) {
        try {
            if (key.length() < CIPHER_KEY_LEN) {
                key = String.format("%-" + CIPHER_KEY_LEN + "s", key).replace(' ', '0');
            } else if (key.length() > CIPHER_KEY_LEN) {
                key = key.substring(0, CIPHER_KEY_LEN);
            }
            byte[] iv = new byte[CIPHER_KEY_LEN];
            SECURE_RANDOM.nextBytes(iv);
            IvParameterSpec initVector = new IvParameterSpec(iv);
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance(CIPHER_NAME);
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, initVector);
            byte[] encryptedData = cipher.doFinal((data.getBytes()));
            String base64_EncryptedData = Base64.getEncoder().encodeToString(encryptedData);
            String base64_IV = Base64.getEncoder().encodeToString(iv);
            return base64_EncryptedData + ":" + base64_IV;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    // 测试方法
    public static void testUID () {
        String uid = "42fa5da9de51f2caa39217c4b8bece42ff6029cf"; // 可复现示例,见 §5.1
        System.out.println("Original UID: " + uid);
        System.out.println("APP_UID: " + TBClient.encryptUID(uid));
    }
    public static void testSig () {
        long unixTime = Instant.now().getEpochSecond();
        System.out.println("Signature: " + TBClient.sign(unixTime));
    }
    public static void main (String[] args) {
        TBClient.testUID();
        TBClient.testSig();
    }
}

5.2.2 OTCHandler.java(参数组装工具类)

/**
 * 前端参数组装:生成JSSDK所需所有参数
 */
import java.util.*;
import java.time.Instant;

public class OTCHandler extends TBClient{
    /**
     * 组装前端所需所有参数
     * @return 参数字典
     */
    private static Map<String, String> htmlParams(){
        Map<String, String> html_params = new HashMap<>();
        long unixTime = Instant.now().getEpochSecond();
        html_params.put("timestamp", Long.toString(unixTime));
        html_params.put("signature", sign(unixTime));
        html_params.put("APP_ID", APP_ID);
        html_params.put("APP_URL", APP_URL);
        html_params.put("APP_KEY", APP_KEY);
        html_params.put("APP_UID", encryptUID(getUID()));
        return html_params;
    }

    /**
     * 需交易所自行实现:获取一次加密后的用户UID
     * @return 交易所侧第一轮 UID——sha1 为 40 位十六进制;硬性上限 100 字符。
     *         该值同时就是服务端 API 的 uid 入参
     */
    private static String getUID(){
        // 此处为示例,需替换为交易所实际业务逻辑
        return "42fa5da9de51f2caa39217c4b8bece42ff6029cf";
    }

    // 测试参数输出
    public static void main (String[] args) {
        Map<String, String> htmlParams = OTCHandler.htmlParams();
        System.out.println(">>> Exporting these params to HTML: ");
        htmlParams.forEach((k, v) -> System.out.println(k + ":" + v));
    }
}

6. 配置参数说明

Legend SDK可配置以下参数,需联系Legend商务团队申请修改:

  1. 结算方式:默认Legend与合作伙伴结算,合作伙伴与客户直接结算;
  2. 最小交易额:需调用/app/pairs接口查询获取;
  3. 支持交易对:默认支持USDT、ETH、BTC及所有Legend支持的法币交易对。

7. 开发核心规范

  1. 环境隔离:测试/生产环境参数独立,禁止交叉使用;
  2. 密钥安全:APP_SECRET、APP_PASSPHRASE仅可在服务器端存储,禁止写入前端代码、配置文件公开化;
  3. 算法规范:必须使用Legend提供的官方加密/签名代码,禁止自行修改算法逻辑;
  4. 唯一性保障:与交易所用户一一对应的是第一轮 UID——绝不复用、绝不跨用户共用——以避免用户标识冲突。APP_UID 不能充当该标识:它内嵌每次加密随机生成的 IV,即使同一用户,每次计算结果也不同(见 §3.2);
  5. 参数一致性:signature与timestamp必须实时生成,一一对应,禁止复用、篡改时间戳;
  6. 异常处理:对加密、签名过程中的异常(如算法异常、密钥错误)做捕获处理,避免服务异常;
  7. 形态纪律:服务端 API 的 uid 只传第一轮 UID;APP_UID 仅用于前端 JSSDK——两者不可混用(见 §3.2)。

8. 技术支持

集成过程中遇到问题,直接联系Legend技术支持团队。