How to generate a JWT signing keys

Gus Thompson
6 min readJan 19, 2022

JWT tokens provide a secure way of knowing who shared the information in a token.

Plenty of people have written about what JWT tokens are and what they contain. I wanted to provide easy practical instructions on JWT token generation. This article is part of a series, including:

  • How to generate a JWT signing keys (this story)
  • Generating a JWT token in native NodeJS
  • Decoding and Validating a JWT token in native NodeJS

TL; DR;

Summary of commands for JWT signing keys.

# Generate key
ssh-keygen -t rsa -b 4096 -m PEM -f ./myRSA256.key
# Generate public key
openssl rsa -in myRSA256.key -pubout -outform PEM -out myRSA256.key.pub

If you need to test these keys using an unencrypted private key on a site like jwt.io, then you can convert your private key using the following command.

# For testing on sites like jwt.io
openssl pkcs8 -topk8 -inform PEM outform PEM -in myRSA256.key -out myRSA256.pem.key -nocrypt

Step 1 — Generate a RSA signing keys for JWT

Command

ssh-keygen -t rsa -b 4096 -m PEM -f ./myRSA256.key

A brief summary of the above command:

ssh-keygen : This is a tool for generating secure keys as part of OpenSSH key management.

-t rsa : Specifies the type of key to create. A number of options exist, I have found in my travels rsa provides the greatest compatibility. Some systems may require you to use a specific key type.¹

-b 4096 : Specifies the number of bits in the key to create. Generally 3072 is considered sufficient for rsa keys, but the cost of a more complex key is negligible for most tasks, so I use 4096

-m PEM : Specifies the key format for the key generation. We use the PEM option which tell ssh-keygen to generate a (PEM public key). The format is set to PEM when generating private key.¹

-f ./myRSA265.key : Simply tell the command the file to output my key. Removing this options generally tries to save this into the machines standard location ~/.ssh/id_rsa which is not the outcome we want.¹

Output ( powershell windows & mac OS ):

When the command runs you will be prompted to enter your passphrase (twice). Remember this we will use it in the next step and it secures your private key from unauthorized used.

Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ./myRSA256.key.
Your public key has been saved in ./myRSA256.key.pub.
The key fingerprint is:
SHA256:HbEBeGu6CZj852WHvBxnPxdvZC5sxH0iKtpRBcwnCLo yourusername@YOURMACHINE
The key's randomart image is:
+---[RSA 4096]----+
| .o.=+ |
| .. o +=. |
| . . .oo. |
| . o. o |
| . oE oS o . . |
| + . .. o . = =|
| . . oB +. + B.|
| . +=.B... = +|
| oo.+. .+ o |
+----[SHA256]-----+

My file myRSA256.key generated if you use the passphrase mykey is.

NOTE : your file will not be same because of the random nature of the cypher.

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,2837001C3413E66E2E92A086EE67BCC4
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
-----END RSA PRIVATE KEY-----

My file myRSA256.key.pub generated if you use the passphrase mykey is. The format of this file is not in the PEM format like the private key above.

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC7DXLOSZtjloUKhf6TVl/RNyccAFJ1zoCM1Qb57OjNYL+NHo8rzPBYpZ6cdkadT/Pkkg9KJRE77/w/rAXm1q1au14ktIvyFdqf52jmALHbrCIgE2sjdXW1zjfXm++nnLHeFmiSsThS5759OIpruE5Vp43xxc/Y29UrBxCcFK445gGwzcGqyxZpqgEnhwD1BM8gmvJ4JkZEFQqDrua0qjVgyFPcs46LrBhYeq7abcq2ALWEKxv4y5D37R7nEfes7rAzMXJd/6xQb4yaLPjhBnv6JsLektY2k6KM5yk84OlWxY5AQRQdisimITx1sDi5qgRc1RIh22sDcMbtYSJ+CNWa2W65je0mKBKJRqqDEMfsZf3LPXBUSl0yWkGSPHTZC3PZCLbiip5ktyx+DtUXhgQHn6gSxrntQ9di7BuGjUoJTbcFfVKtp3CWIk0IJsf/g2SgYy2HALFzC3Oipw4mt9mRE6SVhbljfV5DeuXdZLowGrgGN+MYp5c/j3ID5e44VLMc6gaV31E2FkHWY56fxIH4m+YzV6EXs8yP0lKYH6sbw64K4Vsw2KDk27yP4a4wLvwSLpOx8WPCn7m8Z/u6/lSBq5+RyAt83HTi3iaRdB15INi9loGEvy64sbxQLp8h7ZJAb3ZSqwUqrznWmMyHtsrucelqxrE9+ewTh0XQGjKGZw== yourusername@YOURMACHINE

Step 2 — Generate public key in JWT format

openssl rsa -in myRSA256.key -pubout -outform PEM -out myRSA256.key.pub# ORopenssl rsa -in myRSA256.key -passin pass:<my passphrase> -pubout -outform PEM -out myRSA256.key.pub

openssl : OpenSSL is a cryptography toolkit implementing the Secure Sockets Layer (SSL v2/v3) and Transport Layer Security (TLS v1) network protocols and related cryptography standards required by them.²

rsa : openssl toolkit works with many key types and crytography. rsa tells the command the following operations are rsa based.³

