首页 > 解决方案 > 使用 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 序列化时做错了。但对于我的生活,我看不到它是什么。任何帮助表示赞赏!

标签: c++opensslasn.1ecdsa

解决方案


嗬!这太傻了……

BN_hex2bn() 中的“十六进制”并不意味着十六进制值的数组。相反,它意味着十六进制值的字符串表示!

所以在我上面的例子中,如果我改为初始化

const unsigned char sigbuf[] = "37258a3cf0056e239783aef5840a5e0a"
                               "1fc88a548405341d8286477c145114f8"
                               "0af4bccf58efcd69bdc023f1e2966aa8"
                               "28cf3560e6756d894a609b2b2a6d0651";
const int sig_len = 128;

我最终得到了一个不错的 ECDSA_SIG,因此也得到了不错的 der 和 derlen。

希望这可以帮助将来的人。有时我真的很讨厌 OpenSSL 文档……


推荐阅读