Cryptography Encoding public keys in PEM format

Posted on

One of the most common uses of PEM files is storing or exchanging public keys. But there is more than one way to store a public key in a PEM file.

PKCS#1 RSAPublicKey

The oldest and arguably most simple PEM-based file format for public keys is RSAPublicKey. We can recognize RSAPublicKeyfiles by their BEGIN RSA PUBLIC KEY header:


We can create a public key in RSAPublicKey format with OpenSSL like so:

openssl genrsa -out private-key.pem 3072
openssl rsa -in private-key.pem -RSAPublicKey_out -out public-key.pem

Notice the -RSAPublicKey_out parameter, which explicitly instructs OpenSSL to use the RSAPublicKey format.

When we open the resulting public key file in an ASN.1 Editor, the format looks pretty straightforward – it’s just a sequence of two integers:

RSA public key

As we saw last time, ASN.1 doesn’t encode any metadata such as field names. To make sense of the data structure, we need to take a look at RFC 3447: Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography Specifications Version 2.1: In appendix A, the RFC describes how RSA keys should be represented in ASN.1, and defines the following structure:

RSAPublicKey ::= SEQUENCE {
    modulus           INTEGER,  -- n
    publicExponent    INTEGER   -- e

So the two integers that we see in the screenshot are the modulus, followed by the exponent.

While RSA is still popular, it obviously isn’t the only asymmetric encryption algorithm anymore. What if we want to use another algorithm like elliptic curve cryptography and store a public key for it? Clearly, we need a more flexible file format than one that only supports RSA. That leads us to…

X.509 SubjectPublicKeyInfo

Today, the public key format we see most commonly looks like this:

-----END PUBLIC KEY-----

This is the SubjectPublicKeyInfo format, and it’s the format OpenSSL uses by default when generating a public key:

openssl genrsa -out private-key.pem 3072
openssl rsa -in private-key.pem -pubout -out public-key.pem

When we open this SubjectPublicKeyInfo-formatted RSA public key file in an ASN.1 Editor, it looks a bit more complex:

RSA public key

To make sense of this ASN.1 structure, we again have to refer to an RFC – this time RFC5280, which describes the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile. The RFC contains the following definition:

SubjectPublicKeyInfo  ::=  SEQUENCE  {
     algorithm            AlgorithmIdentifier,
     subjectPublicKey     BIT STRING  }

What we have here is essentially a discriminated union: The first field, algorithm, contains an OID that idenfifies the format of the key in the subjectPublicKey field.

For example, if algorithm contains 1.2.840.113549.1.1.1, then subjectPublicKey contains a RSAPublicKey structure, which is the one we’ve seen previously. Indeed, if you compare the two screenshots, you notice that the second one looks like the first, just with a header prepended.

Thanks to its more flexible structure, we can use the SubjectPublicKeyInfo format for other asymmetric encryption algorithms such as ECC:

openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem
openssl ec -in private-key.pem -pubout -out public-key.pem

If we open an ECC public key, we can see that the header contains a different OID, and that the subjectPublicKey bit string isn’t interpreted as an RSA public key anymore:

ECC public key

SSH2 public keys

There’s another public key format that we’re frequently dealing with, SSH2 public keys:

Comment: "2048-bit RSA,... "

This looks deceivingly similar to the RSAPublicKey- and SubjectPublicKeyInfo-formatted PEM files we looked at previously – but that’s a red herring: SSH public keys don’t use the PEM format at all, as the RFC astutely points out:

Implementers should take care to notice that while the format is superficially similar to those specified by PEM [RFC1421] and OpenPGP [RFC2440], it is not identical; most notably:
  • The other specifications use different BEGIN/END delimiters (five dashes, no space rather than four dashes and a space).
  • There is no blank line before the start of the base64-encoded contents.
  • There is no Cyclic Redundancy Check (CRC) at the end of the base64-encoded block.
  • Header continuation uses a backslash at the end of the continued line rather than whitespace at the start of the next line.

So what is it then, if not a PEM file? It’s just a slightly enhanced version of the original OpenSSH key format defined in [RFC4253], Section 6.6, which looks like this:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCh3jBV1PZ7i/FAqfx0Vev77BuuXyxO0eocYZ1ZkqZdRqWzzbB5CByO3X9CAGhqAs/d...

The base64-encoded body is the same in both formats, all that the SSH2 format adds is the header, footer, and the ability to add metadata such as comments.

In the next posts, we’ll explore how we can import and export public keys in .NET.

Any opinions expressed on this blog are Johannes' own. Refer to the respective vendor’s product documentation for authoritative information.
« Back to home