尽管OpenSSL始终假定ECDSA签名是ASN.1 / DER编码的,但我还需要能够验证P1363编码的签名。 (有关这两种表格的详细介绍,请参见the answer to this SO question。)
[想法是修补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进行序列化做错了。但是对于我的一生,我不知道那是什么。任何帮助表示赞赏!
Do!这太傻了...
BN_hex2bn()中的“十六进制”并不表示十六进制值的数组。相反,它表示十六进制值的字符串表示!
因此,在我上面的示例中,如果我改为像这样初始化
const unsigned char sigbuf[] = "37258a3cf0056e239783aef5840a5e0a"
"1fc88a548405341d8286477c145114f8"
"0af4bccf58efcd69bdc023f1e2966aa8"
"28cf3560e6756d894a609b2b2a6d0651";
const int sig_len = 128;
我最终得到了不错的ECDSA_SIG,因此也得到了不错的der和derlen。
希望这对以后的人有所帮助。有时候我真的很讨厌OpenSSL文档...
所以real答案是使用BN_bin2bn(),就像上面的示例中一样
s->r = BN_bin2bn(pr, hex_len, NULL);
请注意,这如何采用长度参数。这突显了@WhozCraig指出的事实,它实际上只是在这里处理的八位字节序列,而不是字符串。
还请注意,文档指出:>
BN_bin2bn()将长度为len在s的big-endian形式的正整数转换为BIGNUM并将其放置在ret中。如果ret为NULL,则创建一个新的BIGNUM。
因此,因为整数必须为正,所以最终需要使用填充。