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

121 lines
5.0 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.security.SecureRandom
import java.util.*
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
private val logger = KotlinLogging.logger {}
/**
* 任务加密工具类
* 使用 licence + fingerprint 作为密钥对任务数据进行 AES-256-GCM 对称加密
*
* GCM 模式提供认证加密,比 ECB 模式更安全
* 加密数据格式IV(12字节) + 加密数据 + 认证标签(16字节)
*/
object TaskEncryptionUtil {
private const val ALGORITHM = "AES"
private const val TRANSFORMATION = "AES/GCM/NoPadding"
private const val GCM_IV_LENGTH = 12 // GCM 推荐使用 12 字节 IV
private const val GCM_TAG_LENGTH = 16 // GCM 认证标签长度128位
private const val KEY_LENGTH = 32 // AES-256 密钥长度256位 = 32字节
private val secureRandom = SecureRandom()
/**
* 使用 licence + fingerprint 加密任务数据AES-256-GCM
* @param data 待加密的数据JSON字符串
* @param licence 授权码
* @param fingerprint 硬件指纹
* @return Base64编码的加密数据包含IV + 加密数据 + 认证标签)
*/
fun encrypt(data: String, licence: String, fingerprint: String): String {
return try {
// 使用 licence + fingerprint 生成密钥
val key = generateKey(licence, fingerprint)
// 生成随机 IV12字节
val iv = ByteArray(GCM_IV_LENGTH)
secureRandom.nextBytes(iv)
// 创建加密器
val cipher = Cipher.getInstance(TRANSFORMATION)
val parameterSpec = GCMParameterSpec(GCM_TAG_LENGTH * 8, iv) // 标签长度以位为单位
cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec)
// 加密数据
val encryptedBytes = cipher.doFinal(data.toByteArray(StandardCharsets.UTF_8))
// 组合IV + 加密数据(包含认证标签)
val combined = ByteArray(iv.size + encryptedBytes.size)
System.arraycopy(iv, 0, combined, 0, iv.size)
System.arraycopy(encryptedBytes, 0, combined, iv.size, encryptedBytes.size)
// 返回 Base64 编码的加密数据
Base64.getEncoder().encodeToString(combined)
} catch (e: Exception) {
logger.error(e) { "AES-256-GCM加密任务数据失败" }
throw RuntimeException("加密任务数据失败: ${e.message}", e)
}
}
/**
* 使用 licence + fingerprint 解密任务数据AES-256-GCM
* @param encryptedData Base64编码的加密数据包含IV + 加密数据 + 认证标签)
* @param licence 授权码
* @param fingerprint 硬件指纹
* @return 解密后的数据JSON字符串
*/
fun decrypt(encryptedData: String, licence: String, fingerprint: String): String {
return try {
// 使用 licence + fingerprint 生成密钥
val key = generateKey(licence, fingerprint)
// Base64 解码
val combined = Base64.getDecoder().decode(encryptedData)
// 分离 IV 和加密数据
if (combined.size < GCM_IV_LENGTH) {
throw IllegalArgumentException("加密数据格式错误:数据长度不足")
}
val iv = combined.sliceArray(0 until GCM_IV_LENGTH)
val cipherText = combined.sliceArray(GCM_IV_LENGTH until combined.size)
// 创建解密器
val cipher = Cipher.getInstance(TRANSFORMATION)
val parameterSpec = GCMParameterSpec(GCM_TAG_LENGTH * 8, iv)
cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec)
// 解密数据GCM 会自动验证认证标签)
val decryptedBytes = cipher.doFinal(cipherText)
// 返回解密后的字符串
String(decryptedBytes, StandardCharsets.UTF_8)
} catch (e: Exception) {
logger.error(e) { "AES-256-GCM解密任务数据失败" }
throw RuntimeException("解密任务数据失败: ${e.message}", e)
}
}
/**
* 使用 licence + fingerprint 生成 AES-256 密钥256位 = 32字节
* 使用 SHA-256 哈希的全部32字节作为密钥
*/
private fun generateKey(licence: String, fingerprint: String): SecretKeySpec {
val combined = "$licence$fingerprint"
val digest = MessageDigest.getInstance("SHA-256")
val hash = digest.digest(combined.toByteArray(StandardCharsets.UTF_8))
// 使用全部32字节作为 AES-256 密钥
return SecretKeySpec(hash, ALGORITHM)
}
}