【问题标题】:Convert a plain public key to PEM将普通公钥转换为 PEM
【发布时间】:2019-05-20 10:23:28
【问题描述】:

我使用 Prime-256v1 从受信任的应用程序生成了一个 EC 密钥对,并将公钥导出到普通操作系统。密钥大小为 65 字节。公钥是纯格式(只有十六进制密钥)。

导出的公钥需要提供给图书馆(第三方)。该库需要 PEM 格式的公钥。

搜索了一段时间,我的理解是先把plain key转换成DER格式,再把结果转换成PEM。但是我找不到任何用于从普通密钥转换为 DER 或 PEM 的 API。

找到了这个 API 其中 PEM_ASN1_write((i2d_of_void*)i2d_PUBKEY,PEM_STRING_PUBLIC,outfile,ctx->cert->key->public_key,NULL,NULL,0,NULL,NULL);从文件指针转换。但我无法进行文件操作,因为不可能进行文件存储。我正在缓冲区中获取公钥。

我在 C 程序中执行此操作,如果有任何示例代码或 API 将普通十六进制密钥转换为 PEM。

提前致谢

【问题讨论】:

    标签: openssl type-conversion pem


    【解决方案1】:

    您在评论4bb5f0c58cc71806ec4d228b730dd252947e679cce05f71d434787fe228f14c799cf8965780bb308aa722ac179bfa5fd57592a72cbdcfe89ab61ad5d77251186d 中提供的值长度错误。它是 129 个十六进制数字,又名半字节,但 prime256v1(又名 secp256r1 或 P-256)的编码点必须是以 04 开头的 65 个八位字节(未压缩)或以 02 或 03 开头的 33 个八位字节(压缩)。当十六进制字符串(或十进制或八进制)表示 整数 时,您可以在不更改数字的情况下删除或添加前导零,但 EC 点 不是整数。

    您说您的代码提供了 65 个字节,这对于未压缩是正确的。用那个。

    与大多数其他 PK 算法不同,OpenSSL 没有用于 ECC 的特定于算法的 DER(以及 PEM)格式,因此任何准确描述为需要 PEM 公钥(并且不是证书,这是传输公钥的传统方式)必须使用 X.509/PKIX SubjectPublicKeyInfo 格式,OpenSSL 将其命名为 PUBKEY(如 Shane 的回答中所示)并映射到程序中的 EVP_PKEY 结构。要从原始公共点构建它,您必须首先将其与曲线标识结合以形成实际的 EC 公钥,然后将其标识为 EC 以形成 generic publickey,并编写它。 OpenSSL 可以使用“mem”类型的 BIO 对内存进行 I/O(包括 PEM),无需任何文件。 (并且使这成为一个编程问题,而不是一个命令行问题,这确实是题外话,属于另一个堆栈,尽管这里有很多人被问到。)

    /* SO #56218946 */
    #include <stdio.h>
    #include <stdlib.h>
    #include <openssl/ec.h>
    #include <openssl/err.h>
    #include <openssl/evp.h>
    #include <openssl/objects.h>
    #include <openssl/pem.h>
    #ifdef _WIN32
    #include <openssl/applink.c>
    #endif
    
    void err (const char *label){  // for test; improve for real code
      fprintf (stderr, "Error in %s:\n", label);
      ERR_print_errors_fp (stderr);
      exit (1);
    }
    
    int main (void) //(int argc, char**argv)
    {
      ERR_load_crypto_strings(); /* or SSL_load_error_strings */
      //OPENSSL_add_all_algorithms_noconf(); /* for PKCS#8 */
    
      // test data -- replace for real use
      char hex [] = "04bb5f0c58cc71806ec4d228b730dd252947e679cce05f71d434787fe228f14c799cf8965780bb308aa722ac179bfa5fd57592a72cbdcfe89ab61ad5d77251186d";
      unsigned char raw [65]; for( int i = 0; i < 65; i++ ){ sscanf(hex+2*i, "%2hhx", raw+i); }
    
      EC_KEY *eck = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); /* or OBJ_txt2nid("prime256v1") */
      if( !eck ) err("ECCnewbyname");
      EC_KEY_set_asn1_flag(eck, OPENSSL_EC_NAMED_CURVE); /* needed below 1.1.0 */
      const unsigned char *ptr = raw;
      if( !o2i_ECPublicKey (&eck, &ptr, sizeof(raw)) ) err("o2iECPublic=point");
      EVP_PKEY * pkey = EVP_PKEY_new(); 
      if( !EVP_PKEY_assign_EC_KEY(pkey, eck) ) err("PKEYassign");
    
      BIO *bio = BIO_new(BIO_s_mem());
      if( !PEM_write_bio_PUBKEY (bio, pkey) ) err("PEMwrite");
      char *pem = NULL; long len = BIO_get_mem_data (bio, &pem);
      fwrite (pem, 1, len, stdout); // for test; for real use as needed
      return 0;
    }
    

    (添加)另外,由于 X9.62 点编码对于给定曲线是固定大小的,因此给定曲线的 DER 编码 SPKI 结构实际上由一个固定标头和后跟点值组成,因此您可以改为连接该固定标题并进行通用PEM转换:输出破折号-BEGIN行,输出带有换行符的base64,输出破折号-END行。如果知道 ASN.1 是如何工作的,虽然不难计算出标题,但一种捷径是为一个虚拟密钥生成 SPKI 编码,例如openssl ecparam -genkey -name prime256v1 -outform der 并删除最后 65 个字节(如果使用 -conv_form 压缩,则删除 33 个字节)。与 Loading raw 64-byte long ECDSA public key in Java 上的 Java 变体进行比较。

    【讨论】:

    • 非常感谢 dave_thompson_085!我已经编译并运行了代码,它就像一个魅力,完美的艺术!
    • 您好 dave_thompson_085,您愿意分享一些关于 openssl API 的见解吗?好的文档可能。我是网络安全和密码学的初学者。如果您能建议一些很棒的材料开始,那就太好了。提前致谢。
    • sree:Stack 被明确设计为不是一个讨论论坛,并且对场外资源的请求正式偏离主题。但我是一个传统主义者,使用手册页和源代码,尤其是apps/*.cssl/**/*.c 的源代码。 wiki.openssl.org 上有一个 wiki,但我觉得它不是很有用; YMMV。
    【解决方案2】:

    使用 openssl 实用程序,您可以使用的命令是:

    openssl ec -in .\prime256pubkey.cer -pubin -inform der -pubout -outform pem -out .\prime256pubkey.pem

    要使用代码重现这一点,您需要使用这些主要的 openssl api

    围绕 C openssl API 转换为 C++ 代码的 openssl 示例将是:

    template<typename T, typename D>
    std::unique_ptr<T, D> make_handle(T* handle, D deleter)
    {
        return std::unique_ptr<T, D>{handle, deleter};
    }
    
    bool convert_der_ec_pubkey_to_pem()
    {
        // read in DER ec public key
        auto infile = make_handle(BIO_new_file("prime256pubkey.cer", "rb"), BIO_free);
        if(!infile) return false;
    
        auto const eckey = make_handle(d2i_EC_PUBKEY_bio(infile.get(), nullptr), EC_KEY_free);
        if(!eckey) return false;
    
        infile.reset();
    
        // write out PEM ec public key
        auto outfile = make_handle(BIO_new_file("prime256pubkey.pem", "w"), BIO_free);
        if(!outfile) return false;
    
        return PEM_write_bio_EC_PUBKEY(outfile.get(), eckey.get()) != 0;
    }
    

    【讨论】:

    • 什么是make_handle?它是在哪里定义的?
    • 这是一个简单的 std:unique_ptr 包装器,用于将 OpenSSL 原始指针类型转换为 raii 类。它只是使错误处理更容易。如果您阅读上面的代码,则它是在函数上方定义的。你不需要使用它,只需要记住在完成 OpenSSL 对象后调用 free 函数,否则你会泄漏内存。我不喜欢给出泄漏内存的示例代码。
    • 谢谢你,谢恩鲍威尔!我已经编译了程序。我总是将'eckey'设为NULL。您能否也分享“.cer”文件的内容?我有这样的原始公钥:4bb5f0c58cc71806ec4d228b730dd252947e679cce05f71d434787fe228f14c799cf8965780bb308aa722ac179bfa5fd57592a72cbdcfe89ab61ad5d77251186d。目的是将此公钥转换为“.pem/.der”格式。任何提示将不胜感激。
    • 如果 openssl 命令行按我评论的那样工作,那么它与代码相同。如果命令行不适合您,那么我的代码将无法正常工作。您很可能没有 EC 公钥仅 DER 格式的文件。如果您删除 openssl 命令的“-pubin”并且它可以工作,那么您就有一个 DER 格式的私钥。如果是这种情况,您需要使用 d2i_ECPrivateKey_bio 来加载证书文件。
    • 谢谢谢恩!赞成你所有的答案;)我有以下公钥:root@boss:/home/boss/workspace/sree# openssl ec -in private_key.pem -text -noout read EC key Private-Key: (256 bit) priv: b9:cd:04:cd:83:52:d2:41:67:b3:94:7e:45:c2:a7: 40:c5:17:ca:ba:f9:f5:8a:b3:34: a2:cf:e8:16:3a: cf:ff pub: 0​​4:bb:5f:0c:58:cc:71:80:6e:c4:d2:28:b7:30:dd: 25:29:47 :e6:79:cc:e0:5f:71:d4:34:78:7f:e2:28: f1:4c:79:9c:f8:96:57:80:bb:30:8a:a7:22 :ac:17: 9b:fa:5f:d5:75:92:a7:2c:bd:cf:e8:9a:b6:1a:d5: d7:72:51:18:6d ASN1 OID: prime256v1 NIST 曲线: P-256
    猜你喜欢
    • 1970-01-01
    • 2013-04-12
    • 1970-01-01
    • 2011-04-03
    • 2017-11-11
    • 1970-01-01
    • 1970-01-01
    • 2012-02-23
    • 1970-01-01
    相关资源
    最近更新 更多