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

如何在Java中从PEM编码公钥生成包含x5c/x5t的JWK?

生成包含x5c和x5t#S256字段的RSA JWK(基于BouncyCastle)

问题背景

我持有PEM编码的RSA公钥和自签名证书,使用Nimbus JOSE + JWT库生成JWK时,输出结果缺失x5c(X.509证书链)和x5t#S256(证书SHA256指纹)字段,需要补全这两部分的实现。

现有代码

String getJWTKey(final String rawKey) throws Exception {
    final KeyFactory kf = KeyFactory.getInstance("RSA");
    final SubjectPublicKeyInfo pubKeyInfo =
        (SubjectPublicKeyInfo) new PEMParser(new StringReader(rawKey)).readObject();
    final X509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(pubKeyInfo.getEncoded());
    final RSAPublicKey rsaPubKey = (RSAPublicKey) kf.generatePublic(keySpecX509);

    // How to get these two?
    List<Base64> certChain = null;
    Base64URL thumbprint = null;

    final RSAKey key = new RSAKey.Builder(rsaPubKey)
        .keyUse(KeyUse.SIGNATURE)
        .algorithm(new Algorithm("RS256"))
        .keyID("mykey")
        .x509CertChain(certChain)
        .x509CertSHA256Thumbprint(thumbprint)
        .build();
 
    return key.toJSONString();
}

缺失的实现逻辑

要生成x5c和x5t#S256字段,需要完成以下两步:

  • 解析PEM证书并构建证书链:读取PEM中的X.509证书,将其DER编码转为Base64格式,存入列表作为证书链
  • 计算证书SHA256指纹:对证书的DER编码计算SHA256哈希,再将哈希值转为Base64URL编码格式

完整修正代码

import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.Algorithm;
import com.nimbusds.jose.util.Base64;
import com.nimbusds.jose.util.Base64URL;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;

import java.io.StringReader;
import java.security.KeyFactory;
import java.security.spec.X509EncodedKeySpec;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;

String getJWTKey(final String rawPemContent) throws Exception {
    // 若未全局注册BouncyCastle,需添加以下代码
    // Security.addProvider(new BouncyCastleProvider());

    List<Base64> certChain = new ArrayList<>();
    RSAPublicKey rsaPubKey = null;
    Base64URL certThumbprint = null;

    try (PEMParser pemParser = new PEMParser(new StringReader(rawPemContent))) {
        Object obj;
        while ((obj = pemParser.readObject()) != null) {
            if (obj instanceof SubjectPublicKeyInfo) {
                // 解析RSA公钥
                SubjectPublicKeyInfo pubKeyInfo = (SubjectPublicKeyInfo) obj;
                X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pubKeyInfo.getEncoded());
                KeyFactory kf = KeyFactory.getInstance("RSA");
                rsaPubKey = (RSAPublicKey) kf.generatePublic(keySpec);
            } else if (obj instanceof X509CertificateHolder) {
                // 解析X.509证书,构建证书链
                X509CertificateHolder certHolder = (X509CertificateHolder) obj;
                byte[] certDer = certHolder.getEncoded();
                certChain.add(Base64.encode(certDer));

                // 计算证书SHA256指纹(对应x5t#S256字段)
                MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");
                byte[] thumbprintBytes = sha256Digest.digest(certDer);
                certThumbprint = Base64URL.encode(thumbprintBytes);
            }
        }
    }

    // 构建完整的RSA JWK
    RSAKey.Builder jwkBuilder = new RSAKey.Builder(rsaPubKey)
        .keyUse(KeyUse.SIGNATURE)
        .algorithm(new Algorithm("RS256"))
        .keyID("mykey");

    if (!certChain.isEmpty()) {
        jwkBuilder.x509CertChain(certChain);
        jwkBuilder.x509CertSHA256Thumbprint(certThumbprint);
    }

    RSAKey jwk = jwkBuilder.build();
    // 若需返回JWKS格式(包含keys数组),可改用以下代码
    // JWKS jwks = new JWKS(jwk);
    // return jwks.toJSONString();

    return jwk.toJSONString();
}

关键说明

  • 证书链解析:通过PEMParser遍历PEM内容,识别X509CertificateHolder对象,将其DER编码转为Base64字符串,对应JWK的x5c字段
  • 指纹计算:对证书的DER编码计算SHA256哈希,再用Base64URL.encode()处理哈希值,对应JWK的x5t#S256字段
  • 依赖要求:确保项目已引入BouncyCastle相关依赖,如bcprov-jdk15on

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 19:31:02