Files
fullstack-starter/docs/工具箱端-授权对接指南/工具箱端-摘要信息二维码生成指南.md

25 KiB
Raw Blame History

工具箱端 - 摘要信息二维码生成指南

概述

本文档说明工具箱端如何生成摘要信息二维码。工具箱完成检查任务后,需要将摘要信息加密并生成二维码,供 App 扫描后上传到平台。

UX 集成模式补充(当前项目实现)

在当前集成模式中,工具箱将摘要明文传给 UX 的 crypto.encryptSummary 由 UX 执行 HKDF + AES-256-GCM 加密并返回二维码内容 JSONtaskId + encrypted)。

一、业务流程

工具箱完成检查 → 准备摘要信息 → HKDF派生密钥 → AES-256-GCM加密 → 组装二维码内容 → 生成二维码
                                                                                    ↓
App扫描二维码 → 提取taskId和encrypted → 提交到平台 → 平台解密验证 → 保存摘要信息

二、二维码内容格式

二维码内容为 JSON 格式,包含以下字段:

{
  "taskId": "TASK-20260115-4875",
  "encrypted": "uWUcAmp6UQd0w3G3crdsd4613QCxGLoEgslgXJ4G2hQhpQdjtghtQjCBUZwB/JO+NRgH1vSTr8dqBJRq7Qh4nugESrB2jUSGASTf4+5E7cLlDOmtDw7QlqS+6Hb7sn3daMSOovcna07huchHeesrJCiHV8ntEDXdCCdQOEHfkZAvy5gS8jQY41x5Qcnmqbz3qqHTmceIihTj4uqRVyKOE8jxzY6ko76jx0gW239gyFysJUTrqSPiFAr+gToi2l9SWP8ISViBmYmCY2cQtKvPfTKXwxGMid0zE/nDmb9n38X1oR05nAP0v1KaVY7iPcjsWySDGqO2iIbPzV8tQzq5TNuYqn9gvxIX/oRTFECP+aosfmOD5I8H8rVFTebyTHw+ONV3KoN2IMRqnG+a2lucbhzwQk7/cX1hs9lYm+yapmp+0MbLCtf2KMWqJPdeZqTVZgi3R181BCxo3OIwcCFTnZ/b9pdw+q8ai6SJpso5mA0TpUCvqYlGlKMZde0nj07kmLpdAm3AOg3GtPezfJu8iHmsc4PTa8RDsPgTIxcdyxNSMqo1Ws3VLQXm6DHK/kma/vbvSA/N7upPzi7wLvboig=="
}

2.1 字段说明

字段名 类型 说明 示例
taskId String 任务ID从任务二维码中获取 "TASK-20260115-4875"
encrypted String Base64编码的加密数据 "uWUcAmp6UQd0w3G3..."

三、摘要信息数据结构

3.1 明文数据 JSON 格式

加密前的摘要信息为 JSON 格式,包含以下字段:

{
  "enterpriseId": "1173040813421105152",
  "inspectionId": "702286470691215417",
  "summary": "检查摘要信息",
  "timestamp": 1734571234567
}

3.2 字段说明

字段名 类型 说明 示例
enterpriseId String 企业ID从任务数据中获取 "1173040813421105152"
inspectionId String 检查ID从任务数据中获取 "702286470691215417"
summary String 检查摘要信息 "检查摘要信息"
timestamp Number 时间戳(毫秒) 1734571234567

四、密钥派生HKDF-SHA256

4.1 密钥派生参数

使用 HKDF-SHA256licence + fingerprint 派生 AES 密钥:

AES Key = HKDF(
    input = licence + fingerprint,              # 输入密钥材料(字符串拼接)
    salt = taskId,                              # Salt值任务ID
    info = "inspection_report_encryption",     # Info值固定值
    hash = SHA-256,                             # 哈希算法
    length = 32                                 # 输出密钥长度32字节 = 256位
)

重要说明

  • ikm(输入密钥材料)= licence + fingerprint(直接字符串拼接,无分隔符)
  • salt = taskId从任务二维码中获取的任务ID
  • info = "inspection_report_encryption"(固定值,区分不同用途的密钥)
  • length = 32 字节AES-256 密钥长度)

4.2 Python 实现示例

import hashlib
import hkdf

