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

使用node-jose与C# BouncyCastle生成JWK指纹结果不一致的原因

JWK指纹生成结果不一致的原因分析

Node.js 实现(node-jose库)

使用node-jose库生成JWK指纹的代码如下:

const jose = require('node-jose');

generateJwkThumbprint = async (jwk) => {
  let jwkKey = await jose.JWK.asKey(jwk, 'pem'); // 将PEM转换为JWK对象
  let jwkThumbprintBuffer = await jwkKey.thumbprint('SHA-256'); // 对JWK对象计算SHA-256哈希
  let jwkThumbprint = jose.util.base64url.encode(jwkThumbprintBuffer, 'utf8'); // 对哈希结果做Base64URL编码

  return jwkThumbprint;
};

let publicKey = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZ1fr5JeX52k397Eex4oDBwODEOwz\nBG5qGAfmBFSz4aRZkqJeMPq5pV6jhaPN/w9wvTYNc0ibT8129/GCag6Snw==\n-----END PUBLIC KEY-----\n";

(async () => {
    try {
      let thumbprint = await generateJwkThumbprint(publicKey);
      console.log(thumbprint);
    } catch (error) {
      console.error("Error:", error);
    }
})();

运行后得到的指纹:

Mts1dD5R6PnYesvL0YLbc43h4YBVrd8_koC9jFsv_Sg

C# 实现(BouncyCastle库)

尝试用BouncyCastle实现相同功能的代码如下:

static void Main()
{
    var publicKey =
        "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZ1fr5JeX52k397Eex4oDBwODEOwz\nBG5qGAfmBFSz4aRZkqJeMPq5pV6jhaPN/w9wvTYNc0ibT8129/GCag6Snw==\n-----END PUBLIC KEY-----\n";

    var thumbprint = GenerateJwkThumbprint(publicKey);

    Console.Write(thumbprint);
}

private static AsymmetricKeyParameter JwkPemToAsymmetricKey(string jwkPem)
{
    using StringReader reader = new StringReader(jwkPem);
    var pemReader = new PemReader(reader);
    var keyObject = pemReader.ReadObject();

    if (keyObject is ECPublicKeyParameters ecPublicKey)
    {
        return ecPublicKey;
    }

    throw new InvalidOperationException("Invalid JWK PEM format or unsupported key type.");
}

private static string GenerateJwkThumbprint(string jwkPem)
{
    // Convert PEM to JWK
    var jwkKey = JwkPemToAsymmetricKey(jwkPem);

    // Calculate SHA-256 thumbprint
    var sha256Thumbprint = CalculateSha256Thumbprint(jwkKey);

    // Base64url encode the thumbprint
    var thumbprint = Base64UrlEncode(sha256Thumbprint);

    return thumbprint;
}

private static string Base64UrlEncode(byte[] bytes)
{
    return Convert.ToBase64String(bytes).Replace('+', '-').Replace('/', '_').Replace("=", "");
}

private static byte[] CalculateSha256Thumbprint(AsymmetricKeyParameter jwkKey)
{
    IDigest digest = new Sha256Digest();
    var jwkBytes = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(jwkKey).GetDerEncoded();
    digest.BlockUpdate(jwkBytes, 0, jwkBytes.Length);
    var thumbprint = new byte[digest.GetDigestSize()];
    digest.DoFinal(thumbprint, 0);
    return thumbprint;
}

运行后得到的指纹:

yLCIQ8WauQaPAC4A3-LQ5l5s_p9vRfOqPl10efd0hck

差异原因

两者结果不同的核心是计算指纹的数据源完全不同:

  • node-jose严格遵循RFC 7638定义的JWK指纹流程:先将PEM转换为标准JWK对象,再对JWK进行标准化JSON序列化(键名按字典序排列、无额外空格),最后对序列化后的JSON字节计算SHA-256哈希,再做Base64URL编码。
  • 你的C#代码错误地使用了X.509证书指纹的计算方式:直接对SubjectPublicKeyInfo的DER编码字节做哈希,这和JWK指纹的标准计算逻辑完全不匹配。

修正思路(C#)

要得到和Node.js一致的结果,需要按照RFC 7638的步骤实现:

  1. 将EC公钥转换为符合规范的JWK JSON结构(包含kty、crv、x、y等字段,字段名按字典序)
  2. 对JWK JSON进行标准化序列化(无空格、键名排序)
  3. 对序列化后的字节计算SHA-256哈希
  4. 对哈希结果做Base64URL编码

示例修正后的核心代码片段:

private static byte[] CalculateJwkThumbprint(ECPublicKeyParameters ecKey)
{
    // 构建JWK对象(按RFC 7638要求的字段顺序)
    var jwk = new Dictionary<string, string>
    {
        { "kty", "EC" },
        { "crv", "P-256" }, // 根据密钥实际曲线调整,此处为P-256
        { "x", Base64UrlEncode(ecKey.Q.XCoord.GetEncoded()) },
        { "y", Base64UrlEncode(ecKey.Q.YCoord.GetEncoded()) }
    };

    // 标准化JSON序列化:键按字典序,无空格
    var jsonSerializer = new JsonSerializer();
    jsonSerializer.ContractResolver = new OrderedContractResolver(); // 自定义排序解析器
    using var stringWriter = new StringWriter();
    using var jsonWriter = new JsonTextWriter(stringWriter) { Formatting = Formatting.None };
    jsonSerializer.Serialize(jsonWriter, jwk);
    var jwkJson = stringWriter.ToString();

    // 计算SHA-256哈希
    using var sha256 = SHA256.Create();
    return sha256.ComputeHash(Encoding.UTF8.GetBytes(jwkJson));
}

// 自定义JSON键排序解析器
public class OrderedContractResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        return base.CreateProperties(type, memberSerialization)
            .OrderBy(p => p.PropertyName)
            .ToList();
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 02:25:06