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:
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! π