def derive_aes_key(licence: str, fingerprint: str, task_id: str) -> bytes:
    """
    使用 HKDF-SHA256 派生 AES-256 密钥
    
    Args:
        licence: 设备授权码
        fingerprint: 设备硬件指纹
        task_id: 任务ID
    
    Returns:
        派生出的密钥32字节
    """
    # 输入密钥材料
    ikm = licence + fingerprint  # 直接字符串拼接
    
    # HKDF 参数
    salt = task_id
    info = "inspection_report_encryption"
    key_length = 32  # 32字节 = 256位
    
    # 派生密钥
    derived_key = hkdf.HKDF(
        algorithm=hashlib.sha256,
        length=key_length,
        salt=salt.encode('utf-8'),
        info=info.encode('utf-8'),
        ikm=ikm.encode('utf-8')
    ).derive()
    
    return derived_key

4.3 Java/Kotlin 实现示例

import org.bouncycastle.crypto.digests.SHA256Digest
import org.bouncycastle.crypto.generators.HKDFBytesGenerator
import org.bouncycastle.crypto.params.HKDFParameters
import java.nio.charset.StandardCharsets

fun deriveAesKey(licence: String, fingerprint: String, taskId: String): ByteArray {
    // 输入密钥材料
    val ikm = (licence + fingerprint).toByteArray(StandardCharsets.UTF_8)
    
    // HKDF 参数
    val salt = taskId.toByteArray(StandardCharsets.UTF_8)
    val info = "inspection_report_encryption".toByteArray(StandardCharsets.UTF_8)
    val keyLength = 32  // 32字节 = 256位
    
    // 派生密钥
    val hkdf = HKDFBytesGenerator(SHA256Digest())
    val params = HKDFParameters(ikm, salt, info)
    hkdf.init(params)
    
    val derivedKey = ByteArray(keyLength)
    hkdf.generateBytes(derivedKey, 0, keyLength)
    
    return derivedKey
}

五、AES-256-GCM 加密

5.1 加密算法

  • 算法AES-256-GCMGalois/Counter Mode
  • 密钥长度256 位32 字节)
  • IV 长度12 字节96 位)
  • 认证标签长度16 字节128 位)

5.2 加密数据格式

加密后的数据格式Base64 编码前):

[IV(12字节)] + [加密数据] + [认证标签(16字节)]

数据布局

+------------------+------------------+------------------+
|   IV (12字节)    |   加密数据       |   认证标签(16字节)|
+------------------+------------------+------------------+

5.3 Python 实现示例

import base64
import hashlib
import hkdf
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.backends import default_backend
import json
import time

def encrypt_summary_data(
    enterprise_id: str,
    inspection_id: str,
    summary: str,
    licence: str,
    fingerprint: str,
    task_id: str
) -> str:
    """
    加密摘要信息数据
    
    Args:
        enterprise_id: 企业ID
        inspection_id: 检查ID
        summary: 摘要信息
        licence: 设备授权码
        fingerprint: 设备硬件指纹
        task_id: 任务ID
    
    Returns:
        Base64编码的加密数据
    """
    # 1. 组装明文数据JSON格式
    timestamp = int(time.time() * 1000)  # 毫秒时间戳
    plaintext_map = {
        "enterpriseId": str(enterprise_id),
        "inspectionId": str(inspection_id),
        "summary": summary,
        "timestamp": timestamp
    }
    plaintext = json.dumps(plaintext_map, ensure_ascii=False)
    
    # 2. 使用 HKDF-SHA256 派生 AES 密钥
    ikm = licence + fingerprint
    salt = task_id
    info = "inspection_report_encryption"
    key_length = 32
    
    aes_key = hkdf.HKDF(
        algorithm=hashlib.sha256,
        length=key_length,
        salt=salt.encode('utf-8'),
        info=info.encode('utf-8'),
        ikm=ikm.encode('utf-8')
    ).derive()
    
    # 3. 使用 AES-256-GCM 加密数据
    aesgcm = AESGCM(aes_key)
    iv = os.urandom(12)  # 生成12字节随机IV
    encrypted_bytes = aesgcm.encrypt(iv, plaintext.encode('utf-8'), None)
    
    # 4. 组装IV + 加密数据(包含认证标签)
    # AESGCM.encrypt 返回的格式已经是:加密数据 + 认证标签
    combined = iv + encrypted_bytes
    
    # 5. Base64 编码
    encrypted_base64 = base64.b64encode(combined).decode('utf-8')
    
    return encrypted_base64

