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

为何mbedtls生成的ECDSA签名无法在JOSE中验证(RSA正常)

ECDSA签名JWT外部验证失败问题

我在ESP32开发板上用Arduino IDE和配套mbedtls库开发了一个签发、验证JWT的应用。原本用RSA签名一切正常,换成ECDSA(prime256v1+SHA256,对应ES256算法)后,应用内可以正常签发和验证令牌,但用JOSE库或jwt.io调试器等外部工具验证时全部失败。

示例令牌:

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NzI0MTMxNjgsImV4cCI6MTY3MjQxNjc2OH0.MEUCIAjwEDXI424qjrAkSzZ_ydcVLOSAvfQ8YVddYvzDzMvQAiEAkVy4d-hZ01KpcMNKhPHk8E_SDYiB4JKwhm-Kc-Z81rI

对应公钥:

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUGnNIOhPhZZSOg4A4BqAFtGO13W4BGDQpQ0ieTvLU9/CXrY7W77o7pNx7tvugeIoYJxS0NjmxvT4TMpo4Z8P7A==
-----END PUBLIC KEY-----

密钥生成命令:

openssl ecparam -name prime256v1 -genkey -noout -out ecc-private.pem
openssl ec -in ecc-private.pem -pubout -out ecc-public.pem

加载私钥代码:

//get the key
byte *keybuffer = (byte*)malloc((ecc_priv.length()+1)*sizeof(byte));
ecc_priv.getBytes(keybuffer, ecc_priv.length() + 1);
mbedtls_pk_context pk_context;
mbedtls_pk_init(&pk_context);
int rc = mbedtls_pk_parse_key(&pk_context, keybuffer, ecc_priv.length() + 1, NULL, 0);
if (rc != 0){
 printf("Failed to mbedtls_pk_parse_key: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc));
 return -1;
}
free(keybuffer);

签名核心代码:

//mbedtls context
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);

const char* pers="some entropy";
                
mbedtls_ctr_drbg_seed(
  &ctr_drbg,
  mbedtls_entropy_func,
  &entropy,
  (const unsigned char*)pers,
  strlen(pers));
//get the header and payload bytes    
byte *headerAndPayloadbytes = (byte*)malloc((headerAndPayload.length()+1)*sizeof(byte));
headerAndPayload.getBytes(headerAndPayloadbytes, headerAndPayload.length() + 1);
//prepare digest
uint8_t digest[32];
rc = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), headerAndPayloadbytes, headerAndPayload.length(), digest);
if (rc != 0) {
  printf("Failed to mbedtls_md: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc));
  return -1;        
}
free(headerAndPayloadbytes);
//prepare output
byte *oBuf = (byte*)malloc(5000*sizeof(byte));
size_t retSize;
//sign digest
rc = mbedtls_pk_sign(&pk_context, MBEDTLS_MD_SHA256, digest, sizeof(digest), oBuf, &retSize, mbedtls_ctr_drbg_random, &ctr_drbg);
if (rc != 0) {
  printf("Failed to mbedtls_pk_sign: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc));
  return -1;        
}
//encode signature to base64
unsigned int osize = encode_base64_length(retSize);
byte *output = (byte*)malloc((osize+1)*sizeof(byte));
encode_base64(oBuf, retSize, output);
String sig = String((char*)output);
free(output);
//base64 URL specific
sig.replace('+','-');
sig.replace('/','_');
sig.replace("=","");
String completejwt = headerAndPayload + "." + sig;
//free resources
mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy );
mbedtls_pk_free(&pk_context);
free(oBuf);

问题原因与解决方案

核心问题

mbedtls的mbedtls_pk_sign函数对ECDSA签名返回的是ASN.1 DER编码格式,而JWT的ES256算法要求签名必须是raw格式(32字节r值拼接32字节s值)。这就是为什么应用内验证正常(验证时用了相同的mbedtls逻辑,自动处理了DER格式),但外部工具验证失败的原因。

解决步骤

需要将mbedtls生成的DER格式签名转换为JWT要求的raw格式,具体代码修改如下:

  1. 添加DER解析函数,从DER编码中提取r和s:
int ecdsa_der_to_raw(const unsigned char *der_signature, size_t der_len, unsigned char *raw_signature) {
    if (der_signature[0] != 0x30 || der_signature[1] != der_len - 2) {
        return -1;
    }
    // 提取r
    size_t r_len = der_signature[3];
    const unsigned char *r_data = der_signature + 4;
    // 提取s
    size_t s_len = der_signature[4 + r_len + 1];
    const unsigned char *s_data = der_signature + 4 + r_len + 2;

    // 确保r和s都是32字节(prime256v1的长度),不足的话前面补0
    memset(raw_signature, 0, 32);
    memcpy(raw_signature + (32 - r_len), r_data, r_len);
    memset(raw_signature + 32, 0, 32);
    memcpy(raw_signature + 32 + (32 - s_len), s_data, s_len);

    return 0;
}
  1. 修改签名后的处理逻辑,替换原有的Base64编码部分:
// 原签名代码到rc = mbedtls_pk_sign(...)之后
if (rc != 0) {
  printf("Failed to mbedtls_pk_sign: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc));
  return -1;        
}

// 新增:将DER签名转换为raw格式
unsigned char raw_sig[64];
rc = ecdsa_der_to_raw(oBuf, retSize, raw_sig);
if (rc != 0) {
  printf("Failed to convert DER to raw signature\n");
  return -1;
}

// 对raw签名进行Base64URL编码
unsigned int osize = encode_base64_length(64);
byte *output = (byte*)malloc((osize+1)*sizeof(byte));
encode_base64(raw_sig, 64, output);
String sig = String((char*)output);
free(output);

// 后续的Base64URL替换逻辑不变
sig.replace('+','-');
sig.replace('/','_');
sig.replace("=","");
String completejwt = headerAndPayload + "." + sig;

验证修改效果

将转换后的raw签名进行Base64URL编码后,生成的JWT就能在jwt.io等工具中正常验证。比如你提供的示例令牌,把签名部分转换为raw格式后,对应的Base64URL编码为AjwEDXI424qjrAkSzZ_ydcVLOSAvfQ8YVddYvzDzMvQkVy4d-hZ01KpcMNKhPHk8E_SDYiB4JKwhm-Kc-Z81rI,替换原签名后就能通过外部验证。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 00:01:07