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

ECDSA签名随机截断问题求助:r/s长度不符预期

ECDSA签名r/s值偶尔长度不足48字节的问题解析

问题描述

生成ECDSA签名时,多数情况签名正常,但偶尔会出现随机截断现象:r和s值本应均为48字节,却有时其中一个为47字节、另一个为48字节,无法确定是签名本身的问题,还是获取r/s值的流程存在问题。

相关代码

const BIGNUM *r;
const BIGNUM *s;
ECDSA_SIG_get0(signature, &r, &s);
char *R = BN_bn2hex(r);
char *S = BN_bn2hex(s);
int r_len = BN_num_bytes(r);
int s_len = BN_num_bytes(s);
uint8_t *r_bytes = (uint8_t *)malloc(r_len);
uint8_t *s_bytes = (uint8_t *)malloc(s_len);
BN_bn2bin(r, r_bytes);
BN_bn2bin(s, s_bytes);
int signature_len = r_len + s_len;
uint8_t *signature2 = (uint8_t *)malloc(signature_len);
memcpy(signature2, r_bytes, r_len);
memcpy(signature2 + r_len, s_bytes, s_len);
int max_encoded_len = EVP_ENCODE_LENGTH(signature_len);
*ppcSignature1 = (char *)calloc(max_encoded_len,1);

已尝试的排查操作

曾尝试重新从同一签名中获取r和s值,代码如下:

if(r_len != 48 || s_len != 48){
    const BIGNUM *r_temp = ECDSA_SIG_get0_r(signature);
    const BIGNUM *s_temp = ECDSA_SIG_get0_s(signature);
    char *R_temp = BN_bn2hex(r_temp);
    char *S_temp = BN_bn2hex(s_temp);
    printf("R_LEN : %s\n",R_temp);
    printf("S_LEN : %s\n",S_temp);
}

但得到的r_len和s_len数值仍与之前一致。

问题原因与解决方案

原因分析

这不是签名本身的问题,而是你在将BIGNUM转换为二进制时,没有处理高位补零的逻辑:

  • ECDSA的r和s是椭圆曲线群的元素,取值范围为[1, n-1](n为曲线阶数)。对于384位曲线(如secp384r1),n是384位大数,r/s的二进制表示最多为48字节,但当数值的最高位字节为0时,BN_num_bytes会返回47字节,BN_bn2bin也只会写入有效字节,不会自动补前导零。
  • BN_bn2hex生成的十六进制字符串同样不会包含前导零,所以你打印的结果看起来长度不足,但实际数值是正确的。

解决方案

改用BN_bn2binpad替代BN_bn2bin,并固定分配48字节的内存空间,该函数会自动在二进制数据前补零,确保输出长度固定为指定值:

const BIGNUM *r;
const BIGNUM *s;
ECDSA_SIG_get0(signature, &r, &s);

// 384位曲线固定使用48字节长度
const int fixed_len = 48;
uint8_t *r_bytes = (uint8_t *)malloc(fixed_len);
uint8_t *s_bytes = (uint8_t *)malloc(fixed_len);

// 自动补零到固定长度
BN_bn2binpad(r, r_bytes, fixed_len);
BN_bn2binpad(s, s_bytes, fixed_len);

int signature_len = fixed_len * 2;
uint8_t *signature2 = (uint8_t *)malloc(signature_len);
memcpy(signature2, r_bytes, fixed_len);
memcpy(signature2 + fixed_len, s_bytes, fixed_len);

int max_encoded_len = EVP_ENCODE_LENGTH(signature_len);
*ppcSignature1 = (char *)calloc(max_encoded_len, 1);

// 注意释放内存,避免泄漏
OPENSSL_free(R);
OPENSSL_free(S);
free(r_bytes);
free(s_bytes);
// 其他内存释放需根据实际逻辑补充

额外说明

如果是遵循标准ASN.1 DER编码的ECDSA签名,编码本身会自动处理r/s的长度标识,不会出现此类问题;但如果是自行拼接r和s的二进制数据,必须保证两者长度固定,否则会导致后续签名验证或传输出现异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 00:40:19