# 工具箱端 - 报告加密与签名生成指南 ## 概述 本文档说明工具箱端如何生成加密和签名的检查报告 ZIP 文件,以确保: 1. **授权校验**:只有合法授权的工具箱才能生成有效的报告 2. **防篡改校验**:确保报告内容在传输过程中未被篡改 > ### UX 集成模式补充(当前项目实现) > > 在当前集成模式中,工具箱可将原始报告 ZIP 直接上传到 UX 的 `crypto.signAndPackReport`: > > 1. UX 校验 ZIP 并提取必需文件; > 2. UX 生成 `deviceSignature`、`summary.json`、`META-INF/manifest.json`、`META-INF/signature.asc`; > 3. UX 重新打包并返回签名后的 ZIP(Base64),工具箱再用于离线介质回传平台。 ## 一、ZIP 文件结构要求 工具箱生成的 ZIP 文件必须包含以下文件: ``` report.zip ├── summary.json # 摘要信息(必须包含授权和签名字段) ├── assets.json # 资产信息(用于签名校验) ├── vulnerabilities.json # 漏洞信息(用于签名校验) ├── weakPasswords.json # 弱密码信息(用于签名校验) ├── 漏洞评估报告.html # 漏洞评估报告(用于签名校验) └── META-INF/ ├── manifest.json # 文件清单(用于 OpenPGP 签名) └── signature.asc # OpenPGP 签名文件(防篡改) ``` ## 二、授权校验 - 设备签名(device_signature) ### 2.1 目的 设备签名用于验证报告是由合法授权的工具箱生成的,防止第三方伪造扫描结果。 ### 2.2 密钥派生 使用 **HKDF-SHA256** 从设备的 `licence` 和 `fingerprint` 派生签名密钥: ``` K = HKDF( input = licence + fingerprint, # 输入密钥材料(字符串拼接) salt = "AUTH_V3_SALT", # 固定盐值 info = "device_report_signature", # 固定信息参数 hash = SHA-256, # 哈希算法 length = 32 # 输出密钥长度(32字节 = 256位) ) ``` **伪代码示例**: ```python import hkdf # 输入密钥材料 ikm = licence + fingerprint # 字符串直接拼接 # HKDF 参数 salt = "AUTH_V3_SALT" info = "device_report_signature" 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() ``` ### 2.3 签名数据组装(严格顺序) 签名数据必须按照以下**严格顺序**组装: ``` sign_payload = taskId + # 任务ID(字符串) inspectionId + # 检查ID(数字转字符串) SHA256(assets.json) + # assets.json 的 SHA256(hex字符串,小写) SHA256(vulnerabilities.json) + # vulnerabilities.json 的 SHA256(hex字符串,小写) SHA256(weakPasswords.json) + # weakPasswords.json 的 SHA256(hex字符串,小写) SHA256(漏洞评估报告.html) # 漏洞评估报告.html 的 SHA256(hex字符串,小写) ``` **重要说明**: - 所有字符串直接拼接,**不添加任何分隔符** - SHA256 哈希值必须是 **hex 字符串(小写)**,例如:`a1b2c3d4...` - 文件内容必须是**原始字节**,不能进行任何编码转换 - 顺序必须严格一致,任何顺序错误都会导致签名验证失败 **伪代码示例**: ```python import hashlib # 1. 读取文件内容(原始字节) assets_content = read_file("assets.json") vulnerabilities_content = read_file("vulnerabilities.json") weak_passwords_content = read_file("weakPasswords.json") report_html_content = read_file("漏洞评估报告.html") # 2. 计算 SHA256(hex字符串,小写) def sha256_hex(content: bytes) -> str: return hashlib.sha256(content).hexdigest() assets_sha256 = sha256_hex(assets_content) vulnerabilities_sha256 = sha256_hex(vulnerabilities_content) weak_passwords_sha256 = sha256_hex(weak_passwords_content) report_html_sha256 = sha256_hex(report_html_content) # 3. 组装签名数据(严格顺序,直接拼接) sign_payload = ( str(task_id) + str(inspection_id) + assets_sha256 + vulnerabilities_sha256 + weak_passwords_sha256 + report_html_sha256 ) ``` ### 2.4 生成设备签名 使用 **HMAC-SHA256** 计算签名: ``` device_signature = Base64(HMAC-SHA256(key=K, data=sign_payload)) ``` **伪代码示例**: ```python import hmac import base64 # 使用派生密钥计算 HMAC-SHA256 mac = hmac.new( key=derived_key, # 派生密钥(32字节) msg=sign_payload.encode('utf-8'), # 签名数据(UTF-8编码) digestmod=hashlib.sha256 ) # 计算签名 signature_bytes = mac.digest() # Base64 编码 device_signature = base64.b64encode(signature_bytes).decode('utf-8') ``` ### 2.5 写入 summary.json 将 `device_signature` 写入 `summary.json`: ```json { "orgId": 1173040813421105152, "checkId": 702286470691215417, "taskId": "TASK-20260115-4875", "licence": "LIC-8F2A-XXXX", "fingerprint": "FP-2c91e9f3", "deviceSignature": "Base64编码的签名值", "summary": "检查摘要信息", ...其他字段... } ``` **必需字段**: - `licence`:设备授权码(字符串) - `fingerprint`:设备硬件指纹(字符串) - `taskId`:任务ID(字符串) - `deviceSignature`:设备签名(Base64字符串) - `checkId` 或 `inspectionId`:检查ID(数字) ## 三、防篡改校验 - OpenPGP 签名 ### 3.1 目的 OpenPGP 签名用于验证 ZIP 文件在传输过程中未被篡改,确保文件完整性。 ### 3.2 生成 manifest.json 创建 `META-INF/manifest.json` 文件,包含所有文件的 SHA-256 哈希值: ```json { "files": { "summary.json": "a1b2c3d4e5f6...", "assets.json": "b2c3d4e5f6a1...", "vulnerabilities.json": "c3d4e5f6a1b2...", "weakPasswords.json": "d4e5f6a1b2c3...", "漏洞评估报告.html": "e5f6a1b2c3d4..." } } ``` **伪代码示例**: ```python import hashlib import json def calculate_sha256_hex(content: bytes) -> str: return hashlib.sha256(content).hexdigest() # 计算所有文件的 SHA256 files_hashes = { "summary.json": calculate_sha256_hex(summary_content), "assets.json": calculate_sha256_hex(assets_content), "vulnerabilities.json": calculate_sha256_hex(vulnerabilities_content), "weakPasswords.json": calculate_sha256_hex(weak_passwords_content), "漏洞评估报告.html": calculate_sha256_hex(report_html_content) } # 生成 manifest.json manifest = { "files": files_hashes } manifest_json = json.dumps(manifest, ensure_ascii=False, indent=2) ``` ### 3.3 生成 OpenPGP 签名 使用工具箱的**私钥**对 `manifest.json` 进行 OpenPGP 签名,生成 `META-INF/signature.asc`: **伪代码示例(使用 Python gnupg)**: ```python import gnupg # 初始化 GPG gpg = gnupg.GPG() # 导入私钥(或使用已配置的密钥) # gpg.import_keys(private_key_data) # 对 manifest.json 进行签名 with open('META-INF/manifest.json', 'rb') as f: signed_data = gpg.sign_file( f, detach=True, # 分离式签名 clearsign=False, # 不使用明文签名 output='META-INF/signature.asc' ) ``` **伪代码示例(使用 BouncyCastle - Java/Kotlin)**: ```kotlin import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder import org.bouncycastle.openpgp.operator.jcajce.JcaPGPPrivateKey import java.io.ByteArrayOutputStream import java.io.FileOutputStream fun generatePGPSignature( manifestContent: ByteArray, privateKey: PGPPrivateKey, publicKey: PGPPublicKey ): ByteArray { val signatureGenerator = PGPSignatureGenerator( JcaPGPContentSignerBuilder(publicKey.algorithm, PGPUtil.SHA256) ) signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey) signatureGenerator.update(manifestContent) val signature = signatureGenerator.generate() val signatureList = PGPSignatureList(signature) val out = ByteArrayOutputStream() val pgpOut = PGPObjectFactory(PGPUtil.getEncoderStream(out)) signatureList.encode(out) return out.toByteArray() } ``` ### 3.4 打包 ZIP 文件 将所有文件打包成 ZIP 文件,确保包含: - 所有报告文件(summary.json、assets.json 等) - `META-INF/manifest.json` - `META-INF/signature.asc` **伪代码示例**: ```python import zipfile def create_signed_zip(output_path: str): with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf: # 添加报告文件 zipf.write('summary.json', 'summary.json') zipf.write('assets.json', 'assets.json') zipf.write('vulnerabilities.json', 'vulnerabilities.json') zipf.write('weakPasswords.json', 'weakPasswords.json') zipf.write('漏洞评估报告.html', '漏洞评估报告.html') # 添加签名文件 zipf.write('META-INF/manifest.json', 'META-INF/manifest.json') zipf.write('META-INF/signature.asc', 'META-INF/signature.asc') ``` ## 四、完整流程示例 ### 4.1 Python 完整示例 ```python import hashlib import hmac import base64 import json import zipfile import hkdf import gnupg def generate_report_zip( licence: str, fingerprint: str, task_id: str, inspection_id: int, output_path: str ): """ 生成带签名和加密的检查报告 ZIP 文件 """ # ========== 1. 读取报告文件 ========== assets_content = read_file("assets.json") vulnerabilities_content = read_file("vulnerabilities.json") weak_passwords_content = read_file("weakPasswords.json") report_html_content = read_file("漏洞评估报告.html") # ========== 2. 生成设备签名 ========== # 2.1 密钥派生 ikm = licence + fingerprint salt = "AUTH_V3_SALT" info = "device_report_signature" key_length = 32 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() # 2.2 计算文件 SHA256 def sha256_hex(content: bytes) -> str: return hashlib.sha256(content).hexdigest() assets_sha256 = sha256_hex(assets_content) vulnerabilities_sha256 = sha256_hex(vulnerabilities_content) weak_passwords_sha256 = sha256_hex(weak_passwords_content) report_html_sha256 = sha256_hex(report_html_content) # 2.3 组装签名数据(严格顺序) sign_payload = ( str(task_id) + str(inspection_id) + assets_sha256 + vulnerabilities_sha256 + weak_passwords_sha256 + report_html_sha256 ) # 2.4 计算 HMAC-SHA256 mac = hmac.new( key=derived_key, msg=sign_payload.encode('utf-8'), digestmod=hashlib.sha256 ) device_signature = base64.b64encode(mac.digest()).decode('utf-8') # 2.5 生成 summary.json summary = { "orgId": 1173040813421105152, "checkId": inspection_id, "taskId": task_id, "licence": licence, "fingerprint": fingerprint, "deviceSignature": device_signature, "summary": "检查摘要信息" } summary_content = json.dumps(summary, ensure_ascii=False).encode('utf-8') # ========== 3. 生成 OpenPGP 签名 ========== # 3.1 生成 manifest.json files_hashes = { "summary.json": sha256_hex(summary_content), "assets.json": assets_sha256, "vulnerabilities.json": vulnerabilities_sha256, "weakPasswords.json": weak_passwords_sha256, "漏洞评估报告.html": report_html_sha256 } manifest = {"files": files_hashes} manifest_content = json.dumps(manifest, ensure_ascii=False, indent=2).encode('utf-8') # 3.2 生成 OpenPGP 签名 gpg = gnupg.GPG() with open('META-INF/manifest.json', 'wb') as f: f.write(manifest_content) with open('META-INF/manifest.json', 'rb') as f: signed_data = gpg.sign_file( f, detach=True, output='META-INF/signature.asc' ) # ========== 4. 打包 ZIP 文件 ========== with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf: zipf.writestr('summary.json', summary_content) zipf.writestr('assets.json', assets_content) zipf.writestr('vulnerabilities.json', vulnerabilities_content) zipf.writestr('weakPasswords.json', weak_passwords_content) zipf.writestr('漏洞评估报告.html', report_html_content) zipf.writestr('META-INF/manifest.json', manifest_content) zipf.write('META-INF/signature.asc', 'META-INF/signature.asc') print(f"报告 ZIP 文件生成成功: {output_path}") ``` ### 4.2 Java/Kotlin 完整示例 ```kotlin import org.bouncycastle.crypto.digests.SHA256Digest import org.bouncycastle.crypto.generators.HKDFBytesGenerator import org.bouncycastle.crypto.params.HKDFParameters import java.security.MessageDigest import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec import java.util.Base64 import java.util.zip.ZipOutputStream import java.io.FileOutputStream fun generateReportZip( licence: String, fingerprint: String, taskId: String, inspectionId: Long, outputPath: String ) { // ========== 1. 读取报告文件 ========== val assetsContent = readFile("assets.json") val vulnerabilitiesContent = readFile("vulnerabilities.json") val weakPasswordsContent = readFile("weakPasswords.json") val reportHtmlContent = readFile("漏洞评估报告.html") // ========== 2. 生成设备签名 ========== // 2.1 密钥派生 val ikm = (licence + fingerprint).toByteArray(Charsets.UTF_8) val salt = "AUTH_V3_SALT".toByteArray(Charsets.UTF_8) val info = "device_report_signature".toByteArray(Charsets.UTF_8) val keyLength = 32 val hkdf = HKDFBytesGenerator(SHA256Digest()) hkdf.init(HKDFParameters(ikm, salt, info)) val derivedKey = ByteArray(keyLength) hkdf.generateBytes(derivedKey, 0, keyLength) // 2.2 计算文件 SHA256 fun sha256Hex(content: ByteArray): String { val digest = MessageDigest.getInstance("SHA-256") val hashBytes = digest.digest(content) return hashBytes.joinToString("") { "%02x".format(it) } } val assetsSha256 = sha256Hex(assetsContent) val vulnerabilitiesSha256 = sha256Hex(vulnerabilitiesContent) val weakPasswordsSha256 = sha256Hex(weakPasswordsContent) val reportHtmlSha256 = sha256Hex(reportHtmlContent) // 2.3 组装签名数据(严格顺序) val signPayload = buildString { append(taskId) append(inspectionId) append(assetsSha256) append(vulnerabilitiesSha256) append(weakPasswordsSha256) append(reportHtmlSha256) } // 2.4 计算 HMAC-SHA256 val mac = Mac.getInstance("HmacSHA256") val secretKey = SecretKeySpec(derivedKey, "HmacSHA256") mac.init(secretKey) val signatureBytes = mac.doFinal(signPayload.toByteArray(Charsets.UTF_8)) val deviceSignature = Base64.getEncoder().encodeToString(signatureBytes) // 2.5 生成 summary.json val summary = mapOf( "orgId" to 1173040813421105152L, "checkId" to inspectionId, "taskId" to taskId, "licence" to licence, "fingerprint" to fingerprint, "deviceSignature" to deviceSignature, "summary" to "检查摘要信息" ) val summaryContent = objectMapper.writeValueAsString(summary).toByteArray(Charsets.UTF_8) // ========== 3. 生成 OpenPGP 签名 ========== // 3.1 生成 manifest.json val filesHashes = mapOf( "summary.json" to sha256Hex(summaryContent), "assets.json" to assetsSha256, "vulnerabilities.json" to vulnerabilitiesSha256, "weakPasswords.json" to weakPasswordsSha256, "漏洞评估报告.html" to reportHtmlSha256 ) val manifest = mapOf("files" to filesHashes) val manifestContent = objectMapper.writeValueAsString(manifest).toByteArray(Charsets.UTF_8) // 3.2 生成 OpenPGP 签名(使用 BouncyCastle) val signatureAsc = generatePGPSignature(manifestContent, privateKey, publicKey) // ========== 4. 打包 ZIP 文件 ========== ZipOutputStream(FileOutputStream(outputPath)).use { zipOut -> zipOut.putNextEntry(ZipEntry("summary.json")) zipOut.write(summaryContent) zipOut.closeEntry() zipOut.putNextEntry(ZipEntry("assets.json")) zipOut.write(assetsContent) zipOut.closeEntry() zipOut.putNextEntry(ZipEntry("vulnerabilities.json")) zipOut.write(vulnerabilitiesContent) zipOut.closeEntry() zipOut.putNextEntry(ZipEntry("weakPasswords.json")) zipOut.write(weakPasswordsContent) zipOut.closeEntry() zipOut.putNextEntry(ZipEntry("漏洞评估报告.html")) zipOut.write(reportHtmlContent) zipOut.closeEntry() zipOut.putNextEntry(ZipEntry("META-INF/manifest.json")) zipOut.write(manifestContent) zipOut.closeEntry() zipOut.putNextEntry(ZipEntry("META-INF/signature.asc")) zipOut.write(signatureAsc) zipOut.closeEntry() } println("报告 ZIP 文件生成成功: $outputPath") } ``` ## 五、平台端验证流程 平台端会按以下顺序验证: 1. **OpenPGP 签名验证**(防篡改) - 读取 `META-INF/manifest.json` 和 `META-INF/signature.asc` - 使用平台公钥验证签名 - 验证所有文件的 SHA256 是否与 manifest.json 中的哈希值匹配 2. **设备签名验证**(授权) - 从 `summary.json` 提取 `licence`、`fingerprint`、`taskId`、`deviceSignature` - 验证 `licence + fingerprint` 是否已绑定 - 验证 `taskId` 是否存在且属于该设备 - 使用相同的 HKDF 派生密钥 - 重新计算签名并与 `deviceSignature` 比较 ## 六、常见错误和注意事项 ### 6.1 设备签名验证失败 **可能原因**: 1. **密钥派生错误**:确保使用正确的 `salt` 和 `info` 参数 2. **签名数据顺序错误**:必须严格按照 `taskId + inspectionId + SHA256(...)` 的顺序 3. **SHA256 格式错误**:必须是 hex 字符串(小写),不能包含分隔符 4. **文件内容错误**:确保使用原始文件内容,不能进行编码转换 5. **licence 或 fingerprint 不匹配**:确保与平台绑定的值一致 ### 6.2 OpenPGP 签名验证失败 **可能原因**: 1. **私钥不匹配**:确保使用与平台公钥对应的私钥 2. **manifest.json 格式错误**:确保 JSON 格式正确 3. **文件哈希值错误**:确保 manifest.json 中的哈希值与实际文件匹配 ### 6.3 文件缺失 **必需文件**: - `summary.json`(必须包含授权字段) - `assets.json` - `vulnerabilities.json` - `weakPasswords.json`(文件名大小写不敏感) - `漏洞评估报告.html`(文件名包含"漏洞评估报告"且以".html"结尾) - `META-INF/manifest.json` - `META-INF/signature.asc` ## 七、安全设计说明 ### 7.1 为什么第三方无法伪造 1. **设备签名**: - 只有拥有正确 `licence + fingerprint` 的设备才能派生正确的签名密钥 - 即使第三方获取了某个设备的签名,也无法用于其他任务(`taskId` 绑定) - 即使第三方修改了报告内容,签名也会失效(多个文件的 SHA256 绑定) 2. **OpenPGP 签名**: - 只有拥有私钥的工具箱才能生成有效签名 - 任何文件修改都会导致哈希值不匹配 ### 7.2 密钥分离 使用 HKDF 的 `info` 参数区分不同用途的密钥: - `device_report_signature`:用于设备签名 - 其他用途可以使用不同的 `info` 值,确保密钥隔离 ## 八、测试建议 1. **单元测试**: - 测试密钥派生是否正确 - 测试签名生成和验证是否匹配 - 测试文件 SHA256 计算是否正确 2. **集成测试**: - 使用真实数据生成 ZIP 文件 - 上传到平台验证是否通过 - 测试篡改文件后验证是否失败 3. **边界测试**: - 测试文件缺失的情况 - 测试签名数据顺序错误的情况 - 测试错误的 `licence` 或 `fingerprint` 的情况 ## 九、参考实现 - **HKDF 实现**:BouncyCastle(Java/Kotlin)、`hkdf` 库(Python) - **HMAC-SHA256**:Java `javax.crypto.Mac`、Python `hmac` - **OpenPGP**:BouncyCastle(Java/Kotlin)、`gnupg` 库(Python) ## 十、联系支持 如有问题,请联系平台技术支持团队。