ECDSA 签名验证:Go 与 OpenSSL
ECDSA signature verification: Go vs OpenSSL
我正在尝试使用 public 密钥验证哈希的 ECDSA 签名。我已经编写了一个成功执行此操作的小型 Go 程序,但我一直无法将其移植到 C++。
这是我的输入数据:
- Public 键:
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDd9VXmpHjV4voFO+0ZoFPlRr5icZXquxsr9EkaOUO9B7Wl1DAgGI0EKCm1++Bl2Od32xZFeFuG07OTpTMVOCPA==
- 哈希:
562d6ddfb3ceb5abb12d97bc35c4963d249f55b7c75eda618d365492ee98d469
- 签名:
304502204d6d070117d445f4c2fcdbd4df037a1c8cfee2a166353c2e562cd5efd06e914d022100bb06439ded1478bd19022519dc06a84ba18ea4bf30ea9eb9ea90f8b66dad12c7
这是我的工作 go 程序:
package main
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/base64"
"encoding/hex"
)
func main() {
key_, _ := base64.StdEncoding.DecodeString("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDd9VXmpHjV4voFO+0ZoFPlRr5icZXquxsr9EkaOUO9B7Wl1DAgGI0EKCm1++Bl2Od32xZFeFuG07OTpTMVOCPA==")
key, _ := x509.ParsePKIXPublicKey(pkey_)
msg, _ := hex.DecodeString("562d6ddfb3ceb5abb12d97bc35c4963d249f55b7c75eda618d365492ee98d469")
sig, _ := hex.DecodeString("304502204d6d070117d445f4c2fcdbd4df037a1c8cfee2a166353c2e562cd5efd06e914d022100bb06439ded1478bd19022519dc06a84ba18ea4bf30ea9eb9ea90f8b66dad12c7")
valid := ecdsa.VerifyASN1(key.(*ecdsa.PublicKey), msg[:], sig)
if !valid {
panic("key invalid")
}
}
这是我损坏的 C++ 程序,它在最后一个断言时失败了。我想知道这是为什么:
#include <cassert>
#include <cstdlib>
#include <string>
#include <stdexcept>
#include <openssl/bio.h>
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
std::string decode_hex_str(std::string const &hex) {
std::string str;
for (std::size_t i { 0 }; i < hex.size(); i += 2) {
auto byte { hex.substr(i,2) };
str.push_back(std::strtol(byte.c_str(), nullptr, 16));
}
return str;
}
int main() {
std::string key_ {
R"(
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDd9VXmpHjV4voFO+0ZoFPlRr5icZXquxsr9EkaOUO9B7Wl1DAgGI0EKCm1++Bl2Od32xZFeFuG07OTpTMVOCPA==
-----END PUBLIC KEY-----
)"
};
std::string msg { decode_hex_str("562d6ddfb3ceb5abb12d97bc35c4963d249f55b7c75eda618d365492ee98d469") };
std::string sig { decode_hex_str("304502204d6d070117d445f4c2fcdbd4df037a1c8cfee2a166353c2e562cd5efd06e914d022100bb06439ded1478bd19022519dc06a84ba18ea4bf30ea9eb9ea90f8b66dad12c7") };
auto bio { BIO_new_mem_buf(key_.data(), key_.length()) };
assert(bio);
auto key { PEM_read_bio_EC_PUBKEY(bio, nullptr, nullptr, nullptr) };
EVP_MD_CTX *mdctx { nullptr };
EVP_PKEY *pkey { nullptr };
int status;
bool verified;
mdctx = EVP_MD_CTX_new();
assert(mdctx);
pkey = EVP_PKEY_new();
assert(pkey);
assert(EVP_PKEY_assign_EC_KEY(pkey, key) == 1);
assert(EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey) == 1);
assert(EVP_DigestVerifyUpdate(mdctx, msg.data(), msg.length()) == 1);
status = EVP_DigestVerifyFinal(mdctx, reinterpret_cast<unsigned char const *>(sig.data()), sig.length());
switch (status) {
case 0:
verified = false;
break;
case 1:
verified = true;
break;
default:
assert(false);
}
EVP_MD_CTX_free(mdctx);
EVP_PKEY_free(pkey);
assert(verified);
}
EVP_DigestVerify{Init/Update/Final}
做两件事:
- 他们对输入数据进行哈希处理
- 然后他们根据签名验证计算出的散列
另一方面,ecdsa.VerifyASN1
只执行上面的第二步,已经将散列作为输入。
执行此操作的 OpenSSL 函数是 ECDSA_verify
。
所以你可以这样做:
BIO* bio = BIO_new_mem_buf(key_.data(), key_.length());
EC_KEY* key = PEM_read_bio_EC_PUBKEY(bio, nullptr, nullptr, nullptr);
int verified = ECDSA_verify(
0,
(unsigned char const*)msghash.data(), msghash.length(),
(unsigned char const*)sig.data(), sig.length(),
key
);
std::cout << verified << std::endl;
应该打印 1
.
请注意,代码中的变量命名令人困惑 - 变量 msg
实际上包含消息 digest。所以 msghash
或 digest
会是一个更好的名字。进行加密时,变量命名要准确。
我正在尝试使用 public 密钥验证哈希的 ECDSA 签名。我已经编写了一个成功执行此操作的小型 Go 程序,但我一直无法将其移植到 C++。
这是我的输入数据:
- Public 键:
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDd9VXmpHjV4voFO+0ZoFPlRr5icZXquxsr9EkaOUO9B7Wl1DAgGI0EKCm1++Bl2Od32xZFeFuG07OTpTMVOCPA==
- 哈希:
562d6ddfb3ceb5abb12d97bc35c4963d249f55b7c75eda618d365492ee98d469
- 签名:
304502204d6d070117d445f4c2fcdbd4df037a1c8cfee2a166353c2e562cd5efd06e914d022100bb06439ded1478bd19022519dc06a84ba18ea4bf30ea9eb9ea90f8b66dad12c7
这是我的工作 go 程序:
package main
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/base64"
"encoding/hex"
)
func main() {
key_, _ := base64.StdEncoding.DecodeString("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDd9VXmpHjV4voFO+0ZoFPlRr5icZXquxsr9EkaOUO9B7Wl1DAgGI0EKCm1++Bl2Od32xZFeFuG07OTpTMVOCPA==")
key, _ := x509.ParsePKIXPublicKey(pkey_)
msg, _ := hex.DecodeString("562d6ddfb3ceb5abb12d97bc35c4963d249f55b7c75eda618d365492ee98d469")
sig, _ := hex.DecodeString("304502204d6d070117d445f4c2fcdbd4df037a1c8cfee2a166353c2e562cd5efd06e914d022100bb06439ded1478bd19022519dc06a84ba18ea4bf30ea9eb9ea90f8b66dad12c7")
valid := ecdsa.VerifyASN1(key.(*ecdsa.PublicKey), msg[:], sig)
if !valid {
panic("key invalid")
}
}
这是我损坏的 C++ 程序,它在最后一个断言时失败了。我想知道这是为什么:
#include <cassert>
#include <cstdlib>
#include <string>
#include <stdexcept>
#include <openssl/bio.h>
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
std::string decode_hex_str(std::string const &hex) {
std::string str;
for (std::size_t i { 0 }; i < hex.size(); i += 2) {
auto byte { hex.substr(i,2) };
str.push_back(std::strtol(byte.c_str(), nullptr, 16));
}
return str;
}
int main() {
std::string key_ {
R"(
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDd9VXmpHjV4voFO+0ZoFPlRr5icZXquxsr9EkaOUO9B7Wl1DAgGI0EKCm1++Bl2Od32xZFeFuG07OTpTMVOCPA==
-----END PUBLIC KEY-----
)"
};
std::string msg { decode_hex_str("562d6ddfb3ceb5abb12d97bc35c4963d249f55b7c75eda618d365492ee98d469") };
std::string sig { decode_hex_str("304502204d6d070117d445f4c2fcdbd4df037a1c8cfee2a166353c2e562cd5efd06e914d022100bb06439ded1478bd19022519dc06a84ba18ea4bf30ea9eb9ea90f8b66dad12c7") };
auto bio { BIO_new_mem_buf(key_.data(), key_.length()) };
assert(bio);
auto key { PEM_read_bio_EC_PUBKEY(bio, nullptr, nullptr, nullptr) };
EVP_MD_CTX *mdctx { nullptr };
EVP_PKEY *pkey { nullptr };
int status;
bool verified;
mdctx = EVP_MD_CTX_new();
assert(mdctx);
pkey = EVP_PKEY_new();
assert(pkey);
assert(EVP_PKEY_assign_EC_KEY(pkey, key) == 1);
assert(EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey) == 1);
assert(EVP_DigestVerifyUpdate(mdctx, msg.data(), msg.length()) == 1);
status = EVP_DigestVerifyFinal(mdctx, reinterpret_cast<unsigned char const *>(sig.data()), sig.length());
switch (status) {
case 0:
verified = false;
break;
case 1:
verified = true;
break;
default:
assert(false);
}
EVP_MD_CTX_free(mdctx);
EVP_PKEY_free(pkey);
assert(verified);
}
EVP_DigestVerify{Init/Update/Final}
做两件事:
- 他们对输入数据进行哈希处理
- 然后他们根据签名验证计算出的散列
ecdsa.VerifyASN1
只执行上面的第二步,已经将散列作为输入。
执行此操作的 OpenSSL 函数是 ECDSA_verify
。
所以你可以这样做:
BIO* bio = BIO_new_mem_buf(key_.data(), key_.length());
EC_KEY* key = PEM_read_bio_EC_PUBKEY(bio, nullptr, nullptr, nullptr);
int verified = ECDSA_verify(
0,
(unsigned char const*)msghash.data(), msghash.length(),
(unsigned char const*)sig.data(), sig.length(),
key
);
std::cout << verified << std::endl;
应该打印 1
.
请注意,代码中的变量命名令人困惑 - 变量 msg
实际上包含消息 digest。所以 msghash
或 digest
会是一个更好的名字。进行加密时,变量命名要准确。