首页 > 解决方案 > 将普通公钥转换为 PEM

问题描述

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

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

经过一段时间的搜索,我的理解是先从普通密钥转换为DER格式,然后将结果转换为PEM。但是我找不到任何用于从普通密钥转换为 DER 或 PEM 的 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。

提前致谢

标签: openssltype-conversionpem

解决方案


您在评论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 以形成通用公钥,并编写它。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 是如何工作的,计算标题并不难,但一种捷径是使用 eg 为虚拟密钥生成 SPKI 编码,openssl ecparam -genkey -name prime256v1 -outform der然后删除最后 65 个字节(如果使用 压缩,则删除 33 个字节-conv_form)。与Loading raw 64-byte long ECDSA public key in Java中的 Java 变体进行比较。


推荐阅读