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

如何从JWK重建RSA公钥以验证JWT?

解决从JWK生成RSA公钥并验证JWT签名的问题

问题根源

你当前的代码错误在于直接将JWK中的n(模数)和e(指数)字符串拼接,没有正确处理它们的编码格式,也没有遵循X.509公钥的ASN.1结构规范:

  • JWK的n和e是Base64URL编码(而非标准Base64)的二进制数据,需要先解码为原始字节
  • PEM格式的公钥要求内容是DER编码的ASN.1结构,而非简单拼接字符串

正确的转换步骤

1. 处理Base64URL解码

Base64URL和标准Base64的区别在于:替换+为-,/为_,并去掉末尾的=填充符。需要先把n和e转换为标准Base64再解码。

2. 构建ASN.1结构

RSA公钥的ASN.1 DER结构是一个嵌套序列:

Sequence (2 elements)
  Sequence (2 elements)
    ObjectIdentifier 1.2.840.113549.1.1.1 (RSA Encryption)
    NULL
  BitString (1 element)
    Sequence (2 elements)
      Integer (n: 模数)
      Integer (e: 指数)

3. 转换为PEM格式

将DER编码的结构添加PEM头、尾,并按每64字符分段。

完整PHP实现代码

// 解析JWK
$jwkJson = '{
"kty": "RSA",
"use": "enc",
"n": "ozmvkuGzWNHs9cEcC5PWwbG-dmSjPcoQFxEbqH_fBjkj_nfTTKshdiSq5ciulWEa_rrqQ2qwcSADNxtTzRR1qfud-NvsM8VltT7xDuVVqPTZoWLKa0BWXgQQ-1mCm1KdGltYWccB0R1LoF-rb3DEEZySsHvqErYzYt4M_rqjEiK5Y9y1h3k1h5Yk4zGLWchko3jiDS-pVevvWsQsN-Y3KuB19485G9P_MXLtfJWQ4wC4jlo9etdD_hgDfxX-hQy3wuwHfHifLdxvxiB8X5Is4m6DuY4_7hS5RwXAjO1QSd-zUYZNT_2yWVR56_jyiZEiOdgIm9QtLPZCTKzqsXoqZQ",
"e": "AQAB"
}';
$jwk = json_decode($jwkJson);

// Base64URL转标准Base64并解码
function base64url_decode($data) {
    $padding = strlen($data) % 4;
    if ($padding > 0) {
        $data .= str_repeat('=', 4 - $padding);
    }
    return base64_decode(strtr($data, '-_', '+/'));
}

$n = base64url_decode($jwk->n);
$e = base64url_decode($jwk->e);

// 构建ASN.1 DER结构
// 先构建内层的n和e序列
$seqNE = pack('H*', '30') . // Sequence标签
         pack('H*', dechex(strlen($n) + strlen($e) + 4)) . // 长度
         pack('H*', '02') . // Integer标签
         pack('H*', dechex(strlen($n))) . // n的长度
         $n .
         pack('H*', '02') . // Integer标签
         pack('H*', dechex(strlen($e))) . // e的长度
         $e;

// 构建外层的AlgorithmIdentifier序列
$algSeq = pack('H*', '300d06092a864886f70d0101010500');

// 构建最外层的公钥序列
$pubKeySeq = pack('H*', '30') . // Sequence标签
             pack('H*', dechex(strlen($algSeq) + strlen($seqNE) + 2)) . // 长度
             $algSeq .
             pack('H*', '03') . // BitString标签
             pack('H*', dechex(strlen($seqNE) + 1)) . // 长度(+1是因为BitString的unused bits字段)
             pack('H*', '00') . // unused bits为0
             $seqNE;

// 转换为PEM格式
$pem = "-----BEGIN PUBLIC KEY-----\n" .
       chunk_split(base64_encode($pubKeySeq), 64, "\n") .
       "-----END PUBLIC KEY-----";

// 验证签名(替换成你的$dataToVerify和$signature)
$isSignatureValid = openssl_verify($dataToVerify, $signature, $pem, OPENSSL_ALGO_SHA256);

// 检查结果
if ($isSignatureValid === 1) {
    echo "签名验证通过";
} elseif ($isSignatureValid === 0) {
    echo "签名验证失败";
} else {
    echo "验证出错: " . openssl_error_string();
}

关键细节解释

  • base64url_decode函数:处理JWK特有的编码格式,确保正确解码出原始二进制的模数和指数
  • ASN.1结构打包:使用pack函数手动构建符合X.509标准的DER结构,这是openssl能识别公钥的核心
  • PEM分段:必须按每64字符换行,否则openssl无法正确解析

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 12:05:07