Skip to content

Commit 6b1e5e0

Browse files
committed
Merge pull request jpadilla#20 from mandus/master
Update of pyjwt to support RSA keys
2 parents f902a82 + ed40c85 commit 6b1e5e0

File tree

6 files changed

+91
-4
lines changed

6 files changed

+91
-4
lines changed

AUTHORS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ Patches and Suggestions
77
-----------------------
88

99
- FELD Boris <boris.feld@novapost.fr> <lothiraldan@gmail.com>
10+
11+
- Åsmund Ødegård <asmund@xal.no> <ao@mcash.no>
12+
Adding support for RSA-SHA256 privat/public signature.

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,18 @@ The JWT spec supports several algorithms for cryptographic signing. This library
2929
* HS256 - HMAC using SHA-256 hash algorithm (default)
3030
* HS384 - HMAC using SHA-384 hash algorithm
3131
* HS512 - HMAC using SHA-512 hash algorithm
32+
* RS256 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-256 hash algorithm
33+
* RS384 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-384 hash algorithm
34+
* RS512 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-512 hash algorithm
3235

3336
Change the algorithm with by setting it in encode:
3437

3538
jwt.encode({"some": "payload"}, "secret", "HS512")
3639

40+
For the RSASSA-PKCS1-v1_5 algorithms, the "secret" argument in jwt.encode is supposed to be a private RSA key as
41+
imported with Crypto.PublicKey.RSA.importKey. Likewise, the "secret" argument in jwt.decode is supposed to be the
42+
public RSA key imported with the same method.
43+
3744
Tests
3845
-----
3946

jwt/__init__.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
from calendar import timegm
1515
from collections import Mapping
1616

17+
from Crypto.Signature import PKCS1_v1_5
18+
from Crypto.Hash import SHA256
19+
from Crypto.Hash import SHA384
20+
from Crypto.Hash import SHA512
21+
1722
try:
1823
import json
1924
except ImportError:
@@ -38,7 +43,19 @@ class ExpiredSignature(Exception):
3843
'HS256': lambda msg, key: hmac.new(key, msg, hashlib.sha256).digest(),
3944
'HS384': lambda msg, key: hmac.new(key, msg, hashlib.sha384).digest(),
4045
'HS512': lambda msg, key: hmac.new(key, msg, hashlib.sha512).digest(),
41-
}
46+
'RS256': lambda msg, key: PKCS1_v1_5.new(key).sign(SHA256.new(msg)),
47+
'RS384': lambda msg, key: PKCS1_v1_5.new(key).sign(SHA384.new(msg)),
48+
'RS512': lambda msg, key: PKCS1_v1_5.new(key).sign(SHA512.new(msg)),
49+
}
50+
51+
verify_methods = {
52+
'HS256': lambda msg, key: hmac.new(key, msg, hashlib.sha256).digest(),
53+
'HS384': lambda msg, key: hmac.new(key, msg, hashlib.sha384).digest(),
54+
'HS512': lambda msg, key: hmac.new(key, msg, hashlib.sha512).digest(),
55+
'RS256': lambda msg, key, sig: PKCS1_v1_5.new(key).verify(SHA256.new(msg), sig),
56+
'RS384': lambda msg, key, sig: PKCS1_v1_5.new(key).verify(SHA384.new(msg), sig),
57+
'RS512': lambda msg, key, sig: PKCS1_v1_5.new(key).verify(SHA512.new(msg), sig),
58+
}
4259

4360

4461
def constant_time_compare(val1, val2):
@@ -146,9 +163,13 @@ def decode(jwt, key='', verify=True, verify_expiration=True, leeway=0):
146163
try:
147164
if isinstance(key, unicode):
148165
key = key.encode('utf-8')
149-
expected = signing_methods[header['alg']](signing_input, key)
150-
if not constant_time_compare(signature, expected):
151-
raise DecodeError("Signature verification failed")
166+
if header['alg'].startswith('HS'):
167+
expected = verify_methods[header['alg']](signing_input, key)
168+
if not constant_time_compare(signature, expected):
169+
raise DecodeError("Signature verification failed")
170+
else:
171+
if not verify_methods[header['alg']](signing_input, key, signature):
172+
raise DecodeError("Signature verification failed")
152173
except KeyError:
153174
raise DecodeError("Algorithm not supported")
154175

tests/test_jwt.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
if sys.version_info >= (3, 0, 0):
1111
unicode = str
1212

13+
from Crypto.PublicKey import RSA
14+
1315

1416
def utc_timestamp():
1517
return timegm(datetime.utcnow().utctimetuple())
@@ -172,6 +174,32 @@ def test_decode_with_expiration_with_leeway(self):
172174
with self.assertRaises(jwt.ExpiredSignature):
173175
jwt.decode(jwt_message, secret, leeway=1)
174176

177+
def test_encode_decode_with_rsa_sha256(self):
178+
with open('tests/testkey','r') as rsa_priv_file:
179+
priv_rsakey = RSA.importKey(rsa_priv_file.read())
180+
jwt_message = jwt.encode(self.payload, priv_rsakey, algorithm='RS256')
181+
with open('tests/testkey.pub','r') as rsa_pub_file:
182+
pub_rsakey = RSA.importKey(rsa_pub_file.read())
183+
assert jwt.decode(jwt_message, pub_rsakey)
184+
185+
def test_encode_decode_with_rsa_sha384(self):
186+
with open('tests/testkey','r') as rsa_priv_file:
187+
priv_rsakey = RSA.importKey(rsa_priv_file.read())
188+
jwt_message = jwt.encode(self.payload, priv_rsakey, algorithm='RS384')
189+
with open('tests/testkey.pub','r') as rsa_pub_file:
190+
pub_rsakey = RSA.importKey(rsa_pub_file.read())
191+
assert jwt.decode(jwt_message, pub_rsakey)
192+
193+
def test_encode_decode_with_rsa_sha512(self):
194+
with open('tests/testkey','r') as rsa_priv_file:
195+
priv_rsakey = RSA.importKey(rsa_priv_file.read())
196+
jwt_message = jwt.encode(self.payload, priv_rsakey, algorithm='RS512')
197+
with open('tests/testkey.pub','r') as rsa_pub_file:
198+
pub_rsakey = RSA.importKey(rsa_pub_file.read())
199+
assert jwt.decode(jwt_message, pub_rsakey)
200+
201+
202+
175203

