Skip to content

Instantly share code, notes, and snippets.

@jpf
Last active February 17, 2024 15:07
Show Gist options
  • Star 26 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save jpf/1e860e5ea70c0a70fd5e to your computer and use it in GitHub Desktop.
Save jpf/1e860e5ea70c0a70fd5e to your computer and use it in GitHub Desktop.
JWTs signed with RS256 in Python: A demonstration of org-babel

Introduction

This is a guide to using pyjwt to sign and validate a JWT using RS256.

The trickiest part of doing this is knowing what the proper OpenSSL commands are to generate the RSA keypair. I demonstrate that below.

Generating RSA keys

Here is how use the OpenSSL command line tool to generate a private key with a size of 1024.

openssl genrsa 1024

-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCxlVm+DawmijNIDmAt11Sk5lRkd9691RyQevmr8u/eKvTV7eta
ZmGm2GmbuURYzwQfQ1+DFNrzu70wBLTyDrxCoX/vb/5hANwLJr5Eec6gGTF6/y4F
riRM1NEBZG9PnvmEPa1bfa27dnV5hz+GTIsCFCw4rXGI7c6ETg+v9t4HqQIDAQAB
AoGAMoekuYd6bJz2apJsm56h3yoK6WuSXcG+Fv5m/J5r0nO2pwjD5z0qnCcIJd9Z
q0t8iMjK7KmKg7/v3TH5qsa2mmUZ8UYMI5VmkwlKL4BD9mT67+ZAlqdLOHlrdrhG
u2FWR0PF5W2z06NqohWHepL01K7PLtxzaVzbQIwS4X3YsuECQQDrcqdSqj6gSF3Q
ZOZEMoES64iwAe/Fi4VmE6s0PEhoVXbbxFNxuvQPkylmoGs82ByAqltNk7p8G0Wb
hd+j3/KHAkEAwRWmkD189gSR/jLPEy/2fIkXhfe98OruPyG2yDsa3vp5wC2nrniG
udMxC4OJ1yXHGt4KBeOoOL94lX9p9UhQTwJAOx+SZs67ZTJm5HLB4/Qut1qP+2qx
FBEiEWz0++v7Xr+/VhZpwdBpgxO4PL4hz6iRF7ovrT5ggNO0WgZ3D0aoNwJBAKhx
T9ajnaEuCYLeJmJRxFGOc3QO1agX+3Id4kw5q858arxp18/QG5B/Glk2Dokfztu0
er/6hCXFe9fHyNMPm+cCQQCsqdhniYyaHPlyn/6bOwCcZua74JRZ3vNSkjAKzHan
WjSRdVvCHs8bY1Um0QZ+nHpT3QhcBPYyQoJArs8P1XBH
-----END RSA PRIVATE KEY-----

Now that we have this private key, let's take a look at what's inside and then extract the public key into a separate string.

A look inside the private key

Here is what is inside the keypair we just generated:

