# 工具箱端 - 设备授权二维码生成指南 ## 概述 本文档说明工具箱端如何生成设备授权二维码,用于设备首次授权和绑定。App 扫描二维码后,会将加密的设备信息提交到平台完成授权校验和绑定。 > ### UX 集成模式补充(当前项目实现) > > 在当前集成模式中,工具箱不直接执行 RSA 加密,而是调用 UX 接口: > > 1. 工具箱先调用 `device.register` 传入 `licence` 与平台公钥,`fingerprint` 由 UX 本机计算并入库。 > 2. 工具箱再调用 `crypto.encryptDeviceInfo` 获取加密后的 Base64 密文。 > 3. 工具箱将该密文生成二维码供 App 扫码提交平台。 ## 一、业务流程 ``` 工具箱 → 生成设备信息 → RSA-OAEP加密 → Base64编码 → 生成二维码 ↓ App扫描二维码 → 提取加密数据 → 调用平台接口 → 平台解密验证 → 授权成功 ``` ## 二、设备信息准备 ### 2.1 设备信息字段 工具箱需要准备以下设备信息: | 字段名 | 类型 | 说明 | 示例 | |--------|------|------|------| | `licence` | String | 设备授权码(工具箱唯一标识) | `"LIC-8F2A-XXXX"` | | `fingerprint` | String | 设备硬件指纹(设备唯一标识) | `"FP-2c91e9f3"` | ### 2.2 生成设备信息 JSON 将设备信息组装成 JSON 格式: ```json { "licence": "LIC-8F2A-XXXX", "fingerprint": "FP-2c91e9f3" } ``` **重要说明**: - `licence` 和 `fingerprint` 必须是字符串类型 - JSON 格式必须正确,不能有多余的逗号或格式错误 - 建议使用标准的 JSON 库生成,避免手动拼接 **伪代码示例**: ```python import json device_info = { "licence": "LIC-8F2A-XXXX", # 工具箱授权码 "fingerprint": "FP-2c91e9f3" # 设备硬件指纹 } # 转换为 JSON 字符串 device_info_json = json.dumps(device_info, ensure_ascii=False) # 结果: {"licence":"LIC-8F2A-XXXX","fingerprint":"FP-2c91e9f3"} ``` ## 三、RSA-OAEP 加密 ### 3.1 加密算法 使用 **RSA-OAEP** 非对称加密算法: - **算法名称**:`RSA/ECB/OAEPWithSHA-256AndMGF1Padding` - **密钥长度**:2048 位(推荐) - **填充方式**:OAEP with SHA-256 and MGF1 - **加密方向**:使用**平台公钥**加密,平台使用私钥解密 ### 3.2 获取平台公钥 平台公钥需要从平台获取,通常以 **Base64 编码**的字符串形式提供。 **公钥格式**: - 格式:X.509 标准格式(DER 编码) - 存储:Base64 编码的字符串 - 示例:`MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzDlZvMDVaL+fjl05Hi182JOAUAaN4gh9rOF+1NhKfO4J6e0HLy8lBuylp3A4xoTiyUejNm22h0dqAgDSPnY/xZR76POFTD1soHr2LaFCN8JAbQ96P8gE7wC9qpoTssVvIVRH7QbVd260J6eD0Szwcx9cg591RSN69pMpe5IVRi8T99Hhql6/wnZHORPr18eESLOY93jRskLzc0q18r68RRoTJiQf+9YC8ub5iKp7rCjVnPi1UbIYmXmL08tk5mksYA0NqWQAa1ofKxx/9tQtB9uTjhTxuTu94XU9jlGU87qaHZs+kpqa8CAbYYJFbSP1xHwoZzpU2jpw2aF22HBYxwIDAQAB` ### 3.3 加密步骤 1. **加载平台公钥**:从 Base64 字符串加载公钥对象 2. **初始化加密器**:使用 `RSA/ECB/OAEPWithSHA-256AndMGF1Padding` 算法 3. **加密数据**:使用公钥加密设备信息 JSON 字符串(UTF-8 编码) 4. **Base64 编码**:将加密后的字节数组进行 Base64 编码 ### 3.4 Python 实现示例 ```python import base64 import json from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import serialization from cryptography.hazmat.backends import default_backend def encrypt_device_info(licence: str, fingerprint: str, platform_public_key_base64: str) -> str: """ 使用平台公钥加密设备信息 Args: licence: 设备授权码 fingerprint: 设备硬件指纹 platform_public_key_base64: 平台公钥(Base64编码) Returns: Base64编码的加密数据 """ # 1. 组装设备信息 JSON device_info = { "licence": licence, "fingerprint": fingerprint } device_info_json = json.dumps(device_info, ensure_ascii=False) # 2. 加载平台公钥 public_key_bytes = base64.b64decode(platform_public_key_base64) public_key = serialization.load_der_public_key( public_key_bytes, backend=default_backend() ) # 3. 使用 RSA-OAEP 加密 # OAEP padding with SHA-256 and MGF1 encrypted_bytes = public_key.encrypt( device_info_json.encode('utf-8'), padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None ) ) # 4. Base64 编码 encrypted_base64 = base64.b64encode(encrypted_bytes).decode('utf-8') return encrypted_base64 ``` ### 3.5 Java/Kotlin 实现示例 ```kotlin import java.security.KeyFactory import java.security.PublicKey import java.security.spec.X509EncodedKeySpec import java.util.Base64 import javax.crypto.Cipher import java.nio.charset.StandardCharsets object DeviceAuthorizationUtil { private const val CIPHER_ALGORITHM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" /** * 使用平台公钥加密设备信息 * * @param licence 设备授权码 * @param fingerprint 设备硬件指纹 * @param platformPublicKeyBase64 平台公钥(Base64编码) * @return Base64编码的加密数据 */ fun encryptDeviceInfo( licence: String, fingerprint: String, platformPublicKeyBase64: String ): String { // 1. 组装设备信息 JSON val deviceInfo = mapOf( "licence" to licence, "fingerprint" to fingerprint ) val deviceInfoJson = objectMapper.writeValueAsString(deviceInfo) // 2. 加载平台公钥 val publicKeyBytes = Base64.getDecoder().decode(platformPublicKeyBase64) val keySpec = X509EncodedKeySpec(publicKeyBytes) val keyFactory = KeyFactory.getInstance("RSA") val publicKey = keyFactory.generatePublic(keySpec) // 3. 使用 RSA-OAEP 加密 val cipher = Cipher.getInstance(CIPHER_ALGORITHM) cipher.init(Cipher.ENCRYPT_MODE, publicKey) val encryptedBytes = cipher.doFinal(deviceInfoJson.toByteArray(StandardCharsets.UTF_8)) // 4. Base64 编码 return Base64.getEncoder().encodeToString(encryptedBytes) } } ``` ### 3.6 C# 实现示例 ```csharp using System; using System.Security.Cryptography; using System.Text; using System.Text.Json; public class DeviceAuthorizationUtil { private const string CipherAlgorithm = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; /// /// 使用平台公钥加密设备信息 /// public static string EncryptDeviceInfo( string licence, string fingerprint, string platformPublicKeyBase64) { // 1. 组装设备信息 JSON var deviceInfo = new { licence = licence, fingerprint = fingerprint }; var deviceInfoJson = JsonSerializer.Serialize(deviceInfo); // 2. 加载平台公钥 var publicKeyBytes = Convert.FromBase64String(platformPublicKeyBase64); using var rsa = RSA.Create(); rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _); // 3. 使用 RSA-OAEP 加密 var encryptedBytes = rsa.Encrypt( Encoding.UTF8.GetBytes(deviceInfoJson), RSAEncryptionPadding.OaepSHA256 ); // 4. Base64 编码 return Convert.ToBase64String(encryptedBytes); } } ``` ## 四、生成二维码 ### 4.1 二维码内容 二维码内容就是加密后的 **Base64 编码字符串**(不是 JSON 格式)。 **示例**: ``` MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzDlZvMDVaL+fjl05Hi182JOAUAaN4gh9rOF+1NhKfO4J6e0HLy8lBuylp3A4xoTiyUejNm22h0dqAgDSPnY/xZR76POFTD1soHr2LaFCN8JAbQ96P8gE7wC9qpoTssVvIVRH7QbVd260J6eD0Szwcx9cg591RSN69pMpe5IVRi8T99Hhql6/wnZHORPr18eESLOY93jRskLzc0q18r68RRoTJiQf+9YC8ub5iKp7rCjVnPi1UbIYmXmL08tk5mksYA0NqWQAa1ofKxx/9tQtB9uTjhTxuTu94XU9jlGU87qaHZs+kpqa8CAbYYJFbSP1xHwoZzpU2jpw2aF22HBYxwIDAQAB... ``` ### 4.2 二维码生成 使用标准的二维码生成库生成二维码图片。 **Python 示例(使用 qrcode 库)**: ```python import qrcode from PIL import Image def generate_qr_code(encrypted_data: str, output_path: str = "device_qr.png"): """ 生成设备授权二维码 Args: encrypted_data: Base64编码的加密数据 output_path: 二维码图片保存路径 """ qr = qrcode.QRCode( version=1, # 控制二维码大小(1-40) error_correction=qrcode.constants.ERROR_CORRECT_M, # 错误纠正级别 box_size=10, # 每个小方块的像素数 border=4, # 边框的厚度 ) qr.add_data(encrypted_data) qr.make(fit=True) # 创建二维码图片 img = qr.make_image(fill_color="black", back_color="white") img.save(output_path) print(f"二维码已生成: {output_path}") ``` **Java/Kotlin 示例(使用 ZXing 库)**: ```kotlin import com.google.zxing.BarcodeFormat import com.google.zxing.EncodeHintType import com.google.zxing.qrcode.QRCodeWriter import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel import java.awt.image.BufferedImage import javax.imageio.ImageIO import java.io.File fun generateQRCode(encryptedData: String, outputPath: String = "device_qr.png") { val hints = hashMapOf().apply { put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M) put(EncodeHintType.CHARACTER_SET, "UTF-8") put(EncodeHintType.MARGIN, 1) } val writer = QRCodeWriter() val bitMatrix = writer.encode(encryptedData, 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") } ``` ## 五、完整流程示例 ### 5.1 Python 完整示例 ```python import json import base64 import qrcode from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import serialization from cryptography.hazmat.backends import default_backend def generate_device_authorization_qr( licence: str, fingerprint: str, platform_public_key_base64: str, qr_output_path: str = "device_qr.png" ) -> str: """ 生成设备授权二维码 Args: licence: 设备授权码 fingerprint: 设备硬件指纹 platform_public_key_base64: 平台公钥(Base64编码) qr_output_path: 二维码图片保存路径 Returns: 加密后的Base64字符串(二维码内容) """ # 1. 组装设备信息 JSON device_info = { "licence": licence, "fingerprint": fingerprint } device_info_json = json.dumps(device_info, ensure_ascii=False) print(f"设备信息 JSON: {device_info_json}") # 2. 加载平台公钥 public_key_bytes = base64.b64decode(platform_public_key_base64) public_key = serialization.load_der_public_key( public_key_bytes, backend=default_backend() ) # 3. 使用 RSA-OAEP 加密 encrypted_bytes = public_key.encrypt( device_info_json.encode('utf-8'), padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None ) ) # 4. Base64 编码 encrypted_base64 = base64.b64encode(encrypted_bytes).decode('utf-8') print(f"加密后的 Base64: {encrypted_base64[:100]}...") # 只显示前100个字符 # 5. 生成二维码 qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=10, border=4, ) qr.add_data(encrypted_base64) qr.make(fit=True) img = qr.make_image(fill_color="black", back_color="white") img.save(qr_output_path) print(f"二维码已生成: {qr_output_path}") return encrypted_base64 # 使用示例 if __name__ == "__main__": # 平台公钥(示例,实际使用时需要从平台获取) platform_public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzDlZvMDVaL+fjl05Hi182JOAUAaN4gh9rOF+1NhKfO4J6e0HLy8lBuylp3A4xoTiyUejNm22h0dqAgDSPnY/xZR76POFTD1soHr2LaFCN8JAbQ96P8gE7wC9qpoTssVvIVRH7QbVd260J6eD0Szwcx9cg591RSN69pMpe5IVRi8T99Hhql6/wnZHORPr18eESLOY93jRskLzc0q18r68RRoTJiQf+9YC8ub5iKp7rCjVnPi1UbIYmXmL08tk5mksYA0NqWQAa1ofKxx/9tQtB9uTjhTxuTu94XU9jlGU87qaHZs+kpqa8CAbYYJFbSP1xHwoZzpU2jpw2aF22HBYxwIDAQAB" # 设备信息 licence = "LIC-8F2A-XXXX" fingerprint = "FP-2c91e9f3" # 生成二维码 encrypted_data = generate_device_authorization_qr( licence=licence, fingerprint=fingerprint, platform_public_key_base64=platform_public_key, qr_output_path="device_authorization_qr.png" ) print(f"\n二维码内容(加密后的Base64):\n{encrypted_data}") ``` ### 5.2 Java/Kotlin 完整示例 ```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 java.awt.image.BufferedImage import java.security.KeyFactory import java.security.PublicKey import java.security.spec.X509EncodedKeySpec import java.util.Base64 import javax.crypto.Cipher import javax.imageio.ImageIO import java.io.File import java.nio.charset.StandardCharsets object DeviceAuthorizationQRGenerator { private const val CIPHER_ALGORITHM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" private val objectMapper = ObjectMapper() /** * 生成设备授权二维码 */ fun generateDeviceAuthorizationQR( licence: String, fingerprint: String, platformPublicKeyBase64: String, qrOutputPath: String = "device_qr.png" ): String { // 1. 组装设备信息 JSON val deviceInfo = mapOf( "licence" to licence, "fingerprint" to fingerprint ) val deviceInfoJson = objectMapper.writeValueAsString(deviceInfo) println("设备信息 JSON: $deviceInfoJson") // 2. 加载平台公钥 val publicKeyBytes = Base64.getDecoder().decode(platformPublicKeyBase64) val keySpec = X509EncodedKeySpec(publicKeyBytes) val keyFactory = KeyFactory.getInstance("RSA") val publicKey = keyFactory.generatePublic(keySpec) // 3. 使用 RSA-OAEP 加密 val cipher = Cipher.getInstance(CIPHER_ALGORITHM) cipher.init(Cipher.ENCRYPT_MODE, publicKey) val encryptedBytes = cipher.doFinal(deviceInfoJson.toByteArray(StandardCharsets.UTF_8)) // 4. Base64 编码 val encryptedBase64 = Base64.getEncoder().encodeToString(encryptedBytes) println("加密后的 Base64: ${encryptedBase64.take(100)}...") // 5. 生成二维码 val hints = hashMapOf().apply { put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M) put(EncodeHintType.CHARACTER_SET, "UTF-8") put(EncodeHintType.MARGIN, 1) } val writer = QRCodeWriter() val bitMatrix = writer.encode(encryptedBase64, 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(qrOutputPath)) println("二维码已生成: $qrOutputPath") return encryptedBase64 } } // 使用示例 fun main() { // 平台公钥(示例,实际使用时需要从平台获取) val platformPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzDlZvMDVaL+fjl05Hi182JOAUAaN4gh9rOF+1NhKfO4J6e0HLy8lBuylp3A4xoTiyUejNm22h0dqAgDSPnY/xZR76POFTD1soHr2LaFCN8JAbQ96P8gE7wC9qpoTssVvIVRH7QbVd260J6eD0Szwcx9cg591RSN69pMpe5IVRi8T99Hhql6/wnZHORPr18eESLOY93jRskLzc0q18r68RRoTJiQf+9YC8ub5iKp7rCjVnPi1UbIYmXmL08tk5mksYA0NqWQAa1ofKxx/9tQtB9uTjhTxuTu94XU9jlGU87qaHZs+kpqa8CAbYYJFbSP1xHwoZzpU2jpw2aF22HBYxwIDAQAB" // 设备信息 val licence = "LIC-8F2A-XXXX" val fingerprint = "FP-2c91e9f3" // 生成二维码 val encryptedData = DeviceAuthorizationQRGenerator.generateDeviceAuthorizationQR( licence = licence, fingerprint = fingerprint, platformPublicKeyBase64 = platformPublicKey, qrOutputPath = "device_authorization_qr.png" ) println("\n二维码内容(加密后的Base64):\n$encryptedData") } ``` ## 六、平台端验证流程 平台端会按以下流程验证: 1. **接收请求**:App 扫描二维码后,将 `encryptedDeviceInfo` 和 `appid` 提交到平台 2. **RSA-OAEP 解密**:使用平台私钥解密 `encryptedDeviceInfo` 3. **提取设备信息**:从解密后的 JSON 中提取 `licence` 和 `fingerprint` 4. **设备验证**: - 检查 `filing_device_licence` 表中是否存在该 `licence` - 如果存在,验证 `fingerprint` 是否匹配 - 如果 `fingerprint` 不匹配,记录非法授权日志并返回错误 5. **App 绑定**:检查 `filing_app_licence` 表中是否存在绑定关系 - 如果不存在,创建新的绑定记录 - 如果已存在,返回已绑定信息 6. **返回响应**:返回 `deviceLicenceId` 和 `licence` ## 七、常见错误和注意事项 ### 7.1 加密失败 **可能原因**: 1. **公钥格式错误**:确保使用正确的 Base64 编码的公钥 2. **算法不匹配**:必须使用 `RSA/ECB/OAEPWithSHA-256AndMGF1Padding` 3. **数据长度超限**:RSA-2048 最多加密 245 字节(设备信息 JSON 通常不会超过) 4. **字符编码错误**:确保使用 UTF-8 编码 ### 7.2 二维码扫描失败 **可能原因**: 1. **二维码内容过长**:如果加密后的数据过长,可能需要使用更高版本的二维码(version) 2. **错误纠正级别过低**:建议使用 `ERROR_CORRECT_M` 或更高 3. **二维码图片质量差**:确保二维码图片清晰,有足够的对比度 ### 7.3 平台验证失败 **可能原因**: 1. **licence 已存在但 fingerprint 不匹配**:设备被替换或授权码被复用 2. **JSON 格式错误**:确保 JSON 格式正确,字段名和类型匹配 3. **加密数据损坏**:确保 Base64 编码和解码正确 ## 八、安全设计说明 ### 8.1 为什么使用 RSA-OAEP 1. **非对称加密**:只有平台拥有私钥,可以解密数据 2. **OAEP 填充**:提供更好的安全性,防止某些攻击 3. **SHA-256**:使用强哈希算法,提供更好的安全性 ### 8.2 为什么第三方无法伪造 1. **只有平台能解密**:第三方无法获取平台私钥,无法解密数据 2. **fingerprint 验证**:平台会验证硬件指纹,防止授权码被复用 3. **非法授权日志**:平台会记录所有非法授权尝试 ## 九、测试建议 1. **单元测试**: - 测试 JSON 生成是否正确 - 测试加密和解密是否匹配 - 测试 Base64 编码和解码是否正确 2. **集成测试**: - 使用真实平台公钥生成二维码 - App 扫描二维码并提交到平台 - 验证平台是否能正确解密和验证 3. **边界测试**: - 测试超长的 licence 或 fingerprint - 测试特殊字符的处理 - 测试错误的公钥格式 ## 十、参考实现 - **Python**:`cryptography` 库(RSA 加密)、`qrcode` 库(二维码生成) - **Java/Kotlin**:JDK `javax.crypto`(RSA 加密)、ZXing 库(二维码生成) - **C#**:`System.Security.Cryptography`(RSA 加密)、ZXing.Net 库(二维码生成) ## 十一、联系支持 如有问题,请联系平台技术支持团队获取: - 平台公钥(Base64 编码) - 测试环境地址 - 技术支持