Create a public key from a modulus and the exponent

If you are looking for a way to create a public key (PEM or SSH format), starting from the modulus and the exponent and without any piece of code, then you reached the right place!

Decoding operands

First, I will fetch the two operands, the modulus and the exponent, from a JWKS (Json Web Key Set). Here is a partial representation:

{
    "keys": [
        {
            "kty": "RSA",
            "kid": "K14p_n3IB1eTrez6cRjUWB0GOWc",
            "use": "sig",
            "n": "3bmGZLQSl3-wvGZVrZeI-HGz0XjB6F1lcwyxGRNl4GN3c7qHJyK5EiTqBNHKCQ76njmbERvIph8NlrH4G5l8tWUbB5z3vQYBkegc-fK0q7-QAZMJ9GglxjbppeIHpvYlk5G04CNedzyxA4SG2KNPdELgXYZDOVKtmd2jSSVys1P81H7olm1TS_3jTZP9PScY0t0fOibzzOFK7pdpfz6yjMt2FMLwMtLYNcZ2QaBRqFntNZ5biDiSsk60M5f_DwwAAKnDMZ5pT0qDeFMo8JQfhGMhr8a46oNXXbRmLXGUEytbiesQPVaTMpDkWqyxq_pOFhn2Er99i698YS6LNuZpeQ",
            "e": "AQAB"
        }
    ]
}

Where the n field stands for the modulus and the e field is the exponent.

Also, from that single JWK, one can observe that the type is RSA and that its purpose is to sign payloads. Refer to the RFC if any doubt πŸ˜„

If you were not sure, yes, based on these two attributes, one can compute the public key.

Given:

export n="3bmGZLQSl3-wvGZVrZeI-HGz0XjB6F1lcwyxGRNl4GN3c7qHJyK5EiTqBNHKCQ76njmbERvIph8NlrH4G5l8tWUbB5z3vQYBkegc-fK0q7-QAZMJ9GglxjbppeIHpvYlk5G04CNedzyxA4SG2KNPdELgXYZDOVKtmd2jSSVys1P81H7olm1TS_3jTZP9PScY0t0fOibzzOFK7pdpfz6yjMt2FMLwMtLYNcZ2QaBRqFntNZ5biDiSsk60M5f_DwwAAKnDMZ5pT0qDeFMo8JQfhGMhr8a46oNXXbRmLXGUEytbiesQPVaTMpDkWqyxq_pOFhn2Er99i698YS6LNuZpeQ"
export e="AQAB"

Base64 URL-decode the modulus. As there are no GNU tool to do that out-of-the-mac-OS-box, I suggest using that short bash script:

#!/usr/bin/env bash
# Encode to / decode from Base64-URL without padding.

# USAGE:
# bash base64url.sh encode 'Hello!'
# bash base64url.sh decode SGVsbG8h

function encode {
  echo -n "$1" | openssl enc -a -A | tr -d '=' | tr '/+' '_-'
}

function decode {
  _l=$((${#1} % 4))
  if [ $_l -eq 2 ]; then _s="$1"'=='
  elif [ $_l -eq 3 ]; then _s="$1"'='
  else _s="$1" ; fi
  echo "$_s" | tr '_-' '/+' | openssl enc -d -a -A
}

case $1 in
  encode) encode "$2" ;;
  decode) decode $2 ;;
  e) encode "$2" ;;
  d) decode $2 ;;
esac

Then call it like this:

# chmod u+x decoder.sh
./decoder.sh decode $n > modulus.bin

Now, convert it to hex code:

xxd -ps -c 256 modulus.bin

Repeat the same operations for the exponent:

./decoder.sh decode $e > exponent.bin
xxd -ps -c 256 exponent.bin

Now it’s time to build a PEM file.

Building the PEM

Here, we will use openssl commands and a ASN.1 definition file with our computed values inserted in.

# Start with a SEQUENCE
asn1=SEQUENCE:pubkeyinfo

# pubkeyinfo contains an algorithm identifier and the public key wrapped
# in a BIT STRING
[pubkeyinfo]
algorithm=SEQUENCE:rsa_alg
pubkey=BITWRAP,SEQUENCE:rsapubkey

# algorithm ID for RSA is just an OID and a NULL
[rsa_alg]
algorithm=OID:rsaEncryption
parameter=NULL

# Actual public key: modulus and exponent
[rsapubkey] (1)
n=INTEGER:0xddb98664b412977fb0bc6655ad9788f871b3d178c1e85d65730cb1191365e0637773ba872722b91224ea04d1ca090efa9e399b111bc8a61f0d96b1f81b997cb5651b079cf7bd060191e81cf9f2b4abbf90019309f46825c636e9a5e207a6f6259391b4e0235e773cb1038486d8a34f7442e05d86433952ad99dda3492572b353fcd47ee8966d534bfde34d93fd3d2718d2dd1f3a26f3cce14aee97697f3eb28ccb7614c2f032d2d835c67641a051a859ed359e5b883892b24eb43397ff0f0c0000a9c3319e694f4a83785328f0941f846321afc6b8ea83575db4662d7194132b5b89eb103d56933290e45aacb1abfa4e1619f612bf7d8baf7c612e8b36e669

e=INTEGER:0x010001 (2)
1 place the modulus hex code right after n=INTEGER:0x
2 same for the exponent hex code

Transform it into a DER file:

openssl asn1parse -genconf def.asn1 -out pubkey.der -noout

Then, convert it to a PEM file:

openssl rsa -in pubkey.der -inform der -pubin -out pubkey.pem

Finally, you can check that you got a 2048 bits public key with this command:

openssl rsa -pubin -in pubkey.pem -text -noout

Expected output:

output

Building the SSH public key

Also, you might need to convert that PEM file to the SSH public key format:

ssh-keygen -i -m PKCS8 -f pubkey.pem

Here it is:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAADduZlktBKXf7C8ZlWtl4j4cbPReMHoXWVzDLEZE2XgY3dzuocnIrkSJOoE0coJDvqeOZsRG8imHw2WsfgbmXy1ZRsHnPe9BgGR6Bz58rSrf5ABkwn0aCXGNuml4gem9iWTkbTgI153PLEDhIbYo090QuBdhkM5Uq2Z3aNJJXKzU/zUfuiWbVNL/eNNk/09JxjS3R86JvPM4Urul2l/PrgMy3YUwvAy0tg1xnZBoFGoWe01nluIOJKyTrQzl/8PDAAAqcMxnmlPSoN4UyjwlB+EYyGvxrjqg1ddtGYtcZQTK1uJ64A9VpMykORarLGr/E4WGfYSv32Lr3xhLos25mk=

Using a higher level language

If I were to use Java for building a PEM from a JWKS, I would use one of the libs listed under the https://jwt.io homepage.

For example, with the "com.auth0 / java-jwt" lib:

DecodedJWT decodedJWT = JWT.decode(id_token);
JwkProvider provider =  new JwkProviderBuilder(new URL("https://<domain>/path/to/JWKS")).build();
Jwk jwk = provider.get(decodedJWT.getKeyId());
RSAPublicKey rsaPublicKey = (RSAPublicKey) jwk.getPublicKey();
System.out.println("PEM content: " + Base64.getEncoder().encodeToString(rsaPublicKey.getEncoded()));

This is certainly more convenient than fetching ourselves the operands! πŸ˜†


Next: OpenLDAP Helm chart for devs
Previous: How Kubelet actually runs containers
comments powered by Disqus