5.4 Java/Kotlin 实现示例

import com.fasterxml.jackson.databind.ObjectMapper
import org.bouncycastle.crypto.digests.SHA256Digest
import org.bouncycastle.crypto.generators.HKDFBytesGenerator
import org.bouncycastle.crypto.params.HKDFParameters
import java.nio.charset.StandardCharsets
import java.security.SecureRandom
import java.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec

object SummaryEncryptionUtil {
    
    private const val ALGORITHM = "AES"
    private const val TRANSFORMATION = "AES/GCM/NoPadding"
    private const val GCM_IV_LENGTH = 12 // 12 bytes = 96 bits
    private const val GCM_TAG_LENGTH = 16 // 16 bytes = 128 bits
    private const val GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH * 8 // 128 bits
    
    private val objectMapper = ObjectMapper()
    private val secureRandom = SecureRandom()
    
    /**
     * 加密摘要信息数据
     */
    fun encryptSummaryData(
        enterpriseId: String,
        inspectionId: String,
        summary: String,
        licence: String,
        fingerprint: String,
        taskId: String
    ): String {
        // 1. 组装明文数据JSON格式
        val timestamp = System.currentTimeMillis()
        val plaintextMap = mapOf(
            "enterpriseId" to enterpriseId,
            "inspectionId" to inspectionId,
            "summary" to summary,
            "timestamp" to timestamp
        )
        val plaintext = objectMapper.writeValueAsString(plaintextMap)
        
        // 2. 使用 HKDF-SHA256 派生 AES 密钥
        val ikm = (licence + fingerprint).toByteArray(StandardCharsets.UTF_8)
        val salt = taskId.toByteArray(StandardCharsets.UTF_8)
        val info = "inspection_report_encryption".toByteArray(StandardCharsets.UTF_8)
        val keyLength = 32
        
        val hkdf = HKDFBytesGenerator(SHA256Digest())
        val params = HKDFParameters(ikm, salt, info)
        hkdf.init(params)
        
        val aesKey = ByteArray(keyLength)
        hkdf.generateBytes(aesKey, 0, keyLength)
        
        // 3. 使用 AES-256-GCM 加密数据
        val iv = ByteArray(GCM_IV_LENGTH)
        secureRandom.nextBytes(iv)
        
        val secretKey = SecretKeySpec(aesKey, ALGORITHM)
        val gcmSpec = GCMParameterSpec(GCM_TAG_LENGTH_BITS, iv)
        
        val cipher = Cipher.getInstance(TRANSFORMATION)
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmSpec)
        
        val plaintextBytes = plaintext.toByteArray(StandardCharsets.UTF_8)
        val encryptedBytes = cipher.doFinal(plaintextBytes)
        
        // 4. 组装IV + 加密数据(包含认证标签)
        // GCM 模式会将认证标签附加到密文末尾
        val ciphertext = encryptedBytes.sliceArray(0 until encryptedBytes.size - GCM_TAG_LENGTH)
        val tag = encryptedBytes.sliceArray(encryptedBytes.size - GCM_TAG_LENGTH until encryptedBytes.size)
        
        val combined = iv + ciphertext + tag
        
        // 5. Base64 编码
        return Base64.getEncoder().encodeToString(combined)
    }
}

六、组装二维码内容

6.1 二维码内容 JSON

taskId 和加密后的 encrypted 组装成 JSON 格式:

{
  "taskId": "TASK-20260115-4875",
  "encrypted": "Base64编码的加密数据"
}

6.2 Python 实现示例

import json

def generate_qr_code_content(task_id: str, encrypted: str) -> str:
    """
    生成二维码内容JSON格式
    
    Args:
        task_id: 任务ID
        encrypted: Base64编码的加密数据
    
    Returns:
        JSON格式的字符串
    """
    qr_content = {
        "taskId": task_id,
        "encrypted": encrypted
    }
    return json.dumps(qr_content, ensure_ascii=False)

七、完整流程示例

7.1 Python 完整示例

import base64
import json
import time
import hashlib
import hkdf
import qrcode
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

