Version Description
Web SDK version 5.1.0+
Android SDK version 5.1.0+
iOS SDK version 5.1.0+
Sealed Client Result Description
Generally, the application of the fingerprint of the customer access device to the business scenario is divided into two parts. In the first step, the customer application needs to integrate the SDK to collect the device information first, and the same shield returns the device information black box. The second step client application obtains the black box and passes it to the wind control center, which obtains the device ID and device information through the black box adjustment device fingerprint service to make decisions.
The result of sealing the client is that when the customer application accesses the SDK, you can choose whether to directly return the encrypted device information. If it is confirmed that the front-end application obtains the encrypted device information, the same shield SDK will directly return the anonymous ID, black box and encrypted device information, and the customer's wind control center will decrypt the device information through the key,To make a decision, the interaction between the wind control center and the device fingerprint service is omitted.
Advantages:
- This reduces the delay in the interaction between the end and the end, and the decryption result is directly on your server without the need to call the API service.
- Improved data security, Black products can not maliciously read the client seal results

Configuring Sealed Client Results
If you want to use sealed client results, you need to complete the following steps
- Create and enable an encryption key in the client platform-developer-Key & SDK
- The frontend initialization SDK obtains the sealing result and sends the sealing result to the server
- Your server needs to receive the sealed result and decrypt it with the key
Step 1: Create an encryption key
Navigate to Customer Platform-Developer-Key & SDK-Encryption Key

- Click Add
- Enter name
- Select the front-end encrypted appKey binding, support binding multiple
- Select Status
- Click Save

Copy the encryption key and provide it to the server for use.

Step 2: The server decrypts the sealedResult
! Don't decrypt the sealedResults on the client side
The decryption function is designed only on the backend. If you try to decrypt the sealed result on the client, it means that anyone can see the encryption key. This could leak sensitive information and introduce new hacking attacks.
After the front end sends the client's sealedResult to the server, it is necessary to use the encryption key generated as described above for decryption.
Example of Decrypting the Sealed Result:
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:
data = base64.b64decode(source)
key_bytes = base64.b64decode(key_str)
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]
cipher = Cipher(
algorithms.AES(key_bytes),
modes.GCM(iv, auth_tag),
backend=default_backend()
)
decryptor = cipher.decryptor()
compressed = decryptor.update(ciphertext) + decryptor.finalize()
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()
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 Deviceinfo 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");
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;
}
}
// <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) {
data, _ := base64.StdEncoding.DecodeString(source)
keyBytes, _ := base64.StdEncoding.DecodeString(keyStr)
if len(data) < 12 {
return "", fmt.Errorf("invalid data length")
}
iv := data[:12]
ciphertext := data[12:]
block, _ := aes.NewCipher(keyBytes)
gcm, _ := cipher.NewGCM(block)
compressed, err := gcm.Open(nil, iv, ciphertext, nil)
if err != nil {
return "", err
}
decoder, _ := zstd.NewReader(nil)
defer decoder.Close()
decompressed, err := decoder.DecodeAll(compressed, nil)
if err != nil {
return "", err
}
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 {
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);
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;
}
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 {
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 {
$data = base64_decode($source);
$keyBytes = base64_decode($keyStr);
if (strlen($data) < 12) {
throw new Exception("Invalid data");
}
$iv = substr($data, 0, 12);
$ciphertext = substr($data, 12);
$tag = substr($ciphertext, -16);
$ciphertext = substr($ciphertext, 0, -16);
$compressed = openssl_decrypt(
$ciphertext,
'aes-256-gcm',
$keyBytes,
OPENSSL_RAW_DATA,
$iv,
$tag
);
if ($compressed === false) {
throw new Exception("AES decryption failed");
}
$decompressed = zstd_uncompress($compressed);
if ($decompressed === false) {
throw new Exception("Zstd decompression failed");
}
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);
?>
Disabling Sealed Client Results
You can turn off sealed client results by simply disabling the encryption key for the client platform. Before disabling encryption keys, ensure that your integration can support processing unencrypted data results
