docs: 添加管理平台标准加密算法 Kotlin 参考实现
This commit is contained in:
134
docs/工具箱端-授权对接指南/utils/ZipVerifierUtil.kt
Normal file
134
docs/工具箱端-授权对接指南/utils/ZipVerifierUtil.kt
Normal file
@@ -0,0 +1,134 @@
|
||||
package top.tangyh.lamp.filing.utils
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.bouncycastle.openpgp.*
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import java.security.MessageDigest
|
||||
import java.security.Security
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
object ZipVerifierUtil {
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
// @JvmStatic
|
||||
// fun main(args: Array<String>) {
|
||||
// verifyZip("signed.zip", "public.key")
|
||||
// }
|
||||
|
||||
/**
|
||||
* 验证 ZIP 文件
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
fun verifyZip(zipPath: String, pubkeyContent: String):Boolean {
|
||||
|
||||
println(Security.getProviders().joinToString { it.name })
|
||||
val publicKey = readPublicKey(
|
||||
ByteArrayInputStream(pubkeyContent.toByteArray())
|
||||
)
|
||||
|
||||
val zip = ZipFile(zipPath)
|
||||
|
||||
// 1. 读取 manifest.json
|
||||
val manifestEntry = zip.getEntry("META-INF/manifest.json")
|
||||
?: throw RuntimeException("manifest.json is missing!")
|
||||
val manifestJson = zip.getInputStream(manifestEntry).readAllBytes().toString(Charsets.UTF_8)
|
||||
|
||||
// 2. 读取 signature.asc
|
||||
val sigEntry = zip.getEntry("META-INF/signature.asc")
|
||||
?: throw RuntimeException("signature.asc is missing!")
|
||||
val signature = zip.getInputStream(sigEntry).readAllBytes()
|
||||
|
||||
// 3. 使用 OpenPGP 验证签名
|
||||
val ok = verifyDetachedSignature(publicKey, manifestJson.toByteArray(), signature)
|
||||
if (!ok) throw RuntimeException("PGP signature invalid!")
|
||||
|
||||
// 4. 校验 manifest 里每个文件的 SHA-256
|
||||
val mapper = ObjectMapper()
|
||||
val manifest = mapper.readValue(manifestJson, Map::class.java)
|
||||
val files = manifest["files"] as? Map<String, String>
|
||||
?: throw RuntimeException("Invalid manifest.json: missing 'files'")
|
||||
|
||||
for ((name, expectedHash) in files) {
|
||||
val entry = zip.getEntry(name)
|
||||
?: throw RuntimeException("文件不存在: $name")
|
||||
|
||||
val data = zip.getInputStream(entry).readAllBytes()
|
||||
val hash = sha256Hex(data)
|
||||
|
||||
if (!hash.equals(expectedHash, ignoreCase = true)) {
|
||||
throw RuntimeException("Hash mismatch: $name")
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun sha256Hex(data: ByteArray): String {
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
return bytesToHex(md.digest(data))
|
||||
}
|
||||
|
||||
private fun bytesToHex(bytes: ByteArray): String {
|
||||
return bytes.joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun readPublicKey(keyIn: InputStream): PGPPublicKey {
|
||||
val keyRings = PGPPublicKeyRingCollection(
|
||||
PGPUtil.getDecoderStream(keyIn),
|
||||
JcaKeyFingerprintCalculator()
|
||||
)
|
||||
|
||||
for (keyRing in keyRings) {
|
||||
for (key in keyRing) {
|
||||
if (key.isEncryptionKey || key.isMasterKey) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw IllegalArgumentException("Can't find public key")
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun verifyDetachedSignature(
|
||||
key: PGPPublicKey,
|
||||
data: ByteArray,
|
||||
sigBytes: ByteArray
|
||||
): Boolean {
|
||||
|
||||
val decoder = PGPUtil.getDecoderStream(ByteArrayInputStream(sigBytes))
|
||||
val factory = PGPObjectFactory(decoder, JcaKeyFingerprintCalculator())
|
||||
|
||||
val message = factory.nextObject()
|
||||
?: throw IllegalArgumentException("Invalid signature file")
|
||||
|
||||
val sigList = when (message) {
|
||||
is PGPSignatureList -> message
|
||||
is PGPCompressedData -> {
|
||||
val compressedFactory = PGPObjectFactory(
|
||||
message.dataStream,
|
||||
JcaKeyFingerprintCalculator()
|
||||
)
|
||||
val compressedObj = compressedFactory.nextObject()
|
||||
compressedObj as? PGPSignatureList
|
||||
?: throw IllegalArgumentException("Invalid PGP signature (not signature list)")
|
||||
}
|
||||
else ->
|
||||
throw IllegalArgumentException("Unsupported PGP signature format: ${message::class.java}")
|
||||
}
|
||||
|
||||
val sig = sigList[0]
|
||||
|
||||
sig.init(JcaPGPContentVerifierBuilderProvider().setProvider("BC"), key)
|
||||
sig.update(data)
|
||||
|
||||
return sig.verify()
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user