公钥→证书?

信息安全 证书 证书颁发机构 openssl
2021-08-16 11:58:51

是否可以仅使用公钥将公钥转换为证书?

我已经看到以下内容,它需要一个私钥来形成一个证书请求,然后可以自签名:

openssl req -new -key privkey.pem -out cert.csr

但是,仅给定一个公钥(例如 SSH、OpenSSL、PEM、PKCS8 格式),我如何制作包含公钥的自签名证书(例如 PKCS12 格式)?

谢谢

基本上,我想获取我朋友的公共 PGP 密钥,将其转换为证书,然后在我的电子邮件程序中使用该证书来加密与他的电子邮件,而无需在 Thunderbird 中使用 Enigma 邮件。

2个回答

首先,您尝试做的事情是行不通的PGP 和 S/MIME 从根本上是不兼容的。将您朋友的 PGP 公钥放入 x.509 证书将不允许您使用 S/MIME 工具与他通信要么您都需要使用 S/MIME,要么都需要使用 PGP。你不能混合。

至于这是否可以做到:证书必须由某人签名因此,如果您有公钥,则可以将其嵌入到由其他人签名的证书中,但如果没有私钥,您将无法创建自签名证书。

但这里有一个问题:面向用户的工具(例如 openssl 命令行)只会创建证书(a)使用私钥自签名,或(b)通过签署请求。

并且签名请求本身使用它们嵌入的密钥进行签名。这个想法是,如果你要为某人创建一个证书,你想验证他们是否真的拥有你正在认证的密钥。

但是...当您深入了解内部结构时,例如使用 openSSL API 函数,您几乎可以做任何您想做的事情。您可以创建未签名的请求和证书[1](可能不会在任何地方得到认可),并且您可以仅使用公钥和一些属性来创建证书。

您可能想直接查看使用 openssl C api,或者可能是为其他语言构建的优秀库之一。

这是 Ruby 中的一个示例:此示例由grawity提供作为编辑我没有测试过这段代码。

#!/usr/bin/env ruby
require 'openssl'

pkey_data = File.read("id_rsa")
pkey_password = ""
subject = "/O=Honest Achmed's Used Cars and Certificates/OU=Self-made/CN=Geremia's cert"
expiry_years = 1

# Fill basic v1 fields
cert = OpenSSL::X509::Certificate.new
cert.version = 2  # for X.509 v3
cert.serial = 1
cert.subject = OpenSSL::X509::Name.parse(subject)
cert.issuer = cert.subject
cert.not_before = Time.now
cert.not_after = Time.now + (expiry_years * 365 * 86400)

# Add v3 extensions
extf = OpenSSL::X509::ExtensionFactory.new
extf.subject_certificate = cert
extf.issuer_certificate = cert
cert.extensions = [
    # basicConstraints is marked as 'critical' here
    extf.create_extension("basicConstraints", "CA:FALSE", true),
    extf.create_extension("subjectKeyIdentifier", "hash"),
    # 'copy' means needed information will be taken from subjectKeyIdentifier
    extf.create_extension("authorityKeyIdentifier",
        "keyid:copy, issuer:copy"),
    extf.create_extension("keyUsage",
        "digitalSignature, keyEncipherment, nonRepudiation"),
    extf.create_extension("extendedKeyUsage",
        "clientAuth, serverAuth, emailProtection"),
]

# Add public key and self-sign – if you omit the cert.sign()
# call, you WILL have an unsigned certificate made just with the public key.
pkey = OpenSSL::PKey::RSA.new(pkey_data, pkey_password)
cert.public_key = pkey
cert.sign(pkey, OpenSSL::Digest::SHA1.new)

# Output in PEM format (aka base64-encoded DER)
puts cert.to_pem
  • [1]大约十年前,我为 .NET 创建了一个 OpenSSL 互操作库,它偶然做了这些事情。幸运的是,除了我,没有人见过这个项目。

PKCS#12是一种通用存档格式,适用于所有内容,但在实践中,它用于存储证书及其私钥您没有朋友的私钥,只有公钥,因此制作 PKCS#12 存档没有什么意义。

出于同样的原因,您将无法制作自签名证书,因为生成签名需要使用您没有的私钥。但是,证书中的自签名并没有什么神奇之处。X.509证书通常由证书颁发机构签名(CA 被称为“颁发证书”),因此证书格式包含一个签名字段。当证书不是由 CA 生成时,该字段必须填充一些值,这是传统的使用自签名——但该签名不包含太多有用信息,大多数软件根本不会费心验证它;他们只需要一些大小合适的 blob,以便编码和解码例程工作。

因此,如果您只想要一个“作为证书”的公钥,您可以将这些值与一些随机垃圾一起填充,而不是来自 CA 的签名。正如@tylerl 解释的那样,如果您使用一些实际的 CA 软件(例如OpenSSL)来制作证书会更容易,因为结构在所有ASN.1细节中都很复杂。大多数成熟的 CA 产品都希望将公钥作为PKCS#10 请求提供,这将包括公钥,但也使用相应的私钥签名,作为私钥所有者确实希望获得证书的明确指示。然而,这并不是签名系统所固有的;任何 CA 使用任意数据(包括从任何来源获得的公钥)构建和签署证书在技术上都是可行的。直接使用一些证书感知库可以解决问题。


现在是“PGP”部分。OpenPGP 格式X.509 截然不同,这也适用于电子邮件的加密方式;当涉及 X.509 证书时,编码依赖于S/MIME而不是 OpenPGP。对于您正在尝试做的事情,这有两个后果:

  • 不能保证您朋友的公钥符合 X.509 格式。RSA 密钥应该没问题;然而,OpenPGP 经常使用ElGamal 密钥,根据 X.509 规则没有广泛的标准编码。您的 S/MIME 软件(例如 Thunderbird)将无法理解和使用 X.509 证书中的 ElGamal 密钥。

  • 即使您进行加密,您的朋友也会遇到同样的问题。他必须说服他的软件使用他仅以 OpenPGP 格式存储的私钥应用 S/MIME 及其解密规则。

特别是后一点,意味着您的朋友很可能还必须提取他的私钥并为公钥获取某种 X.509 证书。在这种情况下,让做这个工作会更简单:一旦他为他的公钥建立或获得了证书,让他把它发送给你。


请注意,PKI的主要目的是传达一些关于身份和公钥之间绑定的信任。通过从 PGP 世界的数据构建您自己的 X.509 证书,您不知何故忽略了这种信任。因此,您将需要其他方法来确保您使用的是正确的公钥。这不一定是错误的,但请注意您是在“手动”执行操作,这需要您了解正在发生的事情,包括精细的加密细节。