how to convert raw modulus & exponent to RSA public key (.pem format)

Erik Nyquist picture Erik Nyquist · Dec 19, 2014 · Viewed 18.1k times · Source

I have the modulus & exponent of an RSA public key embedded into a binary file, and I am trying to extract the entire blob and create a usable .pem public key.

Currently, I am extracting the full 260 bytes (4 bytes for the exponent, 256 bytes for the modulus) and encoding as base64. I am doing that using the following shell command :

tail -c $((filesize - start_of_key_data)) filename | head -c $size_of_key_data | base64 > outkey

This gives me the following string :

<<<<<< modulus & exponent extracted from binary file, base64-encoded >>>>>>

tZyrQA6cZFJfVm6FyXwtZaLQYg8EecuO+ObrHTwc8JO+XrgnpNAdmlhbAEPxSNnjwhNnbYGYGL4F
vzmnZXzZU71Key42HQPh1k2Zx1UDbrH5ciODKx1ZbuEx8K24SHnL1nY/H75hwhT/ZRRVGQDvYDT+
sgzw2vmV66+dflw1Zs8BLhqjLjczdHvjeVXsDRJ9Mvvd/dhFH8UlTf4JpLGya9nsNIfNBBIf1Lll
RWwCTiEIbaOMgWcLjLV/2tk/j5Dra/oQnVf/2hVsEF/hXEx41YjeEW/warweoDVG7zaxrHEc/k/r
ZCUCZKxf8nBKdqax/gRICvkG6e5xg2GQw0W/ZwABAAE=

Now, when I take the key.pem keypair that the modulus & exponent were originally extracted from, and display the public portion like so

openssl rsa -in key.pem -pubout -out pubkey.pem

I get this string (I have omitted the header & footer lines :

<<<<<<<<< valid public key data extracted from keypair >>>>>>>>>

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtZyrQA6cZFJfVm6FyXwt
ZaLQYg8EecuO+ObrHTwc8JO+XrgnpNAdmlhbAEPxSNnjwhNnbYGYGL4FvzmnZXzZ
U71Key42HQPh1k2Zx1UDbrH5ciODKx1ZbuEx8K24SHnL1nY/H75hwhT/ZRRVGQDv
YDT+sgzw2vmV66+dflw1Zs8BLhqjLjczdHvjeVXsDRJ9Mvvd/dhFH8UlTf4JpLGy
a9nsNIfNBBIf1LllRWwCTiEIbaOMgWcLjLV/2tk/j5Dra/oQnVf/2hVsEF/hXEx4
1YjeEW/warweoDVG7zaxrHEc/k/rZCUCZKxf8nBKdqax/gRICvkG6e5xg2GQw0W/
ZwIDAQAB

You can see that the key data which I have extracted and base64-encoded myself is actually present in the data of the valid public key data extracted from the key.pem using openssl. However there are 45 characters at the beginning, that my own extracted data does not have -

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA

and the last 8 characters also differ.

ZwIDAQAB

Can anybody offer some advice on how to convert a modulus and exponent into a usable public key?

(the goal is to do this in a bash script, not python or C as I've seen many suggest.)

Answer

Konstantin Shemyak picture Konstantin Shemyak · Dec 19, 2014

Command which you used, openssl rsa -in key.pem -pubout -out pubkey.pem, produces the ASN.1 structure like this:

SEQUENCE(2 elem)
  SEQUENCE(2 elem)
    OBJECT IDENTIFIER 1.2.840.113549.1.1.1
    NULL
  BIT STRING(1 elem)
    SEQUENCE(2 elem)
      INTEGER(2048 bit) 229263895356027367204242482830890190076375310244080661230946245232688…
      INTEGER 65537

(You can see the structure with openssl asn1parse -in pubkey.pem, or using an online ASN.1 decoder).

It contents:

  1. a fixed header (contains all bytes, specifying the encoding of the whole sequence plus the encoding of the modulus)
  2. modulus
  3. header, specifying encoding of the exponent
  4. exponent

If you have the modulus and exponent bytes correctly collected, you can construct the the public key in form, understandable by OpenSSL, by concatenating these four things. You already have the first longer header. The "middle header" is '02 03':

  1. '02' for the integer
  2. length of the integer itself is 3 bytes (65537 = 01 00 01)

If your modulus is 2048 bits (256 bytes) and exponent 3 bytes (so that the length fields remain valid), the PEM file can be produced by concatenating these four:

<header> <modulus> 0x02 0x03 <exponent>

That is why the last bytes from the binary dump differ from the OpenSSL output: the extracted 260 bytes do not contain 02 03, but instead record 65537 as 00 01 00 01 (not 01 00 01 as in ASN.1 encoding).

To summarize, you can produce the PEM file like this:

Convert your extracted modulus+exponent back from base64 and extract them (note the 257 byte offset to skip the leading zero byte of 65537!):

echo 'tZyrQA6cZFJfVm6FyXwtZaLQYg8EecuO+ObrHTwc8JO+XrgnpNAdmlhbAEPxSNnjwhNnbYGYGL4FvzmnZXzZU71Key42HQPh1k2Zx1UDbrH5ciODKx1ZbuEx8K24SHnL1nY/H75hwhT/ZRRVGQDvYDT+sgzw2vmV66+dflw1Zs8BLhqjLjczdHvjeVXsDRJ9Mvvd/dhFH8UlTf4JpLGya9nsNIfNBBIf1LllRWwCTiEIbaOMgWcLjLV/2tk/j5Dra/oQnVf/2hVsEF/hXEx41YjeEW/warweoDVG7zaxrHEc/k/rZCUCZKxf8nBKdqax/gRICvkG6e5xg2GQw0W/ZwABAAE=' | base64 -d > modulus-exp.bin
dd if=modulus-exp.bin of=modulus.bin bs=1 count=256
dd if=modulus-exp.bin of=exponent.bin bs=1 skip=257 count=3

Create the headers:

echo 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA' | base64 -d > header.bin
echo '02 03' | xxd -r -p > mid-header.bin

Concatenate them together:

cat header.bin modulus.bin mid-header.bin exponent.bin > key.der

Convert to PEM:

openssl pkey -inform der -outform pem -pubin -in key.der -out key.pem

Test that you get the working key - by checking it with ASN.1 decoder, or by

openssl asn1parse -in key.pem
openssl asn1parse -in key.pem -strparse 19