API Reference
English

客户端密封结果

版本说明

  • Web SDK 5.1.0及以上
  • Android SDK 5.1.0及以上
  • iOS SDK 5.1.0及以上

密封客户端结果说明

通常客户接入设备指纹应用到业务场景分为两部分,第一步客户的应用需要集成 SDK 先采集设备信息,由同盾返回设备信息黑盒。第二步客户端应用获取到黑盒传递给风控中台,由风控中台通过黑盒调设备指纹服务获取设备ID 及设备信息进行决策。

密封客户端结果,是在客户应用接入SDK 的时候就可以选择是否直接返回加密的设备信息,如果确认由前端应用获取,同盾 SDK 会直接返回匿名ID、黑盒及加密的设备信息,由客户的风控中台通过密钥解密设备信息,进行决策,省略了风控中台与设备指纹服务交互的步骤。

优点:

  • 减少了端与端之前交互的延迟,解密信息直接在您的服务端应用,无需调 API 接口
  • 提高了数据的安全性,黑产无法恶意读取客户端密封结果

配置密封客户端结果

如果您想使用密封客户端结果,需要完成以下步骤

  1. 在客户平台-开发者-密钥&SDK 里创建加密密钥并启用
  2. 前端初始化 SDK 会获取到密封结果,将密封结果发送到服务端
  3. 您的服务端需要接收密封结果并且通过密钥解密

创建加密密钥

导航到客户平台-开发者-密钥&SDK-加密密钥


  1. 点击创建加密密钥
  2. 输入密钥名称
  3. 选择前端加密的 appKey 绑定,支持绑定多个
  4. 选择密钥状态
  5. 点击保存

复制 加密密钥 提供给服务端使用


服务端解密密封结果

  • 不要在客户端解密密封结果
  • 解密功能设计仅在后端进行。如果尝试在客户端解密密封结果,意味着任何人都可以看到加密密钥。这会泄漏敏感信息同时引入新的黑产攻击

前端发送客户端密封结果到服务端后,需要使用上面生成的加密密钥进行解密。

解密密封结果代码参考:

import base64
import json
import zstandard as zstd
import io
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend


def aes_decrypt(source, key_str):
    try:
        # Base64 解码
        data = base64.b64decode(source)
        key_bytes = base64.b64decode(key_str)

        # 验证最小长度 (IV12 + TAG16 + Zstd最小头5)
        if len(data) < 12 + 16 + 5:
            raise ValueError("Data too short for valid payload")

        # 提取各部分
        iv = data[:12]
        auth_tag = data[-16:]
        ciphertext = data[12:-16]

        # AES-GCM 解密
        cipher = Cipher(
            algorithms.AES(key_bytes),
            modes.GCM(iv, auth_tag),
            backend=default_backend()
        )
        decryptor = cipher.decryptor()
        compressed = decryptor.update(ciphertext) + decryptor.finalize()

        # 验证Zstd帧头
        if compressed[:4] != b'\x28\xb5\x2f\xfd':  # Magic Number
            raise ValueError("Invalid Zstd header")

        # 流式解压适配分块压缩
        dctx = zstd.ZstdDecompressor()
        with dctx.stream_reader(io.BytesIO(compressed)) as reader:
            decompressed = reader.read()  # 自动处理未知大小

        # 返回JSON字符串
        device_info = decompressed.decode('utf-8')
        return json.dumps(device_info)

    except Exception as e:
        print(f"AES256-ZSTD decrypt fail! {e}")
        return None
# encryptionKey
keyStr = "your encryptionKey"
# sealedResult
source = "your sealedResult"
result = aes_decrypt(source, keyStr)
print(result)
package cn.tongdun.brick.common.util.tdcipher.Utils;

import cn.tongdun.brick.common.util.JsonUtil;
import com.github.luben.zstd.ZstdInputStream;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
@Slf4j
public class test {

    // 测试用例
    public static void main(String[] args) throws Exception {
        // encryptionKey
        String keyStr = "your encryptionKey";
        // sealedResult
        String encryptSource = "your sealedResult";
        String aesDecrypt = aesDecrypt(encryptSource, keyStr);
        System.out.println(aesDecrypt);
    }


