获取签名

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://testnet.bitrue.com/otc"
APP_KEY = "Ae4fHfWZwFFi9q2l"
APP_SECRET = "9z5zpp0itm1ygx"
APP_PASSPHRASE = "SDFxw0EDj33sdZedd"

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:sdfSEEC293Bd1ZZ5VO98z8Gf/z39m6RjSxhdOV9nW+8+6j9j+ZV2OOfXWwyChSJ3+xzRZulDyi:OTg3NjVmZWRjYmE0MzIxMA==
timestamp:1587426337

3.2 用户UID处理(必实现)

UID为交易所侧用户唯一标识,需经过两次加密后作为APP_UID传递,全程禁止明文传输:

  1. 第一次加密:交易所侧自行实现,推荐sha1(user_id + salt),结果长度不超过20字符
  2. 第二次加密:使用Legend提供的官方加密算法(TBClient.encryptUID)再次加密,作为最终APP_UID。
  3. 核心要求:APP_UID必须保证用户唯一、永不重复,作为OTC平台用户唯一标识。

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 = "交易所原始用户ID";
String salt = "交易所自定义盐值";
String encrypted_user_id = sha1(user_id + salt); // 长度≤20字符

// 第二步:使用Legend官方算法二次加密
String APP_UID = TBClient.encryptUID(encrypted_user_id);

输出示例

Original UID: 42fa5da9de51f2caa39217c4b8bece42ff6029cf
Encrypted UID: Bd1ZZ5VO98z8Gf/z39m6RjSxhdOV9nW+8+6j9j+ZV2OOfXWwyChSJ3+xzRZulDyi:OTg3NjVmZWRjYmE0MzIxMA==

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 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 String CIPHER_VI = "98765fedcba43210";
    private static final int CIPHER_KEY_LEN = 16; //128 bits

    /**
     * 生成请求签名(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)
     * @param UID 交易所侧一次加密后的UID
     * @return 最终APP_UID
     */
    public static String encryptUID(String UID){
        String encrytedUID = TBClient.encrypt(TBClient.APP_SECRET, TBClient.CIPHER_VI, UID);
        return TBClient.APP_PASSPHRASE + encrytedUID;
    }

    /**
     * AES加密底层方法
     * @param key 加密密钥(自动补0/截断至16位)
     * @param iv 初始化向量
     * @param data 待加密数据
     * @return 加密后Base64串 + : + IV的Base64串
     */
    public static String encrypt(String key, String iv, 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);
            }
            IvParameterSpec initVector = new IvParameterSpec(iv.getBytes("UTF-8"));
            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.getBytes("UTF-8"));
            return base64_EncryptedData + ":" + base64_IV;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    // 测试方法
    public static void testUID () {
        String uid = "d58e3582afa99040e27b92b13c8f2280";
        System.out.println("Original UID: " + uid);
        System.out.println("Encrypted 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(长度≤20)
     */
    private static String getUID(){
        // 此处为示例,需替换为交易所实际业务逻辑
        return "d58e3582afa99040e27b92b13c8f2280";
    }

    // 测试参数输出
    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. 唯一性保障:APP_UID需与交易所用户一一对应,永不重复,避免用户标识冲突;
  5. 参数一致性:signature与timestamp必须实时生成,一一对应,禁止复用、篡改时间戳;
  6. 异常处理:对加密、签名过程中的异常(如算法异常、密钥错误)做捕获处理,避免服务异常。

8. 技术支持

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