在C++ openssl中d2i_PrivateKey函数总是失败,以及如何从private_key获取正确的公钥

zbdgwd5y  于 2023-05-20  发布在  其他
关注(0)|答案(1)|浏览(512)

我确切地知道需要二进制数据以及DER编码,这里我使用NID_X9_62_prime256v1(secp256r1)
我的python代码按照我的预期工作

from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, utils
from cryptography.hazmat.backends import default_backend
from cryptography.exceptions import InvalidSignature
import binascii

private_key_hex = '3041020100301306072a8648ce3d020106082a8648ce3d030107042730250201010420ecf1e6a65ac33acbda328d2269ea493d838d9f884cbe19d37b4c7db432d748d8'
private_key_bytes = binascii.unhexlify(private_key_hex)

private_key = serialization.load_der_private_key(private_key_bytes, password=None, backend=default_backend())

data = b'rm=355190&seid=10255&tms=1681095980140'

digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest.update(data)
digest_bytes = digest.finalize()

signature = private_key.sign(digest_bytes, ec.ECDSA(utils.Prehashed(hashes.SHA256())))

signature_hex = binascii.hexlify(signature)

# signature = binascii.unhexlify("3046022100e843d75182aff2db9cdd151efb6bbb8bbe61aabc5a5e2d26f0eb75071e8bb08e022100a1ff522bda09d48c264d48b05416f0177ec5dedd6a50200880a60008a733c66d")
# signature = binascii.unhexlify("3046022100c0f12409c66dd42a30ce6dc2f8ded65f44995aaeeee096412877c56c1cd35d31022100e0c5f16588e597a39e5894b09a89849171c99d6d43c2d6e80b97957fc7a83968")

print('signature:', signature_hex)

public_key = private_key.public_key()
public_key_bytes = public_key.public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo)
public_key_hex = binascii.hexlify(public_key_bytes).decode('utf-8')
print('公钥:', public_key_hex)

try:
    public_key.verify(signature, digest_bytes, ec.ECDSA(utils.Prehashed(hashes.SHA256())))
    print('signature success')
except InvalidSignature:
    print('signature failed')

在这个完全成功的代码示例中,如果私钥和椭圆曲线是固定的,那么公钥也应该是固定的

私钥:3041020100301306072a8648ce3d020106082a8648ce3d030107042730250201010420ecf1e6a65ac33acbda328d2269ea493d838d9f884cbe19d37b4c7db432d748d8
公钥:3059301306072a8648ce3d020106082a8648ce3d030107034200042754a2ad56e81284db868f65c38cc0c594c07a49ca3804e0b9565164f3b882c8e6ff98a23e69c4fe798cf263bebb9853f8136a8921c0aa9740b3d01da68d55d7
曲线:NID_X9_62_prime256v1(secp256r1)

当我使用这个私钥时,试图在C++中获得完全相同的公钥总是失败,这是我的代码,我尝试了两种方法第一:

void test_pub()
{
    // hex private_key
    const char* private_key_hex = "3041020100301306072a8648ce3d020106082a8648ce3d030107042730250201010420ecf1e6a65ac33acbda328d2269ea493d838d9f884cbe19d37b4c7db432d748d8";

    int private_key_len = strlen(private_key_hex) / 2;
    EC_KEY* ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
    BIGNUM* bn_private_key = BN_new();
    BN_bin2bn((const unsigned char*)private_key_hex, private_key_len, bn_private_key);
    EC_KEY_set_private_key(ec_key, bn_private_key);

    EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
    EC_POINT* public_key_point = EC_POINT_new(group);
    EC_POINT_mul(group, public_key_point, bn_private_key, NULL, NULL, NULL);
    EC_KEY_set_public_key(ec_key, public_key_point);

    // publickey to DER
    int public_key_der_len = i2d_EC_PUBKEY(ec_key, NULL);
    unsigned char* public_key_der = (unsigned char*)OPENSSL_malloc(public_key_der_len);
    unsigned char* p = public_key_der;
    i2d_EC_PUBKEY(ec_key, &p);

    // der publickey to hex
    char* public_key_hex = (char*)OPENSSL_malloc(public_key_der_len * 2 + 1);
    for (int i = 0; i < public_key_der_len; ++i) 
    {
        sprintf(&public_key_hex[i * 2], "%02x", public_key_der[i]);
    }

    printf("publickey: %s\n", public_key_hex);

    // free
    OPENSSL_free(public_key_hex);
    OPENSSL_free(public_key_der);
    BN_free(bn_private_key);
    EC_POINT_free(public_key_point);
    EC_KEY_free(ec_key);

    return;
}

i get public key结果如下,很明显,它与python结果不匹配,虽然可以进一步签名和验证public key:3059301306072a8648ce3d020106082a8648ce3d03010703420004c3a79d5a74e86f443be46278de639454e4c01863d687ba8f446cc545f10e3429dabc6c2e7a17ec00710ea86ac5eef711419c0dc62ec86a9085490bdc48169a51
然后我试了另一种方法:

void test_pub()
{

    const char* private_key_hex = "3041020100301306072a8648ce3d020106082a8648ce3d030107042730250201010420ecf1e6a65ac33acbda328d2269ea493d838d9f884cbe19d37b4c7db432d748d8";

    int private_key_len = strlen(private_key_hex) / 2;

    BIGNUM* bn_private_key = BN_new();
    BN_bin2bn((const unsigned char*)private_key_hex, private_key_len, bn_private_key);

    //use d2i_ECPrivateKey
    size_t der_len = strlen(private_key_hex) / 2;
    unsigned char* der_buf = (unsigned char*)malloc(der_len);
    hex_to_bin(private_key_hex, der_buf, der_len);

    EC_KEY* ec_key = d2i_ECPrivateKey(NULL, (const unsigned char**)&der_buf, der_len);
    if (ec_key == NULL)
    {
        unsigned long err = ERR_get_error();
        char err_str[256];
        ERR_error_string_n(err, err_str, sizeof(err_str));
        printf("Error decoding private key: %s\n", err_str);
    }
}

d2i_ECPrivateKey运行失败:私钥解码错误:错误:068000A8:asn1编码例程::错误标记
上面的C++代码是否有错误,openssl的官方示例程序很少,谁能提供一个关于d2i_ECPrivateKey的示例?谢谢
正确地将公钥与私钥分开

zdwk9cvp

zdwk9cvp1#

你的主要问题是你把private_key_hex当作一个二进制blob而不是一个十六进制值的字符串,而且你没有使用正确的OpenSSL API。
主要是你想使用d2i_PrivateKey从二进制数组中加载键,你可以使用它,例如。使用i2d_PUBKEY提取公钥。
下面是一个加载私钥并提取公钥的示例,并测试它是否等于上面的示例。我没有将私钥/公钥作为字符串,而是作为二进制数组。如果你需要将一个字符串转换成二进制数组,那么你可以自己写,或者使用openssl apis OPENSSL_hexstr2bufOPENSSL_buf2hexstr

#include <format>
#include <iostream>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/x509.h>

template<typename T, typename D>
std::unique_ptr<T, D> make_handle(T* handle, D deleter)
{
    return std::unique_ptr<T, D>{handle, deleter};
}

void CheckResult(int const result)
{
    if(!result)
    {
        auto const errorText = std::format("{}", ERR_error_string(ERR_get_error(), nullptr));
        throw std::exception(errorText.c_str());
    }
}

bool public_key_from_cer_hex()
{
    const unsigned char private_key_bytes[] = {
        0x30, 0x41, 0x02, 0x01, 0x00, 0x30, 0x13, 0x06,
        0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
        0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03,
        0x01, 0x07, 0x04, 0x27, 0x30, 0x25, 0x02, 0x01,
        0x01, 0x04, 0x20, 0xec, 0xf1, 0xe6, 0xa6, 0x5a,
        0xc3, 0x3a, 0xcb, 0xda, 0x32, 0x8d, 0x22, 0x69,
        0xea, 0x49, 0x3d, 0x83, 0x8d, 0x9f, 0x88, 0x4c,
        0xbe, 0x19, 0xd3, 0x7b, 0x4c, 0x7d, 0xb4, 0x32,
        0xd7, 0x48, 0xd8
    };

    const unsigned char public_key_bytes[] = {
        0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
        0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
        0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
        0x42, 0x00, 0x04, 0x27, 0x54, 0xa2, 0xad, 0x56,
        0xe8, 0x12, 0x84, 0xdb, 0x86, 0x8f, 0x65, 0xc3,
        0x8c, 0xc0, 0xc5, 0x94, 0xc0, 0x7a, 0x49, 0xca,
        0x38, 0x04, 0xe0, 0xb9, 0x56, 0x51, 0x64, 0xf3,
        0xb8, 0x82, 0xc8, 0xe6, 0xff, 0x98, 0xa2, 0x3e,
        0x69, 0xc4, 0xfe, 0x79, 0x8c, 0xf2, 0x63, 0xbe,
        0xbb, 0x98, 0x53, 0xf8, 0x13, 0x6a, 0x89, 0x21,
        0xc0, 0xaa, 0x97, 0x40, 0xb3, 0xd0, 0x1d, 0xa6,
        0x8d, 0x55, 0xd7
    };

    // decode the private key into EVP_PKEY
    long constexpr private_key_len = sizeof private_key_bytes;
    const unsigned char* private_key = private_key_bytes;

    auto const key = make_handle(d2i_PrivateKey(EVP_PKEY_EC, nullptr, &private_key, private_key_len), EVP_PKEY_free);
    CheckResult(static_cast<bool>(key));

    // encode the public key into bio
    auto const out = make_handle(BIO_new(BIO_s_mem()), BIO_free_all);

    CheckResult(i2d_PUBKEY_bio(out.get(), key.get()));

    // ensure null terminated
    char* p;
    auto const length = BIO_get_mem_data(out.get(), &p);
    CheckResult(length != -1);

    std::cout << "public_key: ";

    for (long i = 0; i < length; i++)
    {
        std::cout << std::format("{:02x}", static_cast<unsigned char>(p[i]));
    }

    std::cout << '\n';

    if(sizeof(public_key_bytes) == length)
    {
        for (long i = 0; i < length; i++)
        {
            if (static_cast<unsigned char>(p[i]) != public_key_bytes[i])
            {
                std::cout << "public key mismatch\n";
                return false;
            }
        }
    }
    else
    {
        std::cout << "public key length mismatch\n";
        return false;
    }

    return true;
}

相关问题