class SummaryQRCodeGenerator:
    """摘要信息二维码生成器"""
    
    def __init__(self, licence: str, fingerprint: str):
        """
        初始化生成器
        
        Args:
            licence: 设备授权码
            fingerprint: 设备硬件指纹
        """
        self.licence = licence
        self.fingerprint = fingerprint
    
    def generate_summary_qr_code(
        self,
        task_id: str,
        enterprise_id: str,
        inspection_id: str,
        summary: str,
        output_path: str = "summary_qr.png"
    ) -> str:
        """
        生成摘要信息二维码
        
        Args:
            task_id: 任务ID从任务二维码中获取
            enterprise_id: 企业ID从任务数据中获取
            inspection_id: 检查ID从任务数据中获取
            summary: 摘要信息
            output_path: 二维码图片保存路径
        
        Returns:
            二维码内容JSON字符串
        """
        # 1. 组装明文数据JSON格式
        timestamp = int(time.time() * 1000)  # 毫秒时间戳
        plaintext_map = {
            "enterpriseId": str(enterprise_id),
            "inspectionId": str(inspection_id),
            "summary": summary,
            "timestamp": timestamp
        }
        plaintext = json.dumps(plaintext_map, ensure_ascii=False)
        print(f"明文数据: {plaintext}")
        
        # 2. 使用 HKDF-SHA256 派生 AES 密钥
        ikm = self.licence + self.fingerprint
        salt = task_id
        info = "inspection_report_encryption"
        key_length = 32
        
        aes_key = hkdf.HKDF(
            algorithm=hashlib.sha256,
            length=key_length,
            salt=salt.encode('utf-8'),
            info=info.encode('utf-8'),
            ikm=ikm.encode('utf-8')
        ).derive()
        print(f"密钥派生成功: {len(aes_key)} 字节")
        
        # 3. 使用 AES-256-GCM 加密数据
        aesgcm = AESGCM(aes_key)
        iv = os.urandom(12)  # 生成12字节随机IV
        encrypted_bytes = aesgcm.encrypt(iv, plaintext.encode('utf-8'), None)
        
        # 组装IV + 加密数据(包含认证标签)
        combined = iv + encrypted_bytes
        
        # Base64 编码
        encrypted_base64 = base64.b64encode(combined).decode('utf-8')
        print(f"加密成功: {encrypted_base64[:50]}...")
        
        # 4. 组装二维码内容JSON格式
        qr_content = {
            "taskId": task_id,
            "encrypted": encrypted_base64
        }
        qr_content_json = json.dumps(qr_content, ensure_ascii=False)
        print(f"二维码内容: {qr_content_json[:100]}...")
        
        # 5. 生成二维码
        qr = qrcode.QRCode(
            version=1,
            error_correction=qrcode.constants.ERROR_CORRECT_M,
            box_size=10,
            border=4,
        )
        qr.add_data(qr_content_json)
        qr.make(fit=True)
        
        img = qr.make_image(fill_color="black", back_color="white")
        img.save(output_path)
        print(f"二维码已生成: {output_path}")
        
        return qr_content_json

# 使用示例
if __name__ == "__main__":
    # 工具箱的授权信息(必须与平台绑定时一致)
    licence = "LIC-8F2A-XXXX"
    fingerprint = "FP-2c91e9f3"
    
    # 创建生成器
    generator = SummaryQRCodeGenerator(licence, fingerprint)
    
    # 从任务二维码中获取的信息
    task_id = "TASK-20260115-4875"
    enterprise_id = "1173040813421105152"
    inspection_id = "702286470691215417"
    summary = "检查摘要信息发现3个高危漏洞5个中危漏洞"
    
    # 生成二维码
    qr_content = generator.generate_summary_qr_code(
        task_id=task_id,
        enterprise_id=enterprise_id,
        inspection_id=inspection_id,
        summary=summary,
        output_path="summary_qr_code.png"
    )
    
    print(f"\n二维码内容:\n{qr_content}")

7.2 Java/Kotlin 完整示例

import com.fasterxml.jackson.databind.ObjectMapper
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import org.bouncycastle.crypto.digests.SHA256Digest
import org.bouncycastle.crypto.generators.HKDFBytesGenerator
import org.bouncycastle.crypto.params.HKDFParameters
import java.awt.image.BufferedImage
import java.nio.charset.StandardCharsets
import java.security.SecureRandom
import java.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
import javax.imageio.ImageIO
import java.io.File