    /**
     * AES256GCM解密
     *
     * @param source 后端返回加密后数据
     * @param keyStr 平台生成加密密钥
     * @return 设备详情Json
     */
    public static String aesDecrypt(String source, String keyStr) {
        try {
            // 后端返回加密后数据
            byte[] data = Base64.getDecoder().decode(source);
            /*
                生成密钥
             */
            byte[] keyBytes = Base64.getDecoder().decode(keyStr);
            SecretKey key = new SecretKeySpec(keyBytes, "AES");
            /*
                提取加密后数据中iv和加密数据
             */
            if (data.length < 12) {
                throw new IllegalArgumentException();
            }
            byte[] iv = Arrays.copyOfRange(data, 0, 12);
            byte[] ciphertext = Arrays.copyOfRange(data, 12, data.length);
            /*
                解密
             */
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
            cipher.init(Cipher.DECRYPT_MODE, key, gcmSpec);
            byte[] dataBeforeEncryption = cipher.doFinal(ciphertext);
            /*
                解压数据
             */
            byte[] unzipData;
            try (ByteArrayInputStream bis = new ByteArrayInputStream(dataBeforeEncryption);
                 ZstdInputStream zis = new ZstdInputStream(bis);
                 ByteArrayOutputStream bos = new ByteArrayOutputStream()) {

                byte[] buffer = new byte[4096];
                int len;
                while ((len = zis.read(buffer)) > 0) {
                    bos.write(buffer, 0, len);
                }
                unzipData = bos.toByteArray();
            }
            /*
                返回结果
             */
            String deviceInfo = new String(unzipData, StandardCharsets.UTF_8);
            return JsonUtil.writeValueAsString(deviceInfo);
        } catch (Exception e) {
            log.error("AES256 decrypt fail!", e);
            return null;
        }
    }

// 添加 Maven 依赖:
// <dependency>
//     <groupId>com.github.luben</groupId>
//     <artifactId>zstd-jni</artifactId>
//     <version>1.5.5-4</version>
// </dependency>
}
package main

import (
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"github.com/klauspost/compress/zstd"
)

func main() {
	// encryptionKey
	var keyStr = "your encryptionKey"
	// sealedResult
	var encryptSource = "your sealedResult"
	fmt.Println(AESDecrypt(encryptSource, keyStr))
}
func AESDecrypt(source, keyStr string) (string, error) {
	// Base64 解码
	data, _ := base64.StdEncoding.DecodeString(source)
	keyBytes, _ := base64.StdEncoding.DecodeString(keyStr)

	// 提取 IV 和密文
	if len(data) < 12 {
		return "", fmt.Errorf("invalid data length")
	}
	iv := data[:12]
	ciphertext := data[12:]

	// AES-GCM 解密
	block, _ := aes.NewCipher(keyBytes)
	gcm, _ := cipher.NewGCM(block)
	compressed, err := gcm.Open(nil, iv, ciphertext, nil)
	if err != nil {
		return "", err
	}

	// Zstandard 解压缩
	decoder, _ := zstd.NewReader(nil)
	defer decoder.Close()
	decompressed, err := decoder.DecodeAll(compressed, nil)
	if err != nil {
		return "", err
	}

	// 返回 JSON 字符串
	deviceInfo := string(decompressed)
	result, _ := json.Marshal(deviceInfo)
	return string(result), nil
}

// 依赖: go get github.com/klauspost/compress/zstd
const crypto = require('crypto');
const zstd = require('@bokuweb/zstd-wasm');

// 定义常量
const MIN_DATA_LENGTH = 28; // 12(IV) + 16(tag) = 28
const ZSTD_MAGIC_NUMBER = 0xFD2FB528;

async function aesDecrypt(source, keyStr) {
    try {
        // Base64 解码
        const data = Buffer.from(source, 'base64');
        const keyBytes = Buffer.from(keyStr, 'base64');

        // 验证最小长度要求
        if (data.length < MIN_DATA_LENGTH) {
            throw new Error(`Data too short (${data.length} bytes). Minimum required: ${MIN_DATA_LENGTH} bytes`);
        }

        // 提取各部分
        const iv = data.subarray(0, 12);
        const authTag = data.subarray(data.length - 16);
        const ciphertext = data.subarray(12, data.length - 16);

        // 创建解密器
        const decipher = crypto.createDecipheriv('aes-256-gcm', keyBytes, iv);
        decipher.setAuthTag(authTag);

        // 解密数据 - 正确处理流式解密
        const chunks = [];
        chunks.push(decipher.update(ciphertext));

        try {
            chunks.push(decipher.final());
        } catch (e) {
            throw new Error('Failed to finalize AES decryption', e);
        }

        const decrypted = Buffer.concat(chunks);

        // 初始化 Zstd
        await zstd.init();

        // 解压缩数据
        let decompressed;
        try {
            if (decrypted.length >= 4 && decrypted.readUInt32LE(0) !== ZSTD_MAGIC_NUMBER) {
                decompressed = decrypted;
            } else {
                decompressed = zstd.decompress(decrypted);
            }
        } catch (e) {
            decompressed = decrypted;
        }

        // 返回 JSON 字符串
        try {
            const decoder = new TextDecoder('utf-8');
            return decoder.decode(decompressed);
            
        } catch (e) {
            throw new Error('Failed to decode decrypted data as UTF-8', e);
        }

    } catch (e) {
        return null;
    }
}

async function testDecryption() {
    // encryptionKey
    const KeyStr = 'your encryptionKey';
    // sealedResult
    const Source = 'your sealedResult';
    
    const result = await aesDecrypt(Source, KeyStr);

    if (result) {
        try {
            // 尝试解析嵌套的JSON
            const parsed = JSON.parse(result);
            if (typeof parsed === 'string') {
                const innerParsed = JSON.parse(parsed);
                console.log('Double-parsed result:', innerParsed);
            } else {
                console.log('Parsed result:', parsed);
            }
        } catch (e) {
            console.error('Error parsing JSON:', e);
            console.log('Raw result:', result);
        }
    } else {
        console.log('Decryption failed');
    }
}

testDecryption();

//npm install @bokuweb/zstd-wasm 
<?php
function aesDecrypt($source, $keyStr) {
    try {
        // Base64 解码
        $data = base64_decode($source);
        $keyBytes = base64_decode($keyStr);
        
        // 提取 IV 和密文
        if (strlen($data) < 12) {
            throw new Exception("Invalid data");
        }
        $iv = substr($data, 0, 12);
        $ciphertext = substr($data, 12);
        
        // 提取认证标签 (最后16字节)
        $tag = substr($ciphertext, -16);
        $ciphertext = substr($ciphertext, 0, -16);
        
        // AES-GCM 解密
        $compressed = openssl_decrypt(
            $ciphertext,
            'aes-256-gcm',
            $keyBytes,
            OPENSSL_RAW_DATA,
            $iv,
            $tag
        );
        if ($compressed === false) {
            throw new Exception("AES decryption failed");
        }
        
        // Zstandard 解压缩
        $decompressed = zstd_uncompress($compressed);
        if ($decompressed === false) {
            throw new Exception("Zstd decompression failed");
        }
        
        // 返回 JSON 字符串
        return json_encode($decompressed);
        
    } catch (Exception $e) {
        error_log("AES256 decrypt fail! " . $e->getMessage());
        return null;
    }
}
// encryptionKey
$testKey = 'your encryptionKey'; 
// sealedResult
$testData = 'your sealedResult'; 
 
$result = aesDecrypt($testData, $testKey);
var_dump($result);

// 安装扩展: 
// pecl install zstd
// 并在php.ini中添加: extension=zstd
?>

禁用密封结果

您只需禁用客户平台的加密密钥即可关闭密封的客户端结果,禁用之后,SDK-API将不再返回加密的设备详情。禁用加密密钥之前,请确保您的集成可以支持处理未加密的数据结果