PEM_write_bio_RSA_PUBKEY() V.S. PEM_write_bio_RSAPublicKey()

I was really confused why OpenSSL has two similar but different kinds of functions both of which "export" RSA public-key. This article digs into differences of those two similar yet different formats.

First let me cite public document available via "man PEM"
The RSAPublicKey functions process an RSA public key using an RSA structure. The public key is encoded using a PKCS#1 RSAPublicKey structure.
The RSA_PUBKEY functions also process an RSA public key using an RSA structure. However the public key is encoded using a SubjectPublicKeyInfo structure and an error occurs if the public key is not RSA.
I'd say "so what?"

To understand the description, let me compare contents of those keys.

This is a public key using PEM_write_bio_RSAPublicKey():

-----BEGIN RSA PUBLIC KEY-----
MIGHAoGBAMbpMn0hO2UGbA+dQAtz2mRIruD1AsHnkuwRpFu1XL7OEKR5+xIgYVE7
Wb6f/6NXldsJ2jXc+MAo3rT8hgvLS1vz6aMSFCUfiLdpDWwRcuNsMLq2GulqKhiw
HSPSbLBKrwzFc4KDf4Fs66Y+0mm592kJCByoB0q1vdPU6UjGxhOBAgED
-----END RSA PUBLIC KEY-----

I cannot say what this exactly means. Let's decode it. Remove "BEGIN" and "END" line and  base64-decode data between them. We can rely on "base64 -d content.txt > content.der".

# Note that, though I'm using the suffix ".der" here, I don't care if the decoded format is officially der format that is mentioned in PKI topics.

Anyway, we now have raw data for the key. The data is actually in ASN.1 format, so dumpasn1 command line is useful to know more about this key.

> dumpasn1 content.der
   0  135: SEQUENCE {
   3  129:   INTEGER
         :     00 C6 E9 32 7D 21 3B 65 06 6C 0F 9D 40 0B 73 DA
         :     64 48 AE E0 F5 02 C1 E7 92 EC 11 A4 5B B5 5C BE
         :     CE 10 A4 79 FB 12 20 61 51 3B 59 BE 9F FF A3 57
         :     95 DB 09 DA 35 DC F8 C0 28 DE B4 FC 86 0B CB 4B
         :     5B F3 E9 A3 12 14 25 1F 88 B7 69 0D 6C 11 72 E3
         :     6C 30 BA B6 1A E9 6A 2A 18 B0 1D 23 D2 6C B0 4A
         :     AF 0C C5 73 82 83 7F 81 6C EB A6 3E D2 69 B9 F7
         :     69 09 08 1C A8 07 4A B5 BD D3 D4 E9 48 C6 C6 13
         :             [ Another 1 bytes skipped ]
 135    1:   INTEGER 3
         :   }

0 warnings, 0 errors.


It looks the content *is* in ASN.1, while we still don't know what it implies right now.

Next, let's do the similar task with the public key exported by PEM_write_bio_RSA_PUBKEY().

-----BEGIN PUBLIC KEY-----
MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDHb+wxQWOTkghh9yNfM5/ZPNlw
LxOYrIeUjV7Lqg30EMI2KT2dLdKzUqvULM4mRWHhk5Ty/dHFAii14GuclpVwQrwW
YWGKq6yxC715otEzFVH6IglEZscsysVXXB6cS+9KmjUZvdWTKsNn50dAtj6cJ5wg
mCKfTq/pxdidxLvRTQIBAw==
-----END PUBLIC KEY-----

You may have noticed that this time the "BEGIN" and "END" lines are actually a little different; there's no "RSA" in this case.

Let's decode this key. Remove "BEGIN" and "END" lines and store the content to content2.txt, decode it with "base64 -d" and store resultant raw data (in ASN.1) in content2.der.

Then, try dumpasn1 again

> dumpasn1 content2.der
   0  157: SEQUENCE {
   3   13:   SEQUENCE {
   5    9:     OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
  16    0:     NULL
         :     }
  18  139:   BIT STRING, encapsulates {
  22  135:     SEQUENCE {
  25  129:       INTEGER
         :         00 C7 6F EC 31 41 63 93 92 08 61 F7 23 5F 33 9F
         :         D9 3C D9 70 2F 13 98 AC 87 94 8D 5E CB AA 0D F4
         :         10 C2 36 29 3D 9D 2D D2 B3 52 AB D4 2C CE 26 45
         :         61 E1 93 94 F2 FD D1 C5 02 28 B5 E0 6B 9C 96 95
         :         70 42 BC 16 61 61 8A AB AC B1 0B BD 79 A2 D1 33
         :         15 51 FA 22 09 44 66 C7 2C CA C5 57 5C 1E 9C 4B
         :         EF 4A 9A 35 19 BD D5 93 2A C3 67 E7 47 40 B6 3E
         :         9C 27 9C 20 98 22 9F 4E AF E9 C5 D8 9D C4 BB D1
         :                 [ Another 1 bytes skipped ]
 157    1:       INTEGER 3
         :       }
         :     }
         :   }