class SummaryQRCodeGenerator(
    private val licence: String,
    private val fingerprint: String
) {
    
    private const val ALGORITHM = "AES"
    private const val TRANSFORMATION = "AES/GCM/NoPadding"
    private const val GCM_IV_LENGTH = 12
    private const val GCM_TAG_LENGTH = 16
    private const val GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH * 8
    
    private val objectMapper = ObjectMapper()
    private val secureRandom = SecureRandom()
    
    /**
     * 生成摘要信息二维码
     */
    fun generateSummaryQRCode(
        taskId: String,
        enterpriseId: String,
        inspectionId: String,
        summary: String,
        outputPath: String = "summary_qr.png"
    ): String {
        // 1. 组装明文数据JSON格式
        val timestamp = System.currentTimeMillis()
        val plaintextMap = mapOf(
            "enterpriseId" to enterpriseId,
            "inspectionId" to inspectionId,
            "summary" to summary,
            "timestamp" to timestamp
        )
        val plaintext = objectMapper.writeValueAsString(plaintextMap)
        println("明文数据: $plaintext")
        
        // 2. 使用 HKDF-SHA256 派生 AES 密钥
        val ikm = (licence + fingerprint).toByteArray(StandardCharsets.UTF_8)
        val salt = taskId.toByteArray(StandardCharsets.UTF_8)
        val info = "inspection_report_encryption".toByteArray(StandardCharsets.UTF_8)
        val keyLength = 32
        
        val hkdf = HKDFBytesGenerator(SHA256Digest())
        val params = HKDFParameters(ikm, salt, info)
        hkdf.init(params)
        
        val aesKey = ByteArray(keyLength)
        hkdf.generateBytes(aesKey, 0, keyLength)
        println("密钥派生成功: ${aesKey.size} 字节")
        
        // 3. 使用 AES-256-GCM 加密数据
        val iv = ByteArray(GCM_IV_LENGTH)
        secureRandom.nextBytes(iv)
        
        val secretKey = SecretKeySpec(aesKey, ALGORITHM)
        val gcmSpec = GCMParameterSpec(GCM_TAG_LENGTH_BITS, iv)
        
        val cipher = Cipher.getInstance(TRANSFORMATION)
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmSpec)
        
        val plaintextBytes = plaintext.toByteArray(StandardCharsets.UTF_8)
        val encryptedBytes = cipher.doFinal(plaintextBytes)
        
        // 组装IV + 加密数据(包含认证标签)
        val ciphertext = encryptedBytes.sliceArray(0 until encryptedBytes.size - GCM_TAG_LENGTH)
        val tag = encryptedBytes.sliceArray(encryptedBytes.size - GCM_TAG_LENGTH until encryptedBytes.size)
        
        val combined = iv + ciphertext + tag
        
        // Base64 编码
        val encryptedBase64 = Base64.getEncoder().encodeToString(combined)
        println("加密成功: ${encryptedBase64.take(50)}...")
        
        // 4. 组装二维码内容JSON格式
        val qrContent = mapOf(
            "taskId" to taskId,
            "encrypted" to encryptedBase64
        )
        val qrContentJson = objectMapper.writeValueAsString(qrContent)
        println("二维码内容: ${qrContentJson.take(100)}...")
        
        // 5. 生成二维码
        val hints = hashMapOf<EncodeHintType, Any>().apply {
            put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M)
            put(EncodeHintType.CHARACTER_SET, "UTF-8")
            put(EncodeHintType.MARGIN, 1)
        }
        
        val writer = QRCodeWriter()
        val bitMatrix = writer.encode(qrContentJson, BarcodeFormat.QR_CODE, 300, 300, hints)
        
        val width = bitMatrix.width
        val height = bitMatrix.height
        val image = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
        
        for (x in 0 until width) {
            for (y in 0 until height) {
                image.setRGB(x, y, if (bitMatrix[x, y]) 0x000000 else 0xFFFFFF)
            }
        }
        
        ImageIO.write(image, "PNG", File(outputPath))
        println("二维码已生成: $outputPath")
        
        return qrContentJson
    }
}

