# 工具箱端 - 设备授权二维码生成指南
## 概述
本文档说明工具箱端如何生成设备授权二维码,用于设备首次授权和绑定。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 编码)
- 测试环境地址
- 技术支持