176204
if __name__ == '__main__':
177205
unittest.main()

tests/testkey

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEpQIBAAKCAQEA1HgzBfJv2cOjQryCwe8NEelriOTNFWKZUivevUrRhlqcmZJd
3+
CvuCJRr+xCN+OmO8qwgJJR98feNujxVg+J9Ls3/UOA4HcF9nYH6aqVXELAE8Hk/A
4+
Lvxi96ms1DDuAvQGaYZ+lANxlvxeQFOZSbjkz/9mh8aLeGKwqJLp3p+OhUBQpwvA
5+
UAPg82+OUtgTW3nSljjeFr14B8qAneGSc/wl0ni++1SRZUXFSovzcqQOkla3W27r
6+
rLfrD6LXgj/TsDs4vD1PnIm1zcVenKT7TfYI17bsG/O/Wecwz2Nl19pL7gDosNru
7+
F3ogJWNq1Lyn/ijPQnkPLpZHyhvuiycYcI3DiQIDAQABAoIBAQCt9uzwBZ0HVGQs
8+
lGULnUu6SsC9iXlR9TVMTpdFrij4NODb7Tc5cs0QzJWkytrjvB4Se7XhK3KnMLyp
9+
cvu/Fc7J3fRJIVN98t+V5pOD6rGAxlIPD4Vv8z6lQcw8wQNgb6WAaZriXh93XJNf
10+
YBO2hSj0FU5CBZLUsxmqLQBIQ6RR/OUGAvThShouE9K4N0vKB2UPOCu5U+d5zS3W
11+
44Q5uatxYiSHBTYIZDN4u27Nfo5WA+GTvFyeNsO6tNNWlYfRHSBtnm6SZDY/5i4J
12+
fxP2JY0waM81KRvuHTazY571lHM/TTvFDRUX5nvHIu7GToBKahfVLf26NJuTZYXR
13+
5c09GAXBAoGBAO7a9M/dvS6eDhyESYyCjP6w61jD7UYJ1fudaYFrDeqnaQ857Pz4
14+
BcKx3KMmLFiDvuMgnVVj8RToBGfMV0zP7sDnuFRJnWYcOeU8e2sWGbZmWGWzv0SD
15+
+AhppSZThU4mJ8aa/tgsepCHkJnfoX+3wN7S9NfGhM8GDGxTHJwBpxINAoGBAOO4
16+
ZVtn9QEblmCX/Q5ejInl43Y9nRsfTy9lB9Lp1cyWCJ3eep6lzT60K3OZGVOuSgKQ
17+
vZ/aClMCMbqsAAG4fKBjREA6p7k4/qaMApHQum8APCh9WPsKLaavxko8ZDc41kZt
18+
hgKyUs2XOhW/BLjmzqwGryidvOfszDwhH7rNVmRtAoGBALYGdvrSaRHVsbtZtRM3
19+
imuuOCx1Y6U0abZOx9Cw3PIukongAxLlkL5G/XX36WOrQxWkDUK930OnbXQM7ZrD
20+
+5dW/8p8L09Zw2VHKmb5eK7gYA1hZim4yJTgrdL/Y1+jBDz+cagcfWsXZMNfAZxr
21+
VLh628x0pVF/sof67pqVR9UhAoGBAMcQiLoQ9GJVhW1HMBYBnQVnCyJv1gjBo+0g
22+
emhrtVQ0y6+FrtdExVjNEzboXPWD5Hq9oKY+aswJnQM8HH1kkr16SU2EeN437pQU
23+
zKI/PtqN8AjNGp3JVgLioYp/pHOJofbLA10UGcJTMpmT9ELWsVA8P55X1a1AmYDu
24+
y9f2bFE5AoGAdjo95mB0LVYikNPa+NgyDwLotLqrueb9IviMmn6zKHCwiOXReqXD
25+
X9slB8RA15uv56bmN04O//NyVFcgJ2ef169GZHiRFIgIy0Pl8LYkMhCYKKhyqM7g
26+
xN+SqGqDTKDC22j00S7jcvCaa1qadn1qbdfukZ4NXv7E2d/LO0Y2Kkc=
27+
-----END RSA PRIVATE KEY-----

tests/testkey.pub

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUeDMF8m/Zw6NCvILB7w0R6WuI5M0VYplSK969StGGWpyZkl0K+4IlGv7EI346Y7yrCAklH3x9426PFWD4n0uzf9Q4DgdwX2dgfpqpVcQsATweT8Au/GL3qazUMO4C9AZphn6UA3GW/F5AU5lJuOTP/2aHxot4YrCokunen46FQFCnC8BQA+Dzb45S2BNbedKWON4WvXgHyoCd4ZJz/CXSeL77VJFlRcVKi/NypA6SVrdbbuust+sPoteCP9OwOzi8PU+cibXNxV6cpPtN9gjXtuwb879Z5zDPY2XX2kvuAOiw2u4XeiAlY2rUvKf+KM9CeQ8ulkfKG+6LJxhwjcOJ aasmundo@mair.local

0 commit comments

Comments
 (0)