如何在带有 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()
函数对数据进行签名后,包含了以下代码,我可以获取 as
和 ar
数组中的字节。
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);
我有一个 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()
函数对数据进行签名后,包含了以下代码,我可以获取 as
和 ar
数组中的字节。
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);