Files
fullstack-starter/docs/工具箱端-授权对接指南/utils/DeviceSignatureUtil.kt

130 lines
4.6 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package top.tangyh.lamp.filing.utils
import io.github.oshai.kotlinlogging.KotlinLogging
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.util.*
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
private val logger = KotlinLogging.logger {}
/**
* 设备签名工具类
* 用于生成和验证设备报告签名
*
* 签名算法HMAC-SHA256
* 签名数据(严格顺序):
* sign_payload = taskId + inspectionId +
* SHA256(assets.json) +
* SHA256(vulnerabilities.json) +
* SHA256(weakPasswords.json) +
* SHA256(漏洞评估报告.html)
*
* 安全设计说明:
* - 使用 HMAC-SHA256 提供消息认证,防止伪造和篡改
* - 签名包含 taskId 和 inspectionId确保签名与特定任务绑定
* - 包含多个报告文件的 SHA256确保报告内容完整性
* - 只有拥有正确 licence + fingerprint 的设备才能生成有效签名
*/
object DeviceSignatureUtil {
private const val HMAC_ALGORITHM = "HmacSHA256"
/**
* 签名数据文件列表(严格顺序)
*/
data class SignatureFileHashes(
val assetsJsonSha256: String,
val vulnerabilitiesJsonSha256: String,
val weakPasswordsJsonSha256: String,
val reportHtmlSha256: String
)
/**
* 生成设备签名
*
* @param key 派生密钥32字节
* @param taskId 任务ID
* @param inspectionId 检查ID
* @param fileHashes 各个文件的 SHA256 哈希值hex字符串
* @return Base64 编码的签名
*/
fun generateSignature(
key: ByteArray,
taskId: String,
inspectionId: Long,
fileHashes: SignatureFileHashes
): String {
return try {
// 组装签名数据(严格顺序):
// taskId + inspectionId + SHA256(assets.json) + SHA256(vulnerabilities.json) +
// SHA256(weakPasswords.json) + SHA256(漏洞评估报告.html)
val signatureData = buildString {
append(taskId)
append(inspectionId)
append(fileHashes.assetsJsonSha256)
append(fileHashes.vulnerabilitiesJsonSha256)
append(fileHashes.weakPasswordsJsonSha256)
append(fileHashes.reportHtmlSha256)
}
val dataBytes = signatureData.toByteArray(StandardCharsets.UTF_8)
// 使用 HMAC-SHA256 计算签名
val mac = Mac.getInstance(HMAC_ALGORITHM)
val secretKey = SecretKeySpec(key, HMAC_ALGORITHM)
mac.init(secretKey)
val signatureBytes = mac.doFinal(dataBytes)
// Base64 编码返回
Base64.getEncoder().encodeToString(signatureBytes)
} catch (e: Exception) {
logger.error(e) { "生成设备签名失败: taskId=$taskId, inspectionId=$inspectionId" }
throw RuntimeException("生成设备签名失败: ${e.message}", e)
}
}
/**
* 验证设备签名
*
* @param key 派生密钥32字节
* @param taskId 任务ID
* @param inspectionId 检查ID
* @param fileHashes 各个文件的 SHA256 哈希值hex字符串
* @param expectedSignature Base64 编码的期望签名
* @return true 如果签名匹配false 否则
*/
fun verifySignature(
key: ByteArray,
taskId: String,
inspectionId: Long,
fileHashes: SignatureFileHashes,
expectedSignature: String
): Boolean {
return try {
val calculatedSignature = generateSignature(key, taskId, inspectionId, fileHashes)
// 使用时间安全的比较,防止时序攻击
MessageDigest.isEqual(
Base64.getDecoder().decode(expectedSignature),
Base64.getDecoder().decode(calculatedSignature)
)
} catch (e: Exception) {
logger.error(e) { "验证设备签名失败: taskId=$taskId, inspectionId=$inspectionId" }
false
}
}
/**
* 计算文件的 SHA256 哈希值hex字符串
*
* @param fileContent 文件内容
* @return SHA256 哈希值的 hex 字符串
*/
fun calculateSha256(fileContent: ByteArray): String {
val digest = MessageDigest.getInstance("SHA-256")
val hashBytes = digest.digest(fileContent)
return hashBytes.joinToString("") { "%02x".format(it) }
}
}