// 使用示例
fun main() {
    // 工具箱的授权信息(必须与平台绑定时一致)
    val licence = "LIC-8F2A-XXXX"
    val fingerprint = "FP-2c91e9f3"
    
    // 创建生成器
    val generator = SummaryQRCodeGenerator(licence, fingerprint)
    
    // 从任务二维码中获取的信息
    val taskId = "TASK-20260115-4875"
    val enterpriseId = "1173040813421105152"
    val inspectionId = "702286470691215417"
    val summary = "检查摘要信息发现3个高危漏洞5个中危漏洞"
    
    // 生成二维码
    val qrContent = generator.generateSummaryQRCode(
        taskId = taskId,
        enterpriseId = enterpriseId,
        inspectionId = inspectionId,
        summary = summary,
        outputPath = "summary_qr_code.png"
    )
    
    println("\n二维码内容:\n$qrContent")
}

八、平台端验证流程

平台端会按以下流程验证:

  1. 接收请求App 扫描二维码后,将 taskIdencrypted 提交到平台
  2. 查询任务:根据 taskId 查询任务记录,获取 deviceLicenceId
  3. 获取设备信息:根据 deviceLicenceId 查询设备授权记录,获取 licencefingerprint
  4. 密钥派生:使用相同的 HKDF 参数派生 AES 密钥
  5. 解密数据:使用 AES-256-GCM 解密(自动验证认证标签)
  6. 时间戳校验:验证 timestamp 是否在合理范围内(防止重放攻击)
  7. 保存摘要:将摘要信息保存到数据库

九、常见错误和注意事项

9.1 加密失败

可能原因

  1. 密钥派生错误:确保使用正确的 HKDF 参数

    • ikm = licence + fingerprint(直接字符串拼接)
    • salt = taskId(必须与任务二维码中的 taskId 一致)
    • info = "inspection_report_encryption"(固定值)
    • length = 32 字节
  2. 数据格式错误:确保 JSON 格式正确

    • 字段名和类型必须匹配
    • 时间戳必须是数字类型(毫秒)
  3. IV 生成错误:确保使用安全的随机数生成器生成 12 字节 IV

9.2 平台验证失败

可能原因

  1. taskId 不匹配:确保二维码中的 taskId 与任务二维码中的 taskId 一致
  2. 密钥不匹配:确保 licencefingerprint 与平台绑定时一致
  3. 时间戳过期:平台会验证时间戳,确保时间戳在合理范围内
  4. 认证标签验证失败:数据被篡改或密钥错误

9.3 二维码生成失败

可能原因

  1. 内容过长:如果加密数据过长,可能需要更高版本的二维码
  2. JSON 格式错误:确保 JSON 格式正确
  3. 字符编码错误:确保使用 UTF-8 编码

十、安全设计说明

10.1 为什么使用 HKDF

  1. 密钥分离:使用 info 参数区分不同用途的密钥
  2. Salt 随机性:使用 taskId 作为 salt确保每个任务的密钥不同
  3. 密钥扩展HKDF 提供更好的密钥扩展性

10.2 为什么第三方无法伪造

  1. 密钥绑定:只有拥有正确 licence + fingerprint 的工具箱才能生成正确的密钥
  2. 任务绑定:使用 taskId 作为 salt确保密钥与特定任务绑定
  3. 认证加密GCM 模式提供认证加密,任何篡改都会导致解密失败
  4. 时间戳校验:平台会验证时间戳,防止重放攻击

10.3 密钥派生参数的重要性

  • ikmlicence + fingerprint 是设备唯一标识
  • salttaskId 确保每个任务使用不同的密钥
  • info"inspection_report_encryption" 区分不同用途的密钥
  • length32 字节提供 256 位密钥强度

十一、测试建议

  1. 单元测试

    • 测试密钥派生是否正确
    • 测试加密和解密是否匹配
    • 测试 JSON 格式是否正确
  2. 集成测试

    • 使用真实任务数据生成二维码
    • App 扫描二维码并提交到平台
    • 验证平台是否能正确解密和验证
  3. 边界测试

    • 测试超长的摘要信息
    • 测试特殊字符的处理
    • 测试错误的 taskId 是否会导致解密失败

十二、参考实现

  • PythonhkdfHKDFcryptographyAES-GCMqrcode 库(二维码生成)
  • Java/KotlinBouncyCastleHKDF、JDK javax.cryptoAES-GCM、ZXing 库(二维码生成)
  • C#BouncyCastle.NetHKDFSystem.Security.CryptographyAES-GCM、ZXing.Net 库(二维码生成)

十三、联系支持

如有问题,请联系平台技术支持团队获取:

  • 测试环境地址
  • 技术支持