You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用GCP KMS存储客户端TLS证书私钥并实现PKCS12密钥库调用

GCP KMS替代本地PKCS12私钥实现SSL客户端认证方案

前置准备

  • 从原有.p12密钥库中导出证书公链(仅包含公钥和证书信息,无敏感私钥内容),可存储在应用配置或普通配置中心,无需加密存储
  • 将.p12中的私钥导入GCP KMS,创建「非对称签名密钥」,算法需与原私钥完全匹配(如原私钥为RSA2048 SHA256签名用途,对应选择RSA_SIGN_PKCS1_2048_SHA256算法),记录该密钥的完整资源ID
  • 为应用运行时使用的服务账号授予Cloud KMS CryptoKey Signer角色权限,应用通过IAM鉴权访问KMS,无需传递任何密钥明文或密码

实现思路

原有逻辑的核心是通过本地加载PKCS12文件得到持有私钥明文的KeyManager,用于SSL握手过程的签名操作。替换方案核心是自定义X509ExtendedKeyManager,完全替换原有默认实现:

  • 自定义KeyManager不持有任何私钥明文,仅内置提前导出的证书公链
  • SSL握手需要签名时,直接调用GCP KMS的非对称签名接口完成运算,私钥全程不离开KMS安全域

代码实现示例

首先引入GCP官方KMS客户端SDK,自定义KMS-backed KeyManager实现:

import com.google.cloud.kms.v1.AsymmetricSignRequest
import com.google.cloud.kms.v1.Digest
import com.google.cloud.kms.v1.KeyManagementServiceClient
import java.security.PrivateKey
import java.security.cert.X509Certificate
import javax.net.ssl.SSLEngine
import javax.net.ssl.X509ExtendedKeyManager

class KmsBackedKeyManager(
    private val certChain: Array<X509Certificate>,
    private val kmsKeyResourceId: String,
    private val kmsClient: KeyManagementServiceClient
) : X509ExtendedKeyManager() {

    // 返回证书公链,和原有PKCS12中导出的公链完全一致
    override fun getCertificateChain(alias: String?): Array<X509Certificate> = certChain

    // 这里返回的占位私钥仅用于接口适配,实际签名不会用到私钥内容
    override fun getPrivateKey(alias: String?): PrivateKey = object : PrivateKey {
        override fun getAlgorithm(): String = certChain.first().publicKey.algorithm
        override fun getFormat(): String = "RAW"
        override fun getEncoded(): ByteArray = byteArrayOf()
    }

    // SSL客户端场景下固定返回自定义的密钥别名即可
    override fun chooseClientAlias(keyType: Array<out String>?, issuers: Array<out java.security.Principal>?, engine: SSLEngine?): String = "kms-backed-key"
    override fun chooseServerAlias(keyType: String?, issuers: Array<out java.security.Principal>?, socket: java.net.Socket?): String? = null
    override fun getClientAliases(keyType: String?, issuers: Array<out java.security.Principal>?): Array<String> = arrayOf("kms-backed-key")
    override fun getServerAliases(keyType: String?, issuers: Array<out java.security.Principal>?): Array<String>? = null

    // 签名逻辑实现:将待签名摘要传给KMS完成运算
    fun sign(data: ByteArray, digestAlgorithm: String): ByteArray {
        val digest = Digest.newBuilder().apply {
            when (digestAlgorithm.lowercase()) {
                "sha256" -> sha256 = com.google.protobuf.ByteString.copyFrom(data)
                "sha1" -> sha1 = com.google.protobuf.ByteString.copyFrom(data)
                // 按需补充其他算法适配
            }
        }.build()
        val request = AsymmetricSignRequest.newBuilder()
            .setName(kmsKeyResourceId)
            .setDigest(digest)
            .build()
        return kmsClient.asymmetricSign(request).signature.toByteArray()
    }
}

最终构建SSL上下文的逻辑和原有代码对齐:

import io.netty.handler.ssl.SslContextBuilder
import java.io.ByteArrayInputStream
import java.security.cert.CertificateFactory

// 1. 加载提前导出的证书公链(无敏感内容)
val certFactory = CertificateFactory.getInstance("X.509")
val certChain = listOf(
    certFactory.generateCertificate(ByteArrayInputStream(/* 你的证书二进制内容 */)) as X509Certificate,
    // 按需补充中间证书、根证书
).toTypedArray()

// 2. 初始化GCP KMS客户端,默认走应用默认凭据鉴权
val kmsClient = KeyManagementServiceClient.create()
val kmsKeyId = "projects/你的项目ID/locations/区域/keyRings/密钥环名/cryptoKeys/密钥名/cryptoKeyVersions/版本号"

// 3. 构建自定义KeyManager
val kmsKeyManager = KmsBackedKeyManager(certChain, kmsKeyId, kmsClient)

// 4. 构建SSL上下文,和原有业务逻辑完全兼容
val sslContext = SslContextBuilder
    .forClient()
    .keyManager(kmsKeyManager)
    .build()

额外适配说明:如果使用标准JSSE实现,需补充自定义SignatureProvider拦截SSL栈的签名请求,调用上述KmsBackedKeyManager.sign方法返回KMS签名结果即可,上层通信逻辑无需改造。

注意事项

  • 整个过程无任何私钥或密码明文出现在应用侧内存、配置中,符合密钥安全管理要求
  • 若对延迟敏感,可针对相同握手摘要的签名结果做本地短时间缓存,降低KMS调用频率
  • 需保证KMS中密钥的签名算法和原私钥算法完全一致,避免签名校验失败

内容的提问来源于stack exchange,提问作者Superovan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 02:15:03