-in myRSA256.key : Specifies the source key from a file, it can also be sourced from standard input, if chained with other commands.³

-pubout : By default the command will output a private key, with this option a public key is output instead.

-out myRSA256.key.pub :

optional -outform PEM : This is the default option for the command. The PEM format is the familiar format displayed as

 ----- BEGIN object-type -----
81GivoQ9F1U0Qr+DH3ZfaH8eIkXxT0ToMPJUzWAn8pZv0snA0um6SIgi
UM6j0ZuSMFOCr/lGPAoOQU0fskidGEHi1/kW+suSr28TqsyYZpwBDQ==
----- END object-type -----

Output ( powershell windows & mac OS ):

When the command is run you will be asked to enter the passphrase again, this unlocks the private key generated with the passphrase using the ssh-keygen command above.

Enter pass phrase for myRSA256.key:
writing RSA key

The file myRSA256.key.pub generated if you use the passphrase mykey is :

NOTE : your file will not be same because of the random nature of the cypher.

-----BEGIN PUBLIC KEY-----MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzHrqjxw8dBSQw8ysBtzX
XBXHUN+7BFynMd5IJnH2RvKrCis/n38IdvteAFn78+GmlxuzJnWsYsHVeLfx8Mi7
QW8COybQPTMqwluD9IUJ7nvREWEjhYNF1PU/NZgQxmkpW5l6dbVjuW41qzEI5a7a
b59t7oV+AwZ57vlOiLRcUFSZ8EYKJwkxMJJdvVg0bfhFDC7CRJ9unjfJ2rbEsDHU
hMXwjCc3zFsSw8s3w3zDMvoUfjsMVdzAvKxX/USrFFN1KLxmWIn4sR9ruz1BiYMu
roosHPCTDV+c5eRF7Gq27XqZ5R8dfpC/mqvA0E8iGKEHaPzA1VzgXiGIf+DibGVm
JLZ3YNEHE+UI7BKAizokz+s0koivGOrt2IyoK6mtwt4sUEExO/s6pyrb48ResyLz
I/VI0XAXp6cekJ/1Zpss3TvZ9+e3p0RN2rB30MAd7vD/hlbn/0LbIHXrD5f3838O
uT9vgwxCepX25MHV6vCNnVUoCmTKGUzi4WH/0BB4aTt0xNYdq2A2Svsgq1aP1VR6
IU4sqPWHb2S7j3ktFClT+us67K8tPbeDS4lS+QKoNV/SberBeygYgxRA400KYO07
CpuRCf3WJvzgiNmkkO6Rw2AwH8MuVT7OP5QsU/ZHzBIsee+A+kcjghYMSILbUo12
7ojSvCiT7kCbdPKChABrytECAwEAAQ==
-----END PUBLIC KEY-----

Optional : Step 3 —Test Keys

An important step is to make sure your keys work the way you expect them to work. Using the above process you can generate a test key (do not use your real keys in testing) on third party sites.

For this test I am going to run the following commands:

# Generate 2 private RSA keys 
ssh-keygen -t rsa -b 4096 -m PEM -f ./testKey1.key
ssh-keygen -t rsa -b 4096 -m PEM -f ./testKey2.key
# Generate public keys
openssl rsa -in testKey1.key -pubout -outform PEM -out testKey1.key.pub
openssl rsa -in testKey2.key -pubout -outform PEM -out testKey2.key.pub
# Convert Private keys for testing
openssl pkcs8 -topk8 -inform PEM -outform PEM -in testKey1.key -out testKey1.pem.key -nocrypt
openssl pkcs8 -topk8 -inform PEM -outform PEM -in testKey2.key -out testKey2.pem.key -nocrypt

The last command openssl pkcs8 convert the RSA private key with passphrase into a key with no passphrase in a PEM format. This is required for the tools we will test the keys with.

The reason we generate two keys is to show through testing that the keys cannot be interchanged.

I am going to test the keys using two separate external sites:

token.dev :

  • select the algorithm RS256,
  • delete the contents of the Public Key field,
  • copy the testKey1.pem.key file contents into the Private Key field
  • you will now have a JWT String for your signed token, copy this token.
Testing JWT token using token.dev

jwt.io :

  • paste the token you generated on token.dev into Encoded, the algorithm is read from the token header and automagically set to RSA256 The site will say Invalid Signature because we have not loaded the public key.
Testing generated JWT token in jwt.io note the Invalid Signature
  • copy the testKey.key.pub file contents into the Verify Signature -> Public Key… field.
Testing generated JWT token in jwt.io note the Signature Verified
  • One final test, lets use the public key we generated for testKey2 and make sure it is not a valid signature for testKey1. Copy the testKey2.key.pub into the Verify Signature -> Public Key… field.
Testing generated JWT token in jwt.io with incorrect public key note the Invalid Signature

Next Steps

Read the corresponding articles on:

  • Generating a JWT token native NodeJS

References

[1] : https://man7.org/linux/man-pages/man1/ssh-keygen.1.html

[2] : https://www.openssl.org/docs/manmaster/man1/openssl.html

[3] : https://www.openssl.org/docs/manmaster/man1/openssl-rsa.html

--

--