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'}
@rayluo
Copy link

rayluo commented Oct 12, 2018

In the PyJWT code snippet, both the encode(...) and decode(...) part use a vague key (or input_key). Are they supposed to be something like private_key and public_key, respectively, as demonstrated in PyJWT's official doc?

@AviKKi
Copy link

AviKKi commented Feb 22, 2019

@rayluo yes then are different keys, the name(identifier) of the variable doesn't matter.
Only thing to keep in mind is you use private key to encrypt data and public key to decypt data.

@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