c++ - 使用 OpenSSL 将 P1363 编码签名转换为 ECDSA_SIG
问题描述
虽然 OpenSSL 始终假定 ECDSA 签名是 ASN.1/DER 编码的,但我还需要能够验证 P1363 编码的签名。(有关 2 种形式的全面介绍,请参见例如这个 SO 问题的答案。)
这个想法是修补 ECDSA_verify(),以便如果 ASN.1 解析失败,则假定 P1363 并转换为合成的 ECDSA_SIG,然后可以将其馈入 ECDSA_do_verify()。
这就是我所做的:
#include <openssl/ecdsa.h>
#include <string.h>
using namespace std;
const unsigned char sigbuf[] =
{
0x37, 0x25, 0x8a, 0x3c, 0xf0, 0x05, 0x6e, 0x23, 0x97, 0x83, 0xae, 0xf5, 0x84, 0x0a, 0x5e, 0x0a,
0x1f, 0xc8, 0x8a, 0x54, 0x84, 0x05, 0x34, 0x1d, 0x82, 0x86, 0x47, 0x7c, 0x14, 0x51, 0x14, 0xf8,
0x0a, 0xf4, 0xbc, 0xcf, 0x58, 0xef, 0xcd, 0x69, 0xbd, 0xc0, 0x23, 0xf1, 0xe2, 0x96, 0x6a, 0xa8,
0x28, 0xcf, 0x35, 0x60, 0xe6, 0x75, 0x6d, 0x89, 0x4a, 0x60, 0x9b, 0x2b, 0x2a, 0x6d, 0x06, 0x51
};
const int sig_len = 64;
//int ECDSA_verify(int type, const unsigned char *dgst, int dgst_len,
// const unsigned char *sigbuf, int sig_len, EC_KEY *eckey)
int main(int argc, char *argv[])
{
ECDSA_SIG *s;
const unsigned char *p = sigbuf;
unsigned char *der = NULL;
int derlen = -1;
int ret = -1;
s = ECDSA_SIG_new();
if (s == NULL)
return (ret);
if (d2i_ECDSA_SIG(&s, &p, sig_len) == NULL) {
/*
* ASN.1 decoding failed, see crypto/asn1/tasn_dec.c line 515ff.
* Assume s is encoded as IEEE P1363. for a comprehensive description see
* ttps://stackoverflow.com/questions/36542645/does-openssl-sign-for-ecdsa-apply-asn1-encoding-to-the-hash-before-signing
* Fill the ECDSA_SIG from the P1363.
*/
if ((sig_len % 2) != 0)
return (ret);
if (strlen((char *)sigbuf) != sig_len)
return (ret);
if (s == NULL)
s = ECDSA_SIG_new();
if (s == NULL)
return (ret);
/*
* BN_hex2bn() stops immediately if the hex string starts with '\0', so we skip zeroes.
* I /think/ only the s part of the P1363 may be padded, but it does no harm to skip them
* for the r part, too.
*/
const unsigned char *pr = sigbuf;
while (*pr == '\0')
pr++;
/*
* BN_hex2bn() is greedy, so we create null-terminated copies of both the r and s parts.
* Also note that it looks like BN_hex2bn() takes care of the required leading zero padding
* in case of negative bignums.
*/
int hex_len = (sigbuf + sig_len / 2) - pr;
char *hex = (char *)malloc(hex_len + 1);
strncpy(hex, (const char *)pr, hex_len);
hex[hex_len] = '\0';
/*
* Finally create the BIGNUM and put it in the r part of the ECDSA_SIG.
*/
BN_hex2bn(&(s->r), hex);
free(hex);
/*
* Now do the same for the s part...
*/
unsigned char *ps = const_cast<unsigned char *>(sigbuf) + sig_len / 2;
while (*ps == '\0')
ps++;
hex_len = (sigbuf + sig_len) - ps;
hex = (char *)malloc(hex_len + 1);
strncpy(hex, (const char *)ps, hex_len);
hex[hex_len] = '\0';
BN_hex2bn(&(s->s), hex);
free(hex);
}
/* Ensure signature uses DER and doesn't have trailing garbage */
derlen = i2d_ECDSA_SIG(s, &der);
// if (derlen != sig_len || memcmp(sigbuf, der, derlen))
// goto err;
// ret = ECDSA_do_verify(dgst, dgst_len, s, eckey);
err:
if (derlen > 0) {
OPENSSL_cleanse(der, derlen);
OPENSSL_free(der);
}
ECDSA_SIG_free(s);
return (ret);
}
sigbuf 是一个 prime256v1 真实世界的例子,取自 asn1parse。
现在,当我运行上述程序时,derlen 为 8,der 为“0\006\002\001\007\002\001”。这显然不是我期望的 ASN.1,它应该是:
#30 46 (SEQUENCE, 70 bytes) <-- edit: wrong
30 44 (SEQUENCE, 68 bytes)
02 20 (INTEGER, 32 bytes)
(no padding)
37 25 8A 3C F0 05 6E 23 97 83 AE F5 84 0A 5E 0A
1F C8 8A 54 84 05 34 1D 82 86 47 7C 14 51 14 F8
02 20 (INTEGER, 32 bytes)
(no padding)
0A F4 BC CF 58 EF CD 69 BD C0 23 F1 E2 96 6A A8
28 CF 35 60 E6 75 6D 89 4A 60 9B 2B 2A 6D 06 51
不应该吗?
显然,我在从十六进制缓冲区创建 BIGNUM 或从它们创建 ECDSA_SIG 或 ASN.1 序列化时做错了。但对于我的生活,我看不到它是什么。任何帮助表示赞赏!
解决方案
嗬!这太傻了……
BN_hex2bn() 中的“十六进制”并不意味着十六进制值的数组。相反,它意味着十六进制值的字符串表示!
所以在我上面的例子中,如果我改为初始化
const unsigned char sigbuf[] = "37258a3cf0056e239783aef5840a5e0a"
"1fc88a548405341d8286477c145114f8"
"0af4bccf58efcd69bdc023f1e2966aa8"
"28cf3560e6756d894a609b2b2a6d0651";
const int sig_len = 128;
我最终得到了一个不错的 ECDSA_SIG,因此也得到了不错的 der 和 derlen。
希望这可以帮助将来的人。有时我真的很讨厌 OpenSSL 文档……
推荐阅读
- sockets - 如何使用 Dart 套接字 onError 作为内联函数?
- jquery - 事件发射器停止执行
- android - 从最近的应用程序中刷出应用程序时,定期工作管理器在 Oreo 中不起作用
- php - 安全的 PHP & Java (Android) 加密和解密功能
- spring-batch - spring批处理中的重试处理逻辑
- mysql - 按 3 列以上的汇总分组
- javascript - 在 amcharts v4 中为每个气泡生成图例
- assembly - x86 指令前缀解码
- firebase - TypeError:无法在字符串“about:blank”上创建属性“href”
- c++ - 断点栏上的 Visual Studio 2017 不可见断点