0 warnings, 0 errors.

Now we *do* see some exact difference of those formats. The latter one actually contains a sort of header in it, while the former just contains exact data.

Unfortunately I'm not an expert on this subject, but as far as I *guess* (oh, you probably understand "guess" doesn't guarantee anything), this header is related to RFC2437 (PKCS #1), well-known format for handling RSA (or X509) public keys.

Simply saying, the first one with PEM_write_bio_RSAPublicKey() is not along with standard, while PEM_write_bio_RSA_PUBKEY() is. Actually, the former one cannot be accepted by other platforms like Java's X509EncodedKeySpec.

If you're interested in Java, you can try having PublicKey object using the second public key. Note that, when I tried, I needed to remove "BEGIN" and "END" lines, so what you need to supply to Java is actually this String:

MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDHb+wxQWOTkghh9yNfM5/ZPNlw
LxOYrIeUjV7Lqg30EMI2KT2dLdKzUqvULM4mRWHhk5Ty/dHFAii14GuclpVwQrwW
YWGKq6yxC715otEzFVH6IglEZscsysVXXB6cS+9KmjUZvdWTKsNn50dAtj6cJ5wg
mCKfTq/pxdidxLvRTQIBAw==

And, here's Java code to decode the String into PublicKey object, ... and have Cipher object with the public key (to encrypt other data)

        byte[] der = Base64.decode(pem);
        if (der == null) {
            return;
        }

        PublicKey publicKey = null;
        Cipher cipher = null;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(der);
            publicKey = keyFactory.generatePublic(keySpec);
            cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, mPublicKey);
        } catch (GeneralSecurityException e) {
            return;
        }

Here's another case with Ruby.
http://stackoverflow.com/questions/4635837/invalid-public-keys-when-using-the-ruby-openssl-library

One more interesting fact: "openssl rsa" command cannot understand the former, non-standard one, while OpenSSL as a C library does have a capability to export it:


> openssl rsa -pubin -inform der -in content2.der -text
Public-Key: (1024 bit)
Modulus:
    00:c7:6f:ec:31:41:63:93:92:08:61:f7:23:5f:33:
    9f:d9:3c:d9:70:2f:13:98:ac:87:94:8d:5e:cb:aa:
    0d:f4:10:c2:36:29:3d:9d:2d:d2:b3:52:ab:d4:2c:
    ce:26:45:61:e1:93:94:f2:fd:d1:c5:02:28:b5:e0:
    6b:9c:96:95:70:42:bc:16:61:61:8a:ab:ac:b1:0b:
    bd:79:a2:d1:33:15:51:fa:22:09:44:66:c7:2c:ca:
    c5:57:5c:1e:9c:4b:ef:4a:9a:35:19:bd:d5:93:2a:
    c3:67:e7:47:40:b6:3e:9c:27:9c:20:98:22:9f:4e:
    af:e9:c5:d8:9d:c4:bb:d1:4d
Exponent: 3 (0x3)
writing RSA key
-----BEGIN PUBLIC KEY-----
MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDHb+wxQWOTkghh9yNfM5/ZPNlw
LxOYrIeUjV7Lqg30EMI2KT2dLdKzUqvULM4mRWHhk5Ty/dHFAii14GuclpVwQrwW
YWGKq6yxC715otEzFVH6IglEZscsysVXXB6cS+9KmjUZvdWTKsNn50dAtj6cJ5wg
mCKfTq/pxdidxLvRTQIBAw==
-----END PUBLIC KEY-----
> openssl rsa -pubin -inform der -in content.der -text
unable to load Public Key
140246695663264:error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag:tasn_dec.c:1319:
140246695663264:error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 error:tasn_dec.c:381:Type=X509_ALGOR
140246695663264:error:0D08303A:asn1 encoding routines:ASN1_TEMPLATE_NOEXP_D2I:nested asn1 error:tasn_dec.c:751:Field=algor, Type=X509_PUBKEY


Then, what is a conclusion?
  • PEM_write_bio_RSAPublicKey() should not be used if you consider using the key outside OpenSSL functions, including "openssl" CLI
  • PEM_write_bio_RSA_PUBKEY() is more useful for cases above.
    • I'm not sure enough it is the best for all of cases we may encounter in the real world.
Finally, you may find this project useful. It contains rsa_exp.cpp, which was used when I wrote this article at first.

https://github.com/dmiyakawa/openssl_exp

このブログの人気の投稿

WiiUのコントローラが通信不良に陥った話

LibreOfficeで表紙、目次、本体でフッターのページ番号のスタイルを変える

技術書典2 あ-03 『もわねっとのPythonの本』