使用 OpenSSL API 读取公钥的密码回调

Posted

技术标签:

【中文标题】使用 OpenSSL API 读取公钥的密码回调【英文标题】:Password callback for reading public key with OpenSSL API 【发布时间】:2019-02-23 22:20:11 【问题描述】:

在使用公钥加密时,通常习惯于以加密格式存储私钥,因为它们当然应该是秘密的。这反映在 OpenSSL C API 中,它提供了像 PEM_write_PrivateKey 这样的函数,它将一个可选密码作为函数参数,用于加密密钥(如 AES)。然后,当从磁盘读回加密密钥时,OpenSSL API 提供像PEM_read_PrivateKey 这样的函数,它允许用户提供用作回调的函数指针,以便应用程序可以为 OpenSSL 提供加密密钥的密码。

但让我感到困惑的是,OpenSSL API 似乎还允许用户在读取 Public 密钥时提供密码回调。例如,一个用于读取公钥的 API 函数签名是:

 EVP_PKEY *PEM_read_PUBKEY(FILE *fp, EVP_PKEY **x,
                                        pem_password_cb *cb, void *u);

那么在读取 public 密钥时提供密码回调的目的是什么? AFAIK 加密公钥是没有意义的,因为根据定义,公钥应该可供公众使用。那么为什么OpenSSL API这里有一个函数参数接受密码回调呢?

【问题讨论】:

您是否正在将 C 语言与 C++ 混合使用?你有两个标签,它们是不同的语言。 不,我只是把两者都放了,因为两者都可以使用 OpenSSL API 在这种情况下,您应该添加 pascal、FORTRAN 和一整套将与 C 样式界面互操作的其他语言。最好只标记您感兴趣的目标语言。如果您不关心语言,请使用与语言无关的语言。 任何 PEM 都可以加密,甚至是公钥,尽管通常只有私钥是。但也许发行者想提供一个公钥,但通过只提供给授权用户的密码来限制谁可以实际使用它。 【参考方案1】:

如this comment 中所述,任何 PEM 编码的数据都可以加密。隐私增强邮件 (PEM) 的消息加密在 RFC 1421 中定义,在您的问题的上下文中,查看示例消息很有趣 section 4.6 Summary of Encapsulated Header Fields

-----BEGIN PRIVACY-ENHANCED MESSAGE-----
Proc-Type: 4,ENCRYPTED
Content-Domain: RFC822
DEK-Info: DES-CBC,F8143EDE5960C597
Originator-ID-Symmetric: linn@zendia.enet.dec.com,,
Recipient-ID-Symmetric: linn@zendia.enet.dec.com,ptf-kmc,3
Key-Info: DES-ECB,RSA-MD2,9FD3AAD2F2691B9A,
          B70665BB9BF7CBCDA60195DB94F727D3
Recipient-ID-Symmetric: pem-dev@tis.com,ptf-kmc,4
Key-Info: DES-ECB,RSA-MD2,161A3F75DC82EF26,
          E2EF532C65CBCFF79F83A2658132DB47

LLrHB0eJzyhP+/fSStdW8okeEnv47jxe7SJ/iN72ohNcUk2jHEUSoH1nvNSIWL9M
8tEjmF/zxB+bATMtPjCUWbz8Lr9wloXIkjHUlBLpvXR0UrUzYbkNpk0agV2IzUpk
J6UiRRGcDSvzrsoK+oNvqu6z7Xs5Xfz5rDqUcMlK1Z6720dcBWGGsDLpTpSCnpot
dXd/H5LMDWnonNvPCwQUHt==
-----END PRIVACY-ENHANCED MESSAGE-----

查看 OpenSSL 的 1.1 分支,它有一个函数 PEM_read_bio(),支持读取此类消息并将其拆分为名称(如顶行所示)、标题(下面的名称-值对)和数据(base64 编码的东西):

 int PEM_read_bio(BIO *in, char **name, char **header,
                  unsigned char **data, long *len);

所有 OpenSSL PEM_read_XYZ() 函数都会从 PEM_bytes_read_bio() 调用它,因为它们都是通过宏扩展以相同的方式实现的。该函数包含以下调用:

PEM_read_bio(bp, &nm, &header, &data, &len)

拆分消息,然后

PEM_get_EVP_CIPHER_INFO(header, &cipher);

找出在该消息的标头中找到哪种类型的加密信息并用它填充EVP_CIPHER_INFO对象,然后

PEM_do_header(&cipher, data, &len, cb, u);

根据找到的密码信息对数据进行解密——如果需要,再次解密。请注意代表回调的 cb 参数,这是一种在需要时获取任何密码短语输入的机制。

现在可能令人困惑的是,某些私钥格式(例如 PKCS#8)也有自己的独立于 PEM 编码的加密信息存储机制。从技术上讲,应该可以对此类密钥应用两次加密:一次在 PEM 级别,一次在 PKCS#8 级别。不过,用于生成或转换为 PKCS#8 格式密钥的 OpenSSL 工具似乎没有提供该选项。此外,除非还包含私钥,否则这些工具似乎都没有公开加密任何生成的公钥 PEM 文件的选项。

您可以检查一些输出,看看它们是否符合我的故事。首先,生成一个 PKCS#1 格式的 RSA 密钥对,不加密:

$ openssl genrsa
Generating RSA private key, 2048 bit long modulus (2 primes)
.................+++++
............+++++
e is 65537 (0x010001)
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAlcnR/w7zPoLrhuqFvcfz5fn8DFb0fEcCKOKSj+x+JJxGth9P
rJbxkt4pRXxbMIL0fX59HN5bRvQh2g59l/kfr30kCOnclap9nRrohWyg2i7720Cw
<truncated>

然后是相同的命令,但使用加密,这发生在 PEM 级别,正如您在标题中看到的那样:

$ openssl genrsa -des3
Generating RSA private key, 2048 bit long modulus (2 primes)
.....................+++++
....................+++++
e is 65537 (0x010001)
Enter pass phrase:
Verifying - Enter pass phrase:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,D90861647707F687

DIupLghCjcvpLenqAAULaJj1EDvUUfc2Xc58YVh7rMTSVgLwZ+9CtnUQJcup4aUQ
a1EdGXTadwBQB2jTtiFJbH67/5D26PHXPnM+YN2rnoReOExVS7hKu3DTq7c4j6a3
<truncated>

最后生成了一个类似的密钥,但现在是 PKCS#8,它有自己的加密,因此不会在 PEM 级别加密。您可以看到 PEM 标头不存在。

$ openssl genpkey -algorithm RSA -des3
.........................................+++++
...........................................................................+++++
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIV0Ih4bsI6egCAggA
MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECNOim8HAN8j5BIIEyEe05hHtc8HL
<truncated>

如果我的所有推理都是正确的,那么提示“输入 PEM 密码”是不准确的,因为这不是 PEM 级别的加密,而是 PKCS#8 级别的加密。

【讨论】:

以上是关于使用 OpenSSL API 读取公钥的密码回调的主要内容,如果未能解决你的问题,请参考以下文章

openssl 怎样生成公钥和密钥 x509格式

Openssl ECDSA:私钥密码

无法从 Java 读取 OpenSSL 生成的 ECDSA 密钥:InvalidKeySpecException

利用openssl进行RSA加密解密

openssl rsa 可以用私钥加密 公钥解密吗

由cryptopp创建的DER编码公钥与openssl不同