openssl rsautl“无法加载公钥”

时间:2017-12-03 13:02:22

标签: openssl pkcs#1

我生成了PKCS#1 RSAPublicKey格式的RSA公钥。我想使用此密钥使用openssl rsautl加密某些数据,如下所示:

$ openssl genrsa -out private_key.pem 512
Generating RSA private key, 512 bit long modulus
................++++++++++++
...++++++++++++
e is 65537 (0x10001)
$ openssl rsa -in private_key.pem -RSAPublicKey_out -out public_key.pem
writing RSA key
$ echo "this is the cleartext" | openssl rsautl -encrypt -out encrypted_with_pub_key -pubin -inkey public_key.pem
unable to load Public Key

这里发生了什么?为什么openssl无法读取它自己生成的密钥?它可以读取什么格式?更一般地说,为什么这是so poorly documented

1 个答案:

答案 0 :(得分:1)

TL; DR: OpenSSL支持几种不推荐使用的旧格式。当输入是RSA私钥对时,openssl rsa -pubout似乎有一种特殊情况。最后,我给出了两个命令序列,让OP的加密(和相应的解密)成功;一个通过使用DER而不是PEM,另一个通过首先发射DER,然后将其转换为PEM ......奇怪的是,使用openssl rsa,当输入是DER编码的公钥时输出现代格式。 。 去搞清楚。无论如何,下面是对OP初始尝试失败原因的详细描述。至于“为什么这么糟糕的文件”......欢迎来到OpenSSL。快速学习源目录结构,因为它将加快您的grepping。 :)

长解释(详情摘自OpenSSL 1.0.2m)

OpenSSL rsautl应用程序将-inform参数(传入密钥表示)默认为format = FORMAT_PEM

然后使用以下格式为传入密钥选择阅读器:

else if (format == FORMAT_PEMRSA) {
    RSA *rsa;
    rsa = PEM_read_bio_RSAPublicKey(key, NULL,
                                    (pem_password_cb *)password_callback,
                                    &cb_data);
    /* ... */
}
else if (format == FORMAT_PEM) {
    pkey = PEM_read_bio_PUBKEY(key, NULL,
                               (pem_password_cb *)password_callback,
                               &cb_data);

因此,我们将使用PEM_read_bio_PUBKEY。请注意FORMAT_PEMRSA上方的条目......我们将同时开发导致PEM_read_bio_RSAPublicKey的路径。这两个读者都是通过一些宏来定义的

crypto/pem/pem_all.c:427:IMPLEMENT_PEM_rw(PUBKEY, EVP_PKEY, PEM_STRING_PUBLIC, PUBKEY)
crypto/pem/pem_all.c:241:IMPLEMENT_PEM_rw_const(RSAPublicKey, RSA, PEM_STRING_RSA_PUBLIC, RSAPublicKey)

哪个扩展为

# define IMPLEMENT_PEM_rw(name, type, str, asn1) \
    IMPLEMENT_PEM_read(name, type, str, asn1) \
    IMPLEMENT_PEM_write(name, type, str, asn1)

# define IMPLEMENT_PEM_rw_const(name, type, str, asn1) \
    IMPLEMENT_PEM_read(name, type, str, asn1) \
    IMPLEMENT_PEM_write_const(name, type, str, asn1)    

反过来使用

# define IMPLEMENT_PEM_read(name, type, str, asn1) \
    IMPLEMENT_PEM_read_bio(name, type, str, asn1) \
    IMPLEMENT_PEM_read_fp(name, type, str, asn1)

将实际定义构建为:

# define IMPLEMENT_PEM_read_bio(name, type, str, asn1) \
type *PEM_read_bio_##name(BIO *bp, type **x, pem_password_cb *cb, void *u)\
{ \
  return PEM_ASN1_read_bio((d2i_of_void *)d2i_##asn1, str,bp,(void **)x,cb,u); \
}

特别值得注意的是第3个参数strPEM_STRING_PUBLIC版本的PEM_read_bio_PUBKEYPEM_STRING_RSA_PUBLIC版本的PEM_read_bio_RsaPublicKey。这些字符串分别是

./crypto/pem/pem.h:122:# define PEM_STRING_PUBLIC       "PUBLIC KEY"
./crypto/pem/pem.h:124:# define PEM_STRING_RSA_PUBLIC   "RSA PUBLIC KEY"

查看PEM_ASN1_read_bio的实施情况,我们看到它调用PEM_bytes_read_bio

void *PEM_ASN1_read_bio(d2i_of_void *d2i, const char *name, BIO *bp, void **x,
                        pem_password_cb *cb, void *u)
{
    const unsigned char *p = NULL;
    unsigned char *data = NULL;
    long len;
    char *ret = NULL;

    if (!PEM_bytes_read_bio(&data, &len, NULL, name, bp, cb, u))
        return NULL;
    /* ... */

PEM_bytes_read_bio读取密钥文件,将其分成几部分。 nm获取-----部分之间的标记,例如-----BEGIN RSA PUBLIC KEY-----,然后由check_pem进行检查。

int PEM_bytes_read_bio(unsigned char **pdata, long *plen, char **pnm,
                       const char *name, BIO *bp, pem_password_cb *cb,
                       void *u)
{
    EVP_CIPHER_INFO cipher;
    char *nm = NULL, *header = NULL;
    unsigned char *data = NULL;
    long len;
    int ret = 0;

    for (;;) {
        if (!PEM_read_bio(bp, &nm, &header, &data, &len)) {
            if (ERR_GET_REASON(ERR_peek_error()) == PEM_R_NO_START_LINE)
                ERR_add_error_data(2, "Expecting: ", name);
            return 0;
        }
        if (check_pem(nm, name))

并且check_pem检查如下,其中nm是在文件中找到的字符串,name是从PEM_read_bio_XXX函数中硬编码的位置传递的

static int check_pem(const char *nm, const char *name)
{
    /* Normal matching nm and name */
    if (!strcmp(nm, name))
        return 1;           

    /* special cases for
      PKCS8 format (BEGIN PRIVATE KEY or BEGIN ENCRYPTED PRIVATE KEY)
      Various things ending in PARAMETERS
      Various X509 related files
      PKCS7 format (BEGIN PKCS7 or BEGIN PKCS7 SIGNED DATA)
      CMS things (BEGIN CMS) */

    /* ... */

strcmpname之间只有一个nm。因此,要成功阅读PEM_read_bio_PUBKEY的RSA公钥,需要以-----BEGIN PUBLIC KEY-----开头......但是查看OP指令生成的密钥后,我们会找到-----BEGIN RSA PUBLIC KEY-----。但这是与PEM_read_bio_RsaPublicKey对应的字符串...也许我们可以使用-inform来选择format = FORMAT_PEMRSA,并让rsautl以这种方式读取我们的公钥。 rsautl使用名为str2fmt的函数来解析-inform参数。我们来看看:

int str2fmt(char *s)
{
    if (s == NULL)
        return FORMAT_UNDEF;
    if ((*s == 'D') || (*s == 'd'))
        return (FORMAT_ASN1);
    else if ((*s == 'T') || (*s == 't'))
        return (FORMAT_TEXT);
    else if ((*s == 'N') || (*s == 'n'))
        return (FORMAT_NETSCAPE);
    else if ((*s == 'S') || (*s == 's'))
        return (FORMAT_SMIME);
    else if ((*s == 'M') || (*s == 'm'))
        return (FORMAT_MSBLOB);
    else if ((*s == '1')
             || (strcmp(s, "PKCS12") == 0) || (strcmp(s, "pkcs12") == 0)
             || (strcmp(s, "P12") == 0) || (strcmp(s, "p12") == 0))
        return (FORMAT_PKCS12);
    else if ((*s == 'E') || (*s == 'e'))
        return (FORMAT_ENGINE);
    else if ((*s == 'H') || (*s == 'h'))
        return FORMAT_HTTP;
    else if ((*s == 'P') || (*s == 'p')) {
        if (s[1] == 'V' || s[1] == 'v')
            return FORMAT_PVK;
        else
            return (FORMAT_PEM);
    } else
        return (FORMAT_UNDEF);
}

不。没有办法让它返回FORMAT_PEMRSA。

那么,如果我们编辑公钥文件以使标记改为-----BEGIN PUBLIC KEY-----呢?

,该怎么办?
$ sed -e "s/RSA PUBLIC/PUBLIC/" public_key.pem > public_key_mod.pem             
$ echo "this is the cleartext" | openssl rsautl -encrypt -out encrypted_with_pub_key -pubin -inkey public_key_mod.pem
unable to load Public Key

不。

如果我们将公钥提取到DER而不是PEM怎么办?

$ openssl rsa -in private_key.pem -out public_key.der -outform DER -pubout
writing RSA key
$ echo "this is the cleartext" | openssl rsautl -encrypt -out encrypted_with_pub_key -pubin -inkey public_key.der -keyform DER

$ openssl rsautl  -decrypt -in encrypted_with_pub_key -inkey private_key.pem
this is the cleartext

成功!也许我们可以让OpenSSL将DER键转换为与rsautl

兼容的形式
$ openssl rsa -in public_key.der -inform DER -pubin -out test.pem
writing RSA key
$ cat test.pem
-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM3uGdU6YtwI5S8K+GgddW8KhrzmSFVI
6cvBT+XqOuSVo+n8VyUfADHw4rPxjy/dWDpyOxzWdTg8VZ77Vs06af8CAwEAAQ==
-----END PUBLIC KEY-----
$ echo "this is the cleartext" | openssl rsautl -encrypt -out encrypted_with_pub_key -pubin -inkey test.pem

$ openssl rsautl  -decrypt -in encrypted_with_pub_key -inkey private_key.pem
this is the cleartext

所以是的......看起来openssl rsa仅在BEGIN RSA PUBLIC KEY作为输入时才会写出过时的BEGIN RSA PRIVATE KEY表单。