如何在带有 OPENSSL 的 C++ 中使用 ECDSA 算法对数据进行签名?

How to sign data using ECDSA algorithm, in C++ with OPENSSL?

我有一个 32 字节的 SHA256 摘要数据,需要使用 ECDSA 对其进行签名。私钥位于 .pem 文件中。我已经使用以下命令在控制台中完成了它:openssl.exe dgst -sha256 -sign ecc.pem -out %sigfile% %data_file%。现在我需要在 C++ 中完成,我得到的结果与在控制台中不一样。

考虑到我在控制台中使用的命令,下面的代码有什么问题?

bool sign(unsigned char *digest) {

    BIO *b_pem;
    EVP_PKEY* pkey2 = nullptr;
    const char * fileName = "ecc.pem";
    pkey2 = EVP_PKEY_new();
    b_pem = BIO_new_file(ecFileName, "rb");
    
    PEM_read_bio_PrivateKey(b_pem, &pkey, NULL, NULL))

    unsigned char sign[200] = {0};
    EVP_MD_CTX *mctx;
    const EVP_MD *md = EVP_get_digestbyname("SHA256");
    size_t signlen = 0;

    mctx = EVP_MD_CTX_new();
    
    if (EVP_DigestSignInit(mctx, NULL, md, NULL, pkey)<=0) {
        return false;
    }
    if (EVP_DigestSignUpdate(mctx, digest, 32) <= 0) {
        return false;
    }
    if (EVP_DigestSignFinal(mctx, sign, &signlen) <=0) {
        return false;
    }

    EVP_MD_CTX_free(mctx);
    return true;
}

存在处理缓冲区长度的错误 sign。您分配了 200 个字节,但从未告诉 EVP_DigestSignFinal,因为您设置了 signlen = 0。因此没有任何内容写入您的缓冲区。

根据 the documentation,您需要将 signlen 设置为缓冲区中可用的最大长度,并且在调用后它包含实际使用的字节数。

我找到了我一直在寻找的解决方案。阅读一些使用 OpenSSL 的 ECDSA 的项目示例,我意识到我需要将 ASN1 编码的签名转换为原始签名字节。因此,这是我用来获得所需结果的方法:

通过 EVP_DigestSignFinal() 函数对数据进行签名后,包含了以下代码,我可以获取 asar 数组中的字节。

ECDSA_SIG * ec_sig;
const BIGNUM *er = NULL;
const BIGNUM *es = NULL;
unsigned char *ar;
unsigned char *as;
size_t slen = EVP_PKEY_size(pkey);

ec_sig = d2i_ECDSA_SIG(NULL, (const unsigned char **)&sign, slen);
ECDSA_SIG_get0(ec_sig, &er, &es);

ar = (unsigned char*)malloc(BN_num_bytes(er));
as = (unsigned char*)malloc(BN_num_bytes(es));

BN_bn2bin(er, ar);
BN_bn2bin(es, as);