(Note: The sed command below is needed because org-babel adds leading whitespace to the string and OpenSSL can't deal keypairs that have leading whitespace.

echo "$private_key" | sed -e 's/^[ ]*//' | openssl rsa -text

Private-Key: (1024 bit)
modulus:
    00:aa:ad:9a:f3:09:8d:68:4c:44:09:5b:ab:d4:27:
    7a:7d:0e:00:66:dc:e0:67:58:62:1e:78:d1:2a:7e:
    de:b0:a2:24:b2:9b:03:5d:1f:17:d0:b1:9a:df:2c:
    e7:e7:c3:17:56:90:f2:07:91:84:ec:84:76:10:43:
    35:97:6d:af:03:e7:bf:0e:e3:53:8f:26:10:be:8e:
    e2:12:ae:56:2d:34:14:0a:ae:ef:e7:01:3d:4d:7c:
    f7:1e:c8:11:ca:6f:6f:cf:b7:67:e2:ac:54:77:20:
    09:89:14:c0:66:21:cd:77:fa:91:25:66:59:31:c2:
    c5:59:fe:0e:99:83:f0:56:b3
publicExponent: 65537 (0x10001)
privateExponent:
    4d:57:b2:39:a4:00:82:5c:dd:0f:e8:8c:aa:ec:e0:
    e2:be:6f:8c:2d:57:3b:3d:9f:e8:f3:12:c5:d1:0c:
    14:ba:c5:2a:72:78:49:c0:87:48:38:d3:57:82:bf:
    ec:14:4a:05:1e:55:ae:fc:50:61:e5:7c:a2:cd:f2:
    01:16:e1:11:84:19:61:58:5f:fb:69:8d:ff:76:64:
    fc:e8:3a:b9:28:84:9f:35:d8:9f:96:5c:c8:73:7d:
    39:76:7c:99:a4:81:0d:50:c0:f0:51:45:9d:76:7a:
    f4:53:81:0f:dd:3f:98:40:9b:a0:c5:cc:71:98:f3:
    38:10:1b:b8:42:ff:4b:21
prime1:
    00:df:89:a7:0c:cb:d0:8e:81:76:2a:a0:e0:83:ae:
    70:69:4e:8b:ec:f2:ce:57:9b:16:d5:9a:72:be:66:
    e4:b8:1a:84:e7:f4:28:2e:d6:70:50:f0:0f:85:ed:
    67:33:d2:30:a2:2b:23:da:85:81:7c:2e:85:80:7f:
    4d:74:a9:95:69
prime2:
    00:c3:76:d1:de:11:ed:3b:16:97:ad:0e:65:22:ad:
    1a:bd:95:a7:e7:f8:65:07:3e:ad:bf:30:d6:d4:9a:
    04:9e:4b:5d:08:f2:2d:1e:5c:81:5a:04:80:f7:9d:
    92:e6:54:e1:62:89:67:d1:f3:1a:ff:77:5b:a0:06:
    5a:d8:79:3b:bb
exponent1:
    16:2c:8c:72:9b:81:2b:b1:b1:ec:16:9b:4e:d4:ad:
    f7:f4:3f:b3:18:7e:d9:77:db:f3:02:68:21:75:09:
    79:2d:c0:43:56:17:ea:55:81:3c:b6:23:84:10:81:
    ad:45:4b:67:ba:c9:ca:b2:75:9e:c0:ea:a3:4d:7d:
    7c:76:1a:09
exponent2:
    2f:ac:1b:23:7e:5b:cd:bd:84:e0:c0:52:0a:53:0d:
    e2:8f:4d:94:56:10:cd:e3:8b:9c:c5:dc:9f:9d:b0:
    e2:aa:9e:d6:3f:ba:a1:5d:0e:6f:56:09:de:5a:a0:
    29:6d:2a:4b:4e:17:f4:2c:c6:b8:e7:f3:80:e4:0b:
    e4:20:2d:61
coefficient:
    00:c6:4d:e9:e0:56:90:c7:a1:c0:c9:72:c0:bd:17:
    93:e8:fc:5a:64:5c:76:e2:74:67:e1:9d:3e:48:55:
    bb:d2:c0:30:23:4e:21:5a:a8:01:52:11:53:f3:e2:
    d4:61:2e:38:8c:18:b2:2e:87:02:c3:4d:b3:c8:64:
    dd:50:39:33:06
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCqrZrzCY1oTEQJW6vUJ3p9DgBm3OBnWGIeeNEqft6woiSymwNd
HxfQsZrfLOfnwxdWkPIHkYTshHYQQzWXba8D578O41OPJhC+juISrlYtNBQKru/n
AT1NfPceyBHKb2/Pt2firFR3IAmJFMBmIc13+pElZlkxwsVZ/g6Zg/BWswIDAQAB
AoGATVeyOaQAglzdD+iMquzg4r5vjC1XOz2f6PMSxdEMFLrFKnJ4ScCHSDjTV4K/
7BRKBR5VrvxQYeV8os3yARbhEYQZYVhf+2mN/3Zk/Og6uSiEnzXYn5ZcyHN9OXZ8
maSBDVDA8FFFnXZ69FOBD90/mECboMXMcZjzOBAbuEL/SyECQQDfiacMy9COgXYq
oOCDrnBpTovs8s5XmxbVmnK+ZuS4GoTn9Cgu1nBQ8A+F7Wcz0jCiKyPahYF8LoWA
f010qZVpAkEAw3bR3hHtOxaXrQ5lIq0avZWn5/hlBz6tvzDW1JoEnktdCPItHlyB
WgSA952S5lThYoln0fMa/3dboAZa2Hk7uwJAFiyMcpuBK7Gx7BabTtSt9/Q/sxh+
2Xfb8wJoIXUJeS3AQ1YX6lWBPLYjhBCBrUVLZ7rJyrJ1nsDqo019fHYaCQJAL6wb
I35bzb2E4MBSClMN4o9NlFYQzeOLnMXcn52w4qqe1j+6oV0Ob1YJ3lqgKW0qS04X
9CzGuOfzgOQL5CAtYQJBAMZN6eBWkMehwMlywL0Xk+j8WmRcduJ0Z+GdPkhVu9LA
MCNOIVqoAVIRU/Pi1GEuOIwYsi6HAsNNs8hk3VA5MwY=
-----END RSA PRIVATE KEY-----

Extracting the public key from the private key

Here we use the -pubout flag to extract the public key that we will use later.

echo "$private_key" | sed -e 's/^[ ]*//' | openssl rsa -pubout

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqrZrzCY1oTEQJW6vUJ3p9DgBm
3OBnWGIeeNEqft6woiSymwNdHxfQsZrfLOfnwxdWkPIHkYTshHYQQzWXba8D578O
41OPJhC+juISrlYtNBQKru/nAT1NfPceyBHKb2/Pt2firFR3IAmJFMBmIc13+pEl
ZlkxwsVZ/g6Zg/BWswIDAQAB
-----END PUBLIC KEY-----

Using the RSA keys with PyJWT

This is a little Python list comprehension that will strip out leading whitespace from input key:

key = "\n".join([l.lstrip() for l in input_key.split("\n")])

And here we take the private_key we created with OpenSSL and use that to create a JWT that is signed with RS256:

key = "\n".join([l.lstrip() for l in input_key.split("\n")])
claim = {'test': "hello"}

import jwt
token = jwt.encode(
    claim,
    key,
    algorithm='RS256')
return token

Now, taking that result, we can decode the JWT we just generated and verify the signature:

key = "\n".join([l.lstrip() for l in input_key.split("\n")])

import jwt
return jwt.decode(token, key, algorithms=['RS256'])

{u'test': u'hello'}

#

Introduction

This is a guide to using pyjwt to sign and validate a JWT using RS256.

The trickiest part of doing this is knowing what the proper OpenSSL commands are to generate the RSA keypair. I demonstrate that below.

Generating RSA keys

Here is how use the OpenSSL command line tool to generate a private key with a size of 1024.

openssl genrsa 1024
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCqrZrzCY1oTEQJW6vUJ3p9DgBm3OBnWGIeeNEqft6woiSymwNd
HxfQsZrfLOfnwxdWkPIHkYTshHYQQzWXba8D578O41OPJhC+juISrlYtNBQKru/n
AT1NfPceyBHKb2/Pt2firFR3IAmJFMBmIc13+pElZlkxwsVZ/g6Zg/BWswIDAQAB
AoGATVeyOaQAglzdD+iMquzg4r5vjC1XOz2f6PMSxdEMFLrFKnJ4ScCHSDjTV4K/
7BRKBR5VrvxQYeV8os3yARbhEYQZYVhf+2mN/3Zk/Og6uSiEnzXYn5ZcyHN9OXZ8
maSBDVDA8FFFnXZ69FOBD90/mECboMXMcZjzOBAbuEL/SyECQQDfiacMy9COgXYq
oOCDrnBpTovs8s5XmxbVmnK+ZuS4GoTn9Cgu1nBQ8A+F7Wcz0jCiKyPahYF8LoWA
f010qZVpAkEAw3bR3hHtOxaXrQ5lIq0avZWn5/hlBz6tvzDW1JoEnktdCPItHlyB
WgSA952S5lThYoln0fMa/3dboAZa2Hk7uwJAFiyMcpuBK7Gx7BabTtSt9/Q/sxh+
2Xfb8wJoIXUJeS3AQ1YX6lWBPLYjhBCBrUVLZ7rJyrJ1nsDqo019fHYaCQJAL6wb
I35bzb2E4MBSClMN4o9NlFYQzeOLnMXcn52w4qqe1j+6oV0Ob1YJ3lqgKW0qS04X
9CzGuOfzgOQL5CAtYQJBAMZN6eBWkMehwMlywL0Xk+j8WmRcduJ0Z+GdPkhVu9LA
MCNOIVqoAVIRU/Pi1GEuOIwYsi6HAsNNs8hk3VA5MwY=
-----END RSA PRIVATE KEY-----

Now that we have this private key, let’s take a look at what’s inside and then extract the public key into a separate string.

A look inside the private key

Here is what is inside the keypair we just generated:

(Note: The sed command below is needed because org-babel adds leading whitespace to the string and OpenSSL can’t deal keypairs that have leading whitespace.

echo "$private_key" | sed -e 's/^[ ]*//' | openssl rsa -text
Private-Key: (1024 bit)
modulus:
    00:aa:ad:9a:f3:09:8d:68:4c:44:09:5b:ab:d4:27:
    7a:7d:0e:00:66:dc:e0:67:58:62:1e:78:d1:2a:7e:
    de:b0:a2:24:b2:9b:03:5d:1f:17:d0:b1:9a:df:2c:
    e7:e7:c3:17:56:90:f2:07:91:84:ec:84:76:10:43:
    35:97:6d:af:03:e7:bf:0e:e3:53:8f:26:10:be:8e:
    e2:12:ae:56:2d:34:14:0a:ae:ef:e7:01:3d:4d:7c:
    f7:1e:c8:11:ca:6f:6f:cf:b7:67:e2:ac:54:77:20:
    09:89:14:c0:66:21:cd:77:fa:91:25:66:59:31:c2:
    c5:59:fe:0e:99:83:f0:56:b3
publicExponent: 65537 (0x10001)
privateExponent:
    4d:57:b2:39:a4:00:82:5c:dd:0f:e8:8c:aa:ec:e0:
    e2:be:6f:8c:2d:57:3b:3d:9f:e8:f3:12:c5:d1:0c:
    14:ba:c5:2a:72:78:49:c0:87:48:38:d3:57:82:bf:
    ec:14:4a:05:1e:55:ae:fc:50:61:e5:7c:a2:cd:f2:
    01:16:e1:11:84:19:61:58:5f:fb:69:8d:ff:76:64:
    fc:e8:3a:b9:28:84:9f:35:d8:9f:96:5c:c8:73:7d:
    39:76:7c:99:a4:81:0d:50:c0:f0:51:45:9d:76:7a:
    f4:53:81:0f:dd:3f:98:40:9b:a0:c5:cc:71:98:f3:
    38:10:1b:b8:42:ff:4b:21
prime1:
    00:df:89:a7:0c:cb:d0:8e:81:76:2a:a0:e0:83:ae:
    70:69:4e:8b:ec:f2:ce:57:9b:16:d5:9a:72:be:66:
    e4:b8:1a:84:e7:f4:28:2e:d6:70:50:f0:0f:85:ed:
    67:33:d2:30:a2:2b:23:da:85:81:7c:2e:85:80:7f:
    4d:74:a9:95:69
prime2:
    00:c3:76:d1:de:11:ed:3b:16:97:ad:0e:65:22:ad:
    1a:bd:95:a7:e7:f8:65:07:3e:ad:bf:30:d6:d4:9a:
    04:9e:4b:5d:08:f2:2d:1e:5c:81:5a:04:80:f7:9d:
    92:e6:54:e1:62:89:67:d1:f3:1a:ff:77:5b:a0:06:
    5a:d8:79:3b:bb
exponent1:
    16:2c:8c:72:9b:81:2b:b1:b1:ec:16:9b:4e:d4:ad:
    f7:f4:3f:b3:18:7e:d9:77:db:f3:02:68:21:75:09:
    79:2d:c0:43:56:17:ea:55:81:3c:b6:23:84:10:81:
    ad:45:4b:67:ba:c9:ca:b2:75:9e:c0:ea:a3:4d:7d:
    7c:76:1a:09
exponent2:
    2f:ac:1b:23:7e:5b:cd:bd:84:e0:c0:52:0a:53:0d:
    e2:8f:4d:94:56:10:cd:e3:8b:9c:c5:dc:9f:9d:b0:
    e2:aa:9e:d6:3f:ba:a1:5d:0e:6f:56:09:de:5a:a0:
    29:6d:2a:4b:4e:17:f4:2c:c6:b8:e7:f3:80:e4:0b:
    e4:20:2d:61
coefficient:
    00:c6:4d:e9:e0:56:90:c7:a1:c0:c9:72:c0:bd:17:
    93:e8:fc:5a:64:5c:76:e2:74:67:e1:9d:3e:48:55:
    bb:d2:c0:30:23:4e:21:5a:a8:01:52:11:53:f3:e2:
    d4:61:2e:38:8c:18:b2:2e:87:02:c3:4d:b3:c8:64:
    dd:50:39:33:06
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCqrZrzCY1oTEQJW6vUJ3p9DgBm3OBnWGIeeNEqft6woiSymwNd
HxfQsZrfLOfnwxdWkPIHkYTshHYQQzWXba8D578O41OPJhC+juISrlYtNBQKru/n
AT1NfPceyBHKb2/Pt2firFR3IAmJFMBmIc13+pElZlkxwsVZ/g6Zg/BWswIDAQAB
AoGATVeyOaQAglzdD+iMquzg4r5vjC1XOz2f6PMSxdEMFLrFKnJ4ScCHSDjTV4K/
7BRKBR5VrvxQYeV8os3yARbhEYQZYVhf+2mN/3Zk/Og6uSiEnzXYn5ZcyHN9OXZ8
maSBDVDA8FFFnXZ69FOBD90/mECboMXMcZjzOBAbuEL/SyECQQDfiacMy9COgXYq
oOCDrnBpTovs8s5XmxbVmnK+ZuS4GoTn9Cgu1nBQ8A+F7Wcz0jCiKyPahYF8LoWA
f010qZVpAkEAw3bR3hHtOxaXrQ5lIq0avZWn5/hlBz6tvzDW1JoEnktdCPItHlyB
WgSA952S5lThYoln0fMa/3dboAZa2Hk7uwJAFiyMcpuBK7Gx7BabTtSt9/Q/sxh+
2Xfb8wJoIXUJeS3AQ1YX6lWBPLYjhBCBrUVLZ7rJyrJ1nsDqo019fHYaCQJAL6wb
I35bzb2E4MBSClMN4o9NlFYQzeOLnMXcn52w4qqe1j+6oV0Ob1YJ3lqgKW0qS04X
9CzGuOfzgOQL5CAtYQJBAMZN6eBWkMehwMlywL0Xk+j8WmRcduJ0Z+GdPkhVu9LA
MCNOIVqoAVIRU/Pi1GEuOIwYsi6HAsNNs8hk3VA5MwY=
-----END RSA PRIVATE KEY-----

Extracting the public key from the private key

Here we use the -pubout flag to extract the public key that we will use later.

echo "$private_key" | sed -e 's/^[ ]*//' | openssl rsa -pubout
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqrZrzCY1oTEQJW6vUJ3p9DgBm
3OBnWGIeeNEqft6woiSymwNdHxfQsZrfLOfnwxdWkPIHkYTshHYQQzWXba8D578O
41OPJhC+juISrlYtNBQKru/nAT1NfPceyBHKb2/Pt2firFR3IAmJFMBmIc13+pEl
ZlkxwsVZ/g6Zg/BWswIDAQAB
-----END PUBLIC KEY-----

Using the RSA keys with PyJWT

This is a little Python list comprehension that will strip out leading whitespace from input key:

key = "\n".join([l.lstrip() for l in input_key.split("\n")])

And here we take the private_key we created with OpenSSL and use that to create a JWT that is signed with RS256:

<<cleanup_input_key>>
claim = {'test': "hello"}

import jwt
token = jwt.encode(
    claim,
    key,
    algorithm='RS256')
return token

Now, taking that result, we can decode the JWT we just generated and verify the signature:

<<cleanup_input_key>>

import jwt
return jwt.decode(token, key, algorithms=['RS256'])
{u'test': u'hello'}
@bubbajoe
Copy link

bubbajoe commented Dec 13, 2019

When encoding use the private_key

jwt.encode(claim, private_key, algorithm='RS256')

When decoding use the public key of the private key that it was signed with.

jwt.decode(token, public_key, algorithms=['RS256'])

Just making it a bit clearer for newbies

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment