版本说明
- Web SDK 5.1.0及以上
- Android SDK 5.1.0及以上
- iOS SDK 5.1.0及以上
密封客户端结果说明
通常客户接入设备指纹应用到业务场景分为两部分,第一步客户的应用需要集成 SDK 先采集设备信息,由同盾返回设备信息黑盒。第二步客户端应用获取到黑盒传递给风控中台,由风控中台通过黑盒调设备指纹服务获取设备ID 及设备信息进行决策。
密封客户端结果,是在客户应用接入SDK 的时候就可以选择是否直接返回加密的设备信息,如果确认由前端应用获取,同盾 SDK 会直接返回匿名ID、黑盒及加密的设备信息,由客户的风控中台通过密钥解密设备信息,进行决策,省略了风控中台与设备指纹服务交互的步骤。
优点:
- 减少了端与端之前交互的延迟,解密信息直接在您的服务端应用,无需调 API 接口
- 提高了数据的安全性,黑产无法恶意读取客户端密封结果

配置密封客户端结果
如果您想使用密封客户端结果,需要完成以下步骤
- 在客户平台-开发者-密钥&SDK 里创建加密密钥并启用
- 前端初始化 SDK 会获取到密封结果,将密封结果发送到服务端
- 您的服务端需要接收密封结果并且通过密钥解密
创建加密密钥
导航到客户平台-开发者-密钥&SDK-加密密钥

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

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

服务端解密密封结果
- 不要在客户端解密密封结果
- 解密功能设计仅在后端进行。如果尝试在客户端解密密封结果,意味着任何人都可以看到加密密钥。这会泄漏敏感信息同时引入新的黑产攻击
前端发送客户端密封结果到服务端后,需要使用上面生成的加密密钥进行解密。
解密密封结果代码参考:
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将不再返回加密的设备详情。禁用加密密钥之前,请确保您的集成可以支持处理未加密的数据结果
