JWT encrypting payload in python? (JWE)
Question:
According to RFC 7516 it should be possible to encrypt the payload/claim, called JWE.
Are there any python libraries out there that support that?
I’ve checked PyJWT, python-jose and jwcrypto but they all just have examples for signing with HS256
(JWS).
Sorry if this is totally obvious, but when it comes to things involving crypto I’m extra cautious.
Answers:
Both Jose and jwcrypto libraries can do JWE.
For jose:
claims = {
'iss': 'http://www.example.com',
'sub': 42,
}
pubKey = {'k':
'-----BEGIN PUBLIC KEY-----n
-----END PUBLIC KEY-----'
}
# decrypt on the other end using the private key
privKey = {'k':
'-----BEGIN RSA PRIVATE KEY-----n'+
'-----END RSA PRIVATE KEY-----'
}
encJwt = jose.encrypt(claims, pubKey)
serJwt = jose.serialize_compact(encJwt)
decJwt = jose.decrypt(jose.deserialize_compact(serJwt), privKey)
For jwcrypto:
# algorithm to use
eprot = {'alg': "RSA-OAEP", 'enc': "A128CBC-HS256"}
stringPayload = u'attack at dawn'
E = jwe.JWE(stringPayload, json_encode(eprot))
E.add_recipient(pubKey)
encrypted_token = E.serialize(compact=True)
E = jwe.JWE()
E.deserialize(encrypted_token, key=privKey)
decrypted_payload = E.payload
https://jwcrypto.readthedocs.io/en/latest/jwk.html#examples
from jwcrypto import jwk
_k = jwk.JWK.generate(kty='RSA', size=2048)
_text = _k.export()
import json
# loading the key back
_import_key_dict = json.loads(_text)
key = jwk.JWK(**json.loads(_import_key_dict))
I can add a new library to the above suggested libraries, named jwskate
, as initials of "JSON Web Signing, Keys, Algorithms, Tokens, and Encryption". Disclaimer: I am the author of that lib. I wrote it because I was not satisfied with the APIs from the previous libs which are not Pythonic enough for my tastes.
Here is an usage example to encode/decode a JWE, in this particular case using ECDH-ES+A256KW
and A128CBC-HS256
, but obviously you can use any supported key management and encryption algorithm:
from jwskate import JweCompact, Jwk
plaintext = b"this is an example plaintext"
# I'll use this specific Elliptic Curve private key:
key = Jwk(
{'kid': '8-nLgBsa-vXI_geoGt061_ZiVZ8BB-hYBDSoOQj9QgI',
'alg': 'ECDH-ES+A256KW',
'crv': 'P-256',
'd': '39QMopTVL1u267FOx4ayvsueDU317vHaq_z-PU_NioA',
'kty': 'EC',
'x': 'f_VRZlIk1Qd2eNGFVas9sNXx9wd43L8VymknAyP5Ntk',
'y': 'NmsfCs5VVOk6FEE31aaN9jB8rlfz1MWolBC3af_8DGs'}
)
# alternatively, you can generate one like this:
random_key = Jwk.generate_for_alg("ECDH-ES+A256KW").with_kid_thumbprint()
# sign your JWE
jwe = JweCompact.encrypt(plaintext, jwk=key.public_jwk(), enc="A128CBC-HS256")
print(jwe)
# it will look like:
# eyJlcGsiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiItVnNfYkdSNTdUUVY4MHNnUENwcWZhVjNmWXR4dWdTWmJRM1FLeTJEVDdNIiwieSI6IjBtc0pZSUFfMC1OY2lfM0plOWZLSml3RU1ZdGRBaE9kZDZhdkp5THd0dzQifSwiYWxnIjoiRUNESC1FUytBMjU2S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoiOC1uTGdCc2EtdlhJX2dlb0d0MDYxX1ppVlo4QkItaFlCRFNvT1FqOVFnSSJ9.nnOEhmdonA19LRvyKSrL7f8aEb2vVwE7EU-zO91fyTUls4otMVppYg.h8h7Mxz4irvckPnknsnM0g.sRQJJq-RmiF7GeqvL8EpWTstS-daLbfgGnOPybWeOj8.z3heCfTiI0cjw8GaV0qcHw
# as recipient, you can decrypt your JWE like this:
jwe = JweCompact("""eyJlcGsiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJkSllwMHNTZUVhMnhiMkc4M2Jnam
1VNnp4OEFxTkZRVmlJclJXUnlJYURzIiwieSI6InJXcEZ0OENESGNkQXFoMVR2eG9BZTFCT3FfZ2I3RzJya0hVd0hhNldfV0kif
SwiYWxnIjoiRUNESC1FUytBMjU2S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoiOC1uTGdCc2EtdlhJX2dlb0d0MDYx
X1ppVlo4QkItaFlCRFNvT1FqOVFnSSJ9.Nt89wpmYDZmbmjQCEZnZOygTOP5x2s7trvzLFehw1I_lMzTU-qlrcg.SQgJPG_WNUn
F13XnCJMAtw.wWwu_VUG7LPbsnWFTv-rAyiG84RW4tszR2fQ-AQaLBI.Onf3K4MSKhXaUrS8NpMDIA""")
assert jwe.decrypt(key) == plaintext
According to RFC 7516 it should be possible to encrypt the payload/claim, called JWE.
Are there any python libraries out there that support that?
I’ve checked PyJWT, python-jose and jwcrypto but they all just have examples for signing with HS256
(JWS).
Sorry if this is totally obvious, but when it comes to things involving crypto I’m extra cautious.
Both Jose and jwcrypto libraries can do JWE.
For jose:
claims = {
'iss': 'http://www.example.com',
'sub': 42,
}
pubKey = {'k':
'-----BEGIN PUBLIC KEY-----n
-----END PUBLIC KEY-----'
}
# decrypt on the other end using the private key
privKey = {'k':
'-----BEGIN RSA PRIVATE KEY-----n'+
'-----END RSA PRIVATE KEY-----'
}
encJwt = jose.encrypt(claims, pubKey)
serJwt = jose.serialize_compact(encJwt)
decJwt = jose.decrypt(jose.deserialize_compact(serJwt), privKey)
For jwcrypto:
# algorithm to use
eprot = {'alg': "RSA-OAEP", 'enc': "A128CBC-HS256"}
stringPayload = u'attack at dawn'
E = jwe.JWE(stringPayload, json_encode(eprot))
E.add_recipient(pubKey)
encrypted_token = E.serialize(compact=True)
E = jwe.JWE()
E.deserialize(encrypted_token, key=privKey)
decrypted_payload = E.payload
https://jwcrypto.readthedocs.io/en/latest/jwk.html#examples
from jwcrypto import jwk
_k = jwk.JWK.generate(kty='RSA', size=2048)
_text = _k.export()
import json
# loading the key back
_import_key_dict = json.loads(_text)
key = jwk.JWK(**json.loads(_import_key_dict))
I can add a new library to the above suggested libraries, named jwskate
, as initials of "JSON Web Signing, Keys, Algorithms, Tokens, and Encryption". Disclaimer: I am the author of that lib. I wrote it because I was not satisfied with the APIs from the previous libs which are not Pythonic enough for my tastes.
Here is an usage example to encode/decode a JWE, in this particular case using ECDH-ES+A256KW
and A128CBC-HS256
, but obviously you can use any supported key management and encryption algorithm:
from jwskate import JweCompact, Jwk
plaintext = b"this is an example plaintext"
# I'll use this specific Elliptic Curve private key:
key = Jwk(
{'kid': '8-nLgBsa-vXI_geoGt061_ZiVZ8BB-hYBDSoOQj9QgI',
'alg': 'ECDH-ES+A256KW',
'crv': 'P-256',
'd': '39QMopTVL1u267FOx4ayvsueDU317vHaq_z-PU_NioA',
'kty': 'EC',
'x': 'f_VRZlIk1Qd2eNGFVas9sNXx9wd43L8VymknAyP5Ntk',
'y': 'NmsfCs5VVOk6FEE31aaN9jB8rlfz1MWolBC3af_8DGs'}
)
# alternatively, you can generate one like this:
random_key = Jwk.generate_for_alg("ECDH-ES+A256KW").with_kid_thumbprint()
# sign your JWE
jwe = JweCompact.encrypt(plaintext, jwk=key.public_jwk(), enc="A128CBC-HS256")
print(jwe)
# it will look like:
# eyJlcGsiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiItVnNfYkdSNTdUUVY4MHNnUENwcWZhVjNmWXR4dWdTWmJRM1FLeTJEVDdNIiwieSI6IjBtc0pZSUFfMC1OY2lfM0plOWZLSml3RU1ZdGRBaE9kZDZhdkp5THd0dzQifSwiYWxnIjoiRUNESC1FUytBMjU2S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoiOC1uTGdCc2EtdlhJX2dlb0d0MDYxX1ppVlo4QkItaFlCRFNvT1FqOVFnSSJ9.nnOEhmdonA19LRvyKSrL7f8aEb2vVwE7EU-zO91fyTUls4otMVppYg.h8h7Mxz4irvckPnknsnM0g.sRQJJq-RmiF7GeqvL8EpWTstS-daLbfgGnOPybWeOj8.z3heCfTiI0cjw8GaV0qcHw
# as recipient, you can decrypt your JWE like this:
jwe = JweCompact("""eyJlcGsiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJkSllwMHNTZUVhMnhiMkc4M2Jnam
1VNnp4OEFxTkZRVmlJclJXUnlJYURzIiwieSI6InJXcEZ0OENESGNkQXFoMVR2eG9BZTFCT3FfZ2I3RzJya0hVd0hhNldfV0kif
SwiYWxnIjoiRUNESC1FUytBMjU2S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoiOC1uTGdCc2EtdlhJX2dlb0d0MDYx
X1ppVlo4QkItaFlCRFNvT1FqOVFnSSJ9.Nt89wpmYDZmbmjQCEZnZOygTOP5x2s7trvzLFehw1I_lMzTU-qlrcg.SQgJPG_WNUn
F13XnCJMAtw.wWwu_VUG7LPbsnWFTv-rAyiG84RW4tszR2fQ-AQaLBI.Onf3K4MSKhXaUrS8NpMDIA""")
assert jwe.decrypt(key) == plaintext