645 lines
22 KiB
Markdown
645 lines
22 KiB
Markdown
# 工具箱端 - 任务二维码解密指南
|
||
|
||
## 概述
|
||
|
||
本文档说明工具箱端如何解密任务二维码数据。App 创建任务后,平台会生成加密的任务数据并返回给 App,App 将其生成二维码。工具箱扫描二维码后,需要使用自己的 `licence` 和 `fingerprint` 解密任务数据。
|
||
|
||
> ### UX 集成模式补充(当前项目实现)
|
||
>
|
||
> 在当前集成模式中,工具箱扫描二维码后将密文提交给 UX 的 `crypto.decryptTask`,
|
||
> 由 UX 使用设备绑定的 `licence + fingerprint` 执行 AES-256-GCM 解密并返回任务明文。
|
||
|
||
## 一、业务流程
|
||
|
||
```
|
||
App创建任务 → 平台加密任务数据 → 返回加密数据 → App生成二维码
|
||
↓
|
||
工具箱扫描二维码 → 提取加密数据 → AES-256-GCM解密 → 获取任务信息
|
||
```
|
||
|
||
## 二、任务数据结构
|
||
|
||
### 2.1 任务数据 JSON 格式
|
||
|
||
解密后的任务数据为 JSON 格式,包含以下字段:
|
||
|
||
```json
|
||
{
|
||
"taskId": "TASK-20260115-4875",
|
||
"enterpriseId": "1173040813421105152",
|
||
"orgName": "超艺科技有限公司",
|
||
"inspectionId": "702286470691215417",
|
||
"inspectionPerson": "警务通",
|
||
"issuedAt": 1734571234567
|
||
}
|
||
```
|
||
|
||
### 2.2 字段说明
|
||
|
||
| 字段名 | 类型 | 说明 | 示例 |
|
||
|--------|------|------|------|
|
||
| `taskId` | String | 任务唯一ID(格式:TASK-YYYYMMDD-XXXX) | `"TASK-20260115-4875"` |
|
||
| `enterpriseId` | String | 企业ID | `"1173040813421105152"` |
|
||
| `orgName` | String | 单位名称 | `"超艺科技有限公司"` |
|
||
| `inspectionId` | String | 检查ID | `"702286470691215417"` |
|
||
| `inspectionPerson` | String | 检查人 | `"警务通"` |
|
||
| `issuedAt` | Number | 任务发布时间戳(毫秒) | `1734571234567` |
|
||
|
||
## 三、加密算法说明
|
||
|
||
### 3.1 加密方式
|
||
|
||
- **算法**:AES-256-GCM(Galois/Counter Mode)
|
||
- **密钥长度**:256 位(32 字节)
|
||
- **IV 长度**:12 字节(96 位)
|
||
- **认证标签长度**:16 字节(128 位)
|
||
|
||
### 3.2 密钥生成
|
||
|
||
密钥由工具箱的 `licence` 和 `fingerprint` 生成:
|
||
|
||
```
|
||
密钥 = SHA-256(licence + fingerprint)
|
||
```
|
||
|
||
**重要说明**:
|
||
- `licence` 和 `fingerprint` 直接字符串拼接(无分隔符)
|
||
- 使用 SHA-256 哈希算法的全部 32 字节作为 AES-256 密钥
|
||
- 工具箱必须使用与平台绑定时相同的 `licence` 和 `fingerprint`
|
||
|
||
### 3.3 加密数据格式
|
||
|
||
加密后的数据格式(Base64 编码前):
|
||
|
||
```
|
||
[IV(12字节)] + [加密数据] + [认证标签(16字节)]
|
||
```
|
||
|
||
**数据布局**:
|
||
```
|
||
+------------------+------------------+------------------+
|
||
| IV (12字节) | 加密数据 | 认证标签(16字节)|
|
||
+------------------+------------------+------------------+
|
||
```
|
||
|
||
## 四、解密步骤
|
||
|
||
### 4.1 解密流程
|
||
|
||
1. **扫描二维码**:获取 Base64 编码的加密数据
|
||
2. **Base64 解码**:将 Base64 字符串解码为字节数组
|
||
3. **分离数据**:从字节数组中分离 IV、加密数据和认证标签
|
||
4. **生成密钥**:使用 `licence + fingerprint` 生成 AES-256 密钥
|
||
5. **解密数据**:使用 AES-256-GCM 解密(自动验证认证标签)
|
||
6. **解析 JSON**:将解密后的字符串解析为 JSON 对象
|
||
|
||
### 4.2 Python 实现示例
|
||
|
||
```python
|
||
import base64
|
||
import json
|
||
import hashlib
|
||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||
from cryptography.hazmat.backends import default_backend
|
||
|
||
def decrypt_task_data(
|
||
encrypted_data_base64: str,
|
||
licence: str,
|
||
fingerprint: str
|
||
) -> dict:
|
||
"""
|
||
解密任务二维码数据
|
||
|
||
Args:
|
||
encrypted_data_base64: Base64编码的加密数据
|
||
licence: 设备授权码
|
||
fingerprint: 设备硬件指纹
|
||
|
||
Returns:
|
||
解密后的任务数据(字典)
|
||
"""
|
||
# 1. Base64 解码
|
||
encrypted_bytes = base64.b64decode(encrypted_data_base64)
|
||
|
||
# 2. 分离 IV 和加密数据(包含认证标签)
|
||
if len(encrypted_bytes) < 12:
|
||
raise ValueError("加密数据格式错误:数据长度不足")
|
||
|
||
iv = encrypted_bytes[:12] # IV: 前12字节
|
||
ciphertext_with_tag = encrypted_bytes[12:] # 加密数据 + 认证标签
|
||
|
||
# 3. 生成密钥:SHA-256(licence + fingerprint)
|
||
combined = licence + fingerprint
|
||
key = hashlib.sha256(combined.encode('utf-8')).digest()
|
||
|
||
# 4. 使用 AES-256-GCM 解密
|
||
aesgcm = AESGCM(key)
|
||
decrypted_bytes = aesgcm.decrypt(iv, ciphertext_with_tag, None)
|
||
|
||
# 5. 解析 JSON
|
||
decrypted_json = decrypted_bytes.decode('utf-8')
|
||
task_data = json.loads(decrypted_json)
|
||
|
||
return task_data
|
||
|
||
# 使用示例
|
||
if __name__ == "__main__":
|
||
# 从二维码扫描获取的加密数据
|
||
encrypted_data = "Base64编码的加密数据..."
|
||
|
||
# 工具箱的授权信息(必须与平台绑定时一致)
|
||
licence = "LIC-8F2A-XXXX"
|
||
fingerprint = "FP-2c91e9f3"
|
||
|
||
# 解密任务数据
|
||
task_data = decrypt_task_data(encrypted_data, licence, fingerprint)
|
||
|
||
print("任务ID:", task_data["taskId"])
|
||
print("企业ID:", task_data["enterpriseId"])
|
||
print("单位名称:", task_data["orgName"])
|
||
print("检查ID:", task_data["inspectionId"])
|
||
print("检查人:", task_data["inspectionPerson"])
|
||
print("发布时间:", task_data["issuedAt"])
|
||
```
|
||
|
||
### 4.3 Java/Kotlin 实现示例
|
||
|
||
```kotlin
|
||
import com.fasterxml.jackson.databind.ObjectMapper
|
||
import java.nio.charset.StandardCharsets
|
||
import java.security.MessageDigest
|
||
import java.util.Base64
|
||
import javax.crypto.Cipher
|
||
import javax.crypto.spec.GCMParameterSpec
|
||
import javax.crypto.spec.SecretKeySpec
|
||
|
||
object TaskDecryptionUtil {
|
||
|
||
private const val ALGORITHM = "AES"
|
||
private const val TRANSFORMATION = "AES/GCM/NoPadding"
|
||
private const val GCM_IV_LENGTH = 12 // GCM 推荐使用 12 字节 IV
|
||
private const val GCM_TAG_LENGTH = 16 // GCM 认证标签长度(128位)
|
||
private const val KEY_LENGTH = 32 // AES-256 密钥长度(256位 = 32字节)
|
||
|
||
private val objectMapper = ObjectMapper()
|
||
|
||
/**
|
||
* 解密任务二维码数据
|
||
*
|
||
* @param encryptedDataBase64 Base64编码的加密数据
|
||
* @param licence 设备授权码
|
||
* @param fingerprint 设备硬件指纹
|
||
* @return 解密后的任务数据(Map)
|
||
*/
|
||
fun decryptTaskData(
|
||
encryptedDataBase64: String,
|
||
licence: String,
|
||
fingerprint: String
|
||
): Map<String, Any> {
|
||
// 1. Base64 解码
|
||
val encryptedBytes = Base64.getDecoder().decode(encryptedDataBase64)
|
||
|
||
// 2. 分离 IV 和加密数据(包含认证标签)
|
||
if (encryptedBytes.size < GCM_IV_LENGTH) {
|
||
throw IllegalArgumentException("加密数据格式错误:数据长度不足")
|
||
}
|
||
|
||
val iv = encryptedBytes.sliceArray(0 until GCM_IV_LENGTH)
|
||
val ciphertextWithTag = encryptedBytes.sliceArray(GCM_IV_LENGTH until encryptedBytes.size)
|
||
|
||
// 3. 生成密钥:SHA-256(licence + fingerprint)
|
||
val combined = "$licence$fingerprint"
|
||
val digest = MessageDigest.getInstance("SHA-256")
|
||
val keyBytes = digest.digest(combined.toByteArray(StandardCharsets.UTF_8))
|
||
val key = SecretKeySpec(keyBytes, ALGORITHM)
|
||
|
||
// 4. 使用 AES-256-GCM 解密
|
||
val cipher = Cipher.getInstance(TRANSFORMATION)
|
||
val parameterSpec = GCMParameterSpec(GCM_TAG_LENGTH * 8, iv) // 标签长度以位为单位
|
||
cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec)
|
||
|
||
// 解密数据(GCM 会自动验证认证标签)
|
||
val decryptedBytes = cipher.doFinal(ciphertextWithTag)
|
||
|
||
// 5. 解析 JSON
|
||
val decryptedJson = String(decryptedBytes, StandardCharsets.UTF_8)
|
||
@Suppress("UNCHECKED_CAST")
|
||
return objectMapper.readValue(decryptedJson, Map::class.java) as Map<String, Any>
|
||
}
|
||
}
|
||
|
||
// 使用示例
|
||
fun main() {
|
||
// 从二维码扫描获取的加密数据
|
||
val encryptedData = "Base64编码的加密数据..."
|
||
|
||
// 工具箱的授权信息(必须与平台绑定时一致)
|
||
val licence = "LIC-8F2A-XXXX"
|
||
val fingerprint = "FP-2c91e9f3"
|
||
|
||
// 解密任务数据
|
||
val taskData = TaskDecryptionUtil.decryptTaskData(encryptedData, licence, fingerprint)
|
||
|
||
println("任务ID: ${taskData["taskId"]}")
|
||
println("企业ID: ${taskData["enterpriseId"]}")
|
||
println("单位名称: ${taskData["orgName"]}")
|
||
println("检查ID: ${taskData["inspectionId"]}")
|
||
println("检查人: ${taskData["inspectionPerson"]}")
|
||
println("发布时间: ${taskData["issuedAt"]}")
|
||
}
|
||
```
|
||
|
||
### 4.4 C# 实现示例
|
||
|
||
```csharp
|
||
using System;
|
||
using System.Security.Cryptography;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
|
||
public class TaskDecryptionUtil
|
||
{
|
||
private const int GcmIvLength = 12; // GCM 推荐使用 12 字节 IV
|
||
private const int GcmTagLength = 16; // GCM 认证标签长度(128位)
|
||
|
||
/// <summary>
|
||
/// 解密任务二维码数据
|
||
/// </summary>
|
||
public static Dictionary<string, object> DecryptTaskData(
|
||
string encryptedDataBase64,
|
||
string licence,
|
||
string fingerprint
|
||
)
|
||
{
|
||
// 1. Base64 解码
|
||
byte[] encryptedBytes = Convert.FromBase64String(encryptedDataBase64);
|
||
|
||
// 2. 分离 IV 和加密数据(包含认证标签)
|
||
if (encryptedBytes.Length < GcmIvLength)
|
||
{
|
||
throw new ArgumentException("加密数据格式错误:数据长度不足");
|
||
}
|
||
|
||
byte[] iv = new byte[GcmIvLength];
|
||
Array.Copy(encryptedBytes, 0, iv, 0, GcmIvLength);
|
||
|
||
byte[] ciphertextWithTag = new byte[encryptedBytes.Length - GcmIvLength];
|
||
Array.Copy(encryptedBytes, GcmIvLength, ciphertextWithTag, 0, ciphertextWithTag.Length);
|
||
|
||
// 3. 生成密钥:SHA-256(licence + fingerprint)
|
||
string combined = licence + fingerprint;
|
||
byte[] keyBytes = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(combined));
|
||
|
||
// 4. 使用 AES-256-GCM 解密
|
||
using (AesGcm aesGcm = new AesGcm(keyBytes))
|
||
{
|
||
byte[] decryptedBytes = new byte[ciphertextWithTag.Length - GcmTagLength];
|
||
byte[] tag = new byte[GcmTagLength];
|
||
Array.Copy(ciphertextWithTag, ciphertextWithTag.Length - GcmTagLength, tag, 0, GcmTagLength);
|
||
Array.Copy(ciphertextWithTag, 0, decryptedBytes, 0, decryptedBytes.Length);
|
||
|
||
aesGcm.Decrypt(iv, decryptedBytes, tag, null, decryptedBytes);
|
||
|
||
// 5. 解析 JSON
|
||
string decryptedJson = Encoding.UTF8.GetString(decryptedBytes);
|
||
return JsonSerializer.Deserialize<Dictionary<string, object>>(decryptedJson);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 使用示例
|
||
class Program
|
||
{
|
||
static void Main()
|
||
{
|
||
// 从二维码扫描获取的加密数据
|
||
string encryptedData = "Base64编码的加密数据...";
|
||
|
||
// 工具箱的授权信息(必须与平台绑定时一致)
|
||
string licence = "LIC-8F2A-XXXX";
|
||
string fingerprint = "FP-2c91e9f3";
|
||
|
||
// 解密任务数据
|
||
var taskData = TaskDecryptionUtil.DecryptTaskData(encryptedData, licence, fingerprint);
|
||
|
||
Console.WriteLine($"任务ID: {taskData["taskId"]}");
|
||
Console.WriteLine($"企业ID: {taskData["enterpriseId"]}");
|
||
Console.WriteLine($"单位名称: {taskData["orgName"]}");
|
||
Console.WriteLine($"检查ID: {taskData["inspectionId"]}");
|
||
Console.WriteLine($"检查人: {taskData["inspectionPerson"]}");
|
||
Console.WriteLine($"发布时间: {taskData["issuedAt"]}");
|
||
}
|
||
}
|
||
```
|
||
|
||
## 五、完整流程示例
|
||
|
||
### 5.1 Python 完整示例(包含二维码扫描)
|
||
|
||
```python
|
||
import base64
|
||
import json
|
||
import hashlib
|
||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||
from pyzbar import pyzbar
|
||
from PIL import Image
|
||
|
||
class TaskQRCodeDecoder:
|
||
"""任务二维码解码器"""
|
||
|
||
def __init__(self, licence: str, fingerprint: str):
|
||
"""
|
||
初始化解码器
|
||
|
||
Args:
|
||
licence: 设备授权码
|
||
fingerprint: 设备硬件指纹
|
||
"""
|
||
self.licence = licence
|
||
self.fingerprint = fingerprint
|
||
self._key = self._generate_key()
|
||
|
||
def _generate_key(self) -> bytes:
|
||
"""生成 AES-256 密钥"""
|
||
combined = self.licence + self.fingerprint
|
||
return hashlib.sha256(combined.encode('utf-8')).digest()
|
||
|
||
def scan_qr_code(self, qr_image_path: str) -> dict:
|
||
"""
|
||
扫描二维码并解密任务数据
|
||
|
||
Args:
|
||
qr_image_path: 二维码图片路径
|
||
|
||
Returns:
|
||
解密后的任务数据(字典)
|
||
"""
|
||
# 1. 扫描二维码
|
||
image = Image.open(qr_image_path)
|
||
qr_codes = pyzbar.decode(image)
|
||
|
||
if not qr_codes:
|
||
raise ValueError("未找到二维码")
|
||
|
||
# 获取二维码内容(Base64编码的加密数据)
|
||
encrypted_data_base64 = qr_codes[0].data.decode('utf-8')
|
||
print(f"扫描到二维码内容: {encrypted_data_base64[:50]}...")
|
||
|
||
# 2. 解密任务数据
|
||
return self.decrypt_task_data(encrypted_data_base64)
|
||
|
||
def decrypt_task_data(self, encrypted_data_base64: str) -> dict:
|
||
"""
|
||
解密任务数据
|
||
|
||
Args:
|
||
encrypted_data_base64: Base64编码的加密数据
|
||
|
||
Returns:
|
||
解密后的任务数据(字典)
|
||
"""
|
||
# 1. Base64 解码
|
||
encrypted_bytes = base64.b64decode(encrypted_data_base64)
|
||
|
||
# 2. 分离 IV 和加密数据(包含认证标签)
|
||
if len(encrypted_bytes) < 12:
|
||
raise ValueError("加密数据格式错误:数据长度不足")
|
||
|
||
iv = encrypted_bytes[:12] # IV: 前12字节
|
||
ciphertext_with_tag = encrypted_bytes[12:] # 加密数据 + 认证标签
|
||
|
||
# 3. 使用 AES-256-GCM 解密
|
||
aesgcm = AESGCM(self._key)
|
||
decrypted_bytes = aesgcm.decrypt(iv, ciphertext_with_tag, None)
|
||
|
||
# 4. 解析 JSON
|
||
decrypted_json = decrypted_bytes.decode('utf-8')
|
||
task_data = json.loads(decrypted_json)
|
||
|
||
return task_data
|
||
|
||
# 使用示例
|
||
if __name__ == "__main__":
|
||
# 工具箱的授权信息(必须与平台绑定时一致)
|
||
licence = "LIC-8F2A-XXXX"
|
||
fingerprint = "FP-2c91e9f3"
|
||
|
||
# 创建解码器
|
||
decoder = TaskQRCodeDecoder(licence, fingerprint)
|
||
|
||
# 扫描二维码并解密
|
||
try:
|
||
task_data = decoder.scan_qr_code("task_qr_code.png")
|
||
|
||
print("\n=== 任务信息 ===")
|
||
print(f"任务ID: {task_data['taskId']}")
|
||
print(f"企业ID: {task_data['enterpriseId']}")
|
||
print(f"单位名称: {task_data['orgName']}")
|
||
print(f"检查ID: {task_data['inspectionId']}")
|
||
print(f"检查人: {task_data['inspectionPerson']}")
|
||
print(f"发布时间: {task_data['issuedAt']}")
|
||
|
||
# 可以使用任务信息执行检查任务
|
||
# execute_inspection_task(task_data)
|
||
|
||
except Exception as e:
|
||
print(f"解密失败: {e}")
|
||
```
|
||
|
||
### 5.2 Java/Kotlin 完整示例(包含二维码扫描)
|
||
|
||
```kotlin
|
||
import com.fasterxml.jackson.databind.ObjectMapper
|
||
import com.google.zxing.BinaryBitmap
|
||
import com.google.zxing.MultiFormatReader
|
||
import com.google.zxing.Result
|
||
import com.google.zxing.client.j2se.BufferedImageLuminanceSource
|
||
import com.google.zxing.common.HybridBinarizer
|
||
import java.awt.image.BufferedImage
|
||
import java.io.File
|
||
import java.nio.charset.StandardCharsets
|
||
import java.security.MessageDigest
|
||
import java.util.Base64
|
||
import javax.crypto.Cipher
|
||
import javax.crypto.spec.GCMParameterSpec
|
||
import javax.crypto.spec.SecretKeySpec
|
||
import javax.imageio.ImageIO
|
||
|
||
class TaskQRCodeDecoder(
|
||
private val licence: String,
|
||
private val fingerprint: String
|
||
) {
|
||
|
||
private val key: SecretKeySpec by lazy {
|
||
val combined = "$licence$fingerprint"
|
||
val digest = MessageDigest.getInstance("SHA-256")
|
||
val keyBytes = digest.digest(combined.toByteArray(StandardCharsets.UTF_8))
|
||
SecretKeySpec(keyBytes, "AES")
|
||
}
|
||
|
||
private val objectMapper = ObjectMapper()
|
||
|
||
/**
|
||
* 扫描二维码并解密任务数据
|
||
*/
|
||
fun scanAndDecrypt(qrImagePath: String): Map<String, Any> {
|
||
// 1. 扫描二维码
|
||
val image: BufferedImage = ImageIO.read(File(qrImagePath))
|
||
val source = BufferedImageLuminanceSource(image)
|
||
val bitmap = BinaryBitmap(HybridBinarizer(source))
|
||
val reader = MultiFormatReader()
|
||
val result: Result = reader.decode(bitmap)
|
||
|
||
// 获取二维码内容(Base64编码的加密数据)
|
||
val encryptedDataBase64 = result.text
|
||
println("扫描到二维码内容: ${encryptedDataBase64.take(50)}...")
|
||
|
||
// 2. 解密任务数据
|
||
return decryptTaskData(encryptedDataBase64)
|
||
}
|
||
|
||
/**
|
||
* 解密任务数据
|
||
*/
|
||
fun decryptTaskData(encryptedDataBase64: String): Map<String, Any> {
|
||
// 1. Base64 解码
|
||
val encryptedBytes = Base64.getDecoder().decode(encryptedDataBase64)
|
||
|
||
// 2. 分离 IV 和加密数据(包含认证标签)
|
||
if (encryptedBytes.size < 12) {
|
||
throw IllegalArgumentException("加密数据格式错误:数据长度不足")
|
||
}
|
||
|
||
val iv = encryptedBytes.sliceArray(0 until 12)
|
||
val ciphertextWithTag = encryptedBytes.sliceArray(12 until encryptedBytes.size)
|
||
|
||
// 3. 使用 AES-256-GCM 解密
|
||
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
||
val parameterSpec = GCMParameterSpec(16 * 8, iv) // 标签长度以位为单位
|
||
cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec)
|
||
|
||
// 解密数据(GCM 会自动验证认证标签)
|
||
val decryptedBytes = cipher.doFinal(ciphertextWithTag)
|
||
|
||
// 4. 解析 JSON
|
||
val decryptedJson = String(decryptedBytes, StandardCharsets.UTF_8)
|
||
@Suppress("UNCHECKED_CAST")
|
||
return objectMapper.readValue(decryptedJson, Map::class.java) as Map<String, Any>
|
||
}
|
||
}
|
||
|
||
// 使用示例
|
||
fun main() {
|
||
// 工具箱的授权信息(必须与平台绑定时一致)
|
||
val licence = "LIC-8F2A-XXXX"
|
||
val fingerprint = "FP-2c91e9f3"
|
||
|
||
// 创建解码器
|
||
val decoder = TaskQRCodeDecoder(licence, fingerprint)
|
||
|
||
// 扫描二维码并解密
|
||
try {
|
||
val taskData = decoder.scanAndDecrypt("task_qr_code.png")
|
||
|
||
println("\n=== 任务信息 ===")
|
||
println("任务ID: ${taskData["taskId"]}")
|
||
println("企业ID: ${taskData["enterpriseId"]}")
|
||
println("单位名称: ${taskData["orgName"]}")
|
||
println("检查ID: ${taskData["inspectionId"]}")
|
||
println("检查人: ${taskData["inspectionPerson"]}")
|
||
println("发布时间: ${taskData["issuedAt"]}")
|
||
|
||
// 可以使用任务信息执行检查任务
|
||
// executeInspectionTask(taskData)
|
||
|
||
} catch (e: Exception) {
|
||
println("解密失败: ${e.message}")
|
||
}
|
||
}
|
||
```
|
||
|
||
## 六、常见错误和注意事项
|
||
|
||
### 6.1 解密失败
|
||
|
||
**可能原因**:
|
||
1. **密钥不匹配**:`licence` 或 `fingerprint` 与平台绑定时不一致
|
||
- 确保使用与设备授权时相同的 `licence` 和 `fingerprint`
|
||
- 检查字符串拼接是否正确(无分隔符)
|
||
|
||
2. **数据格式错误**:Base64 编码或数据布局错误
|
||
- 确保 Base64 解码正确
|
||
- 确保 IV 长度正确(12 字节)
|
||
|
||
3. **认证标签验证失败**:数据被篡改或损坏
|
||
- GCM 模式会自动验证认证标签
|
||
- 如果验证失败,说明数据被篡改或密钥错误
|
||
|
||
4. **算法不匹配**:必须使用 `AES/GCM/NoPadding`
|
||
- 确保使用正确的加密算法
|
||
- 确保认证标签长度为 128 位(16 字节)
|
||
|
||
### 6.2 二维码扫描失败
|
||
|
||
**可能原因**:
|
||
1. **二维码图片质量差**:确保图片清晰,有足够的对比度
|
||
2. **二维码内容过长**:如果加密数据过长,可能需要更高版本的二维码
|
||
3. **扫描库不支持**:确保使用支持 Base64 字符串的二维码扫描库
|
||
|
||
### 6.3 JSON 解析失败
|
||
|
||
**可能原因**:
|
||
1. **字符编码错误**:确保使用 UTF-8 编码
|
||
2. **JSON 格式错误**:确保解密后的字符串是有效的 JSON
|
||
3. **字段缺失**:确保所有必需字段都存在
|
||
|
||
## 七、安全设计说明
|
||
|
||
### 7.1 为什么使用 AES-256-GCM
|
||
|
||
1. **认证加密(AEAD)**:GCM 模式提供加密和认证,防止数据被篡改
|
||
2. **强安全性**:AES-256 提供 256 位密钥强度
|
||
3. **自动验证**:GCM 模式会自动验证认证标签,任何篡改都会导致解密失败
|
||
|
||
### 7.2 为什么第三方无法解密
|
||
|
||
1. **密钥绑定**:只有拥有正确 `licence + fingerprint` 的工具箱才能生成正确的密钥
|
||
2. **认证标签**:GCM 模式会验证认证标签,任何篡改都会导致解密失败
|
||
3. **密钥唯一性**:每个设备的 `licence + fingerprint` 组合是唯一的
|
||
|
||
### 7.3 密钥生成的安全性
|
||
|
||
1. **SHA-256 哈希**:使用强哈希算法生成密钥
|
||
2. **密钥长度**:使用全部 32 字节作为 AES-256 密钥
|
||
3. **密钥隔离**:每个设备的密钥是独立的,互不影响
|
||
|
||
## 八、测试建议
|
||
|
||
1. **单元测试**:
|
||
- 测试密钥生成是否正确
|
||
- 测试解密功能是否正常
|
||
- 测试 JSON 解析是否正确
|
||
|
||
2. **集成测试**:
|
||
- 使用真实平台生成的二维码进行测试
|
||
- 测试不同长度的任务数据
|
||
- 测试错误的密钥是否会导致解密失败
|
||
|
||
3. **边界测试**:
|
||
- 测试超长的任务数据
|
||
- 测试特殊字符的处理
|
||
- 测试错误的 Base64 格式
|
||
|
||
## 九、参考实现
|
||
|
||
- **Python**:`cryptography` 库(AES-GCM 加密)、`pyzbar` 库(二维码扫描)
|
||
- **Java/Kotlin**:JDK `javax.crypto`(AES-GCM 加密)、ZXing 库(二维码扫描)
|
||
- **C#**:`System.Security.Cryptography`(AES-GCM 加密)、ZXing.Net 库(二维码扫描)
|
||
|
||
## 十、联系支持
|
||
|
||
如有问题,请联系平台技术支持团队获取:
|
||
- 测试环境地址
|
||
- 技术支持
|
||
|