New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

jwskate

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

jwskate

A Pythonic implementation of the JOSE / JSON Web Crypto related RFCs (JWS, JWK, JWA, JWT, JWE)

  • 0.11.1
  • PyPI
  • Socket score

Maintainers
1

jwskate

PyPi PyPi - License PyPI - Downloads Code style: ruff

A Pythonic implementation of the JOSE set of IETF specifications: Json Web Signature, Keys, Algorithms, Tokens and Encryption (RFC7515 to 7519), hence the name JWSKATE, and their extensions ECDH Signatures (RFC8037), JWK Thumbprints (RFC7638), and JWK Thumbprint URI (RFC9278), with respects to JWT Best Current Practices (RFC8725).

Here is a quick usage example: generating a private RSA key, signing some data, then validating that signature with the matching public key:

from jwskate import Jwk

# Let's generate a random private key, to use with alg 'RS256'.
# Based on that alg, jwskate knows it must be an RSA key.
# RSA keys can be of any size, so let's pass the requested key size as parameter
rsa_private_jwk = Jwk.generate(alg="RS256", key_size=2048)

data = b"Signing is easy!"  # we will sign this
signature = rsa_private_jwk.sign(data)  # done!

print(signature)
# b'-\xe89\x81\xc4\xb9.G\x11\xa6\x93/dm\xf0\xc8\x0f\xd....'

# now extract the public key, and verify the signature with it
rsa_public_jwk = rsa_private_jwk.public_jwk()
assert rsa_public_jwk.verify(data, signature)

# let's see what a `Jwk` looks like:
from collections import UserDict

assert isinstance(rsa_private_jwk, UserDict)  # Jwk are UserDicts

print(rsa_private_jwk.with_usage_parameters())

The result of this print will look like this (with the random parts abbreviated to ... for display purposes only):

{'kty': 'RSA',
 'n': '...',
 'e': 'AQAB',
 'd': '...',
 'p': '...',
 'q': '...',
 'dp': '...',
 'dq': '...',
 'qi': '...',
 'alg': 'RS256',
 'kid': '...',
 'use': 'sig',
 'key_ops': ['sign']}

Now let's sign a JWT containing arbitrary claims, this time using an Elliptic Curve (EC) key:

from jwskate import Jwk, Jwt

# This time let's try an EC key, based on `alg` parameter,
# and let's specify an arbitrary Key ID (kid).
# additional args are either options (like 'key_size' above for RSA keys)
# or additional parameters to include in the JWK
private_jwk = Jwk.generate(alg="ES256", kid="my_key")
# note that based only on the `alg` value, the appropriate key type and curve
# are automatically deduced and included in the JWK
print(private_jwk)
# {'kty': 'EC', 'crv': 'P-256', 'x': 'Ppe...', 'y': '9Si...', 'd': 'g09...', 'alg': 'ES256'}
assert private_jwk.kty == "EC"
assert private_jwk.crv == "P-256"
assert private_jwk.alg == "ES256"
# this is a private key and 'ES256' is a signature alg, so 'use' and 'key_ops' can also be deduced:
assert private_jwk.use == "sig"
assert private_jwk.key_ops == ("sign",)

# here are the claims to sign in a JWT:
claims = {"sub": "some_sub", "claim1": "value1"}

jwt = Jwt.sign(claims, private_jwk)
# that's it! we have a signed JWT.
print(jwt)
# eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzb21lX3N1YiIsImNsYWltMSI6InZhbHVlMSJ9.SBQIlGlFdwoEMViWUFsBmCsXShtOq4lnp3Im5ZVh1PFCGJFdW-dTG9qJjlFSAA_BkM5PF9u38PL7Ai9cC2_DJw
assert isinstance(jwt, Jwt)  # Jwt are objects
assert jwt.claims == claims  # claims can be accessed as a dict
assert jwt.headers == {"typ": "JWT", "alg": "ES256", "kid": "my_key"}  # headers too
assert jwt.sub == "some_sub"  # individual claims can be accessed as attributes
assert jwt["claim1"] == "value1"  # or as dict items (with "subscription")
assert jwt.alg == "ES256"  # alg and kid headers are also accessible as attributes
assert jwt.kid == private_jwk.kid
# notice that alg and kid are automatically set with appropriate values taken from our private jwk
assert isinstance(jwt.signature, bytes)  # signature is accessible too
# verifying the jwt signature is as easy as:
assert jwt.verify_signature(private_jwk.public_jwk())
# since our jwk contains an 'alg' parameter (here 'ES256'), the signature is automatically verified using that alg
# you could also specify an alg manually, useful for keys with no "alg" hint:
assert jwt.verify_signature(private_jwk.public_jwk(), alg="ES256")
# note that jwskate will only trust the alg(s) you provide as parameter, either part of the JWK
# or with `alg` or `algs` params, and will ignore the 'alg' that is set in the JWT, for security reasons.

Now let's sign a JWT with the standardized lifetime, subject, audience and ID claims, plus arbitrary custom claims:

from jwskate import Jwk, JwtSigner

private_jwk = Jwk.generate(alg="ES256")
signer = JwtSigner(issuer="https://myissuer.com", key=private_jwk)
jwt = signer.sign(
    subject="some_sub",
    audience="some_aud",
    extra_claims={"custom_claim1": "value1", "custom_claim2": "value2"},
)

print(jwt.claims)

The generated JWT will include the standardized claims (iss, aud, sub, iat, exp and jti), together with the extra_claims provided to .sign():

{'custom_claim1': 'value1',
 'custom_claim2': 'value2',
 'iss': 'https://myissuer.com',
 'aud': 'some_aud',
 'sub': 'some_sub',
 'iat': 1648823184,
 'exp': 1648823244,
 'jti': '3b400e27-c111-4013-84e0-714acd76bf3a'
}

Features

  • Simple, Clean, Pythonic interface
  • Convenience wrappers around cryptography for all algorithms described in JWA
  • Json Web Keys (JWK) loading, dumping and generation
  • Arbitrary data signature and verification using Json Web Keys
  • Json Web Signatures (JWS) signing and verification
  • Json Web Encryption (JWE) encryption and decryption
  • Json Web Tokens (JWT) signing, verification and validation
  • 100% type annotated, verified with mypy --strict
  • nearly 100% code coverage
  • Relies on cryptography for all cryptographic operations
  • Relies on BinaPy for binary data manipulations

Supported Token Types

Token TypeSupport
Json Web Signature (JWS)☑ Compact
☑ JSON Flat
☑ JSON General
Json Web Encryption (JWE)☑ Compact
☐ JSON Flat
☐ JSON General
Json Web Tokens (JWT)☑ Signed
☑ Signed and Encrypted

Supported Signature algorithms

Signature AlgDescriptionKey TypeReferenceNote
HS256HMAC using SHA-256octRFC7518, Section 3.2
HS384HMAC using SHA-384octRFC7518, Section 3.2
HS512HMAC using SHA-512octRFC7518, Section 3.2
RS256RSASSA-PKCS1-v1_5 using SHA-256RSARFC7518, Section 3.3
RS384RSASSA-PKCS1-v1_5 using SHA-384RSARFC7518, Section 3.3
RS512RSASSA-PKCS1-v1_5 using SHA-512RSARFC7518, Section 3.3
PS256RSASSA-PSS using SHA-256 and MGF1 with SHA-256RSARFC7518, Section 3.5
PS384RSASSA-PSS using SHA-384 and MGF1 with SHA-384RSARFC7518, Section 3.5
PS512RSASSA-PSS using SHA-512 and MGF1 with SHA-512RSARFC7518, Section 3.5
ES256ECDSA using P-256 and SHA-256ECRFC7518, Section 3.4
ES384ECDSA using P-384 and SHA-384ECRFC7518, Section 3.4
ES512ECDSA using P-521 and SHA-512ECRFC7518, Section 3.4
ES256KECDSA using secp256k1 curve and SHA-256ECRFC8812, Section 3.2
EdDSAEdDSA signature algorithmsOKPRFC8037, Section 3.1Ed2219 and Ed448 are supported
HS1HMAC using SHA-1octhttps://www.w3.org/TR/WebCryptoAPIValidation Only
RS1RSASSA-PKCS1-v1_5 with SHA-1RSAhttps://www.w3.org/TR/WebCryptoAPIValidation Only
noneNo digital signature or MAC performedRFC7518, Section 3.6Not usable by mistake

Supported Encryption algorithms

Signature AlgDescriptionReference
A128CBC-HS256AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithmRFC7518, Section 5.2.3
A192CBC-HS384AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithmRFC7518, Section 5.2.4
A256CBC-HS512AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithmRFC7518, Section 5.2.5
A128GCMAES GCM using 128-bit keyRFC7518, Section 5.3
A192GCMAES GCM using 192-bit keyRFC7518, Section 5.3
A256GCMAES GCM using 256-bit keyRFC7518, Section 5.3

Supported Key Management algorithms

Signature AlgDescriptionKey TypeReferenceNote
RSA1_5RSAES-PKCS1-v1_5RSARFC7518, Section 4.2Unwrap Only
RSA-OAEPRSAES OAEP using default parametersRSARFC7518, Section 4.3
RSA-OAEP-256RSAES OAEP using SHA-256 and MGF1 with SHA-256RSARFC7518, Section 4.3
RSA-OAEP-384RSA-OAEP using SHA-384 and MGF1 with SHA-384RSAhttps://www.w3.org/TR/WebCryptoAPI
RSA-OAEP-512RSA-OAEP using SHA-512 and MGF1 with SHA-512RSAhttps://www.w3.org/TR/WebCryptoAPI
A128KWAES Key Wrap using 128-bit keyoctRFC7518, Section 4.4
A192KWAES Key Wrap using 192-bit keyoctRFC7518, Section 4.4
A256KWAES Key Wrap using 256-bit keyoctRFC7518, Section 4.4
A128GCMKWKey wrapping with AES GCM using 128-bit keyoctRFC7518, Section 4.7
A192GCMKWKey wrapping with AES GCM using 192-bit keyoctRFC7518, Section 4.7
A256GCMKWKey wrapping with AES GCM using 256-bit keyoctRFC7518, Section 4.7
dirDirect use of a shared symmetric keyoctRFC7518, Section 4.5
ECDH-ESECDH-ES using Concat KDFECRFC7518, Section 4.6
ECDH-ES+A128KWECDH-ES using Concat KDF and "A128KW" wrappingECRFC7518, Section 4.6
ECDH-ES+A192KWECDH-ES using Concat KDF and "A192KW" wrappingECRFC7518, Section 4.6
ECDH-ES+A256KWECDH-ES using Concat KDF and "A256KW" wrappingECRFC7518, Section 4.6
PBES2-HS256+A128KWPBES2 with HMAC SHA-256 and "A128KW" wrappingpasswordRFC7518, Section 4.8
PBES2-HS384+A192KWPBES2 with HMAC SHA-384 and "A192KW" wrappingpasswordRFC7518, Section 4.8
PBES2-HS512+A256KWPBES2 with HMAC SHA-512 and "A256KW" wrappingpasswordRFC7518, Section 4.8

Supported Elliptic Curves

CurveDescriptionKey TypeUsageReference
P-256P-256 CurveECsignature, encryptionRFC7518, Section 6.2.1.1
P-384P-384 CurveECsignature, encryptionRFC7518, Section 6.2.1.1
P-521P-521 CurveECsignature, encryptionRFC7518, Section 6.2.1.1
secp256k1SECG secp256k1 curveECsignature, encryptionRFC8812, Section 3.1
Ed25519Ed25519 signature algorithm key pairsOKPsignatureRFC8037, Section 3.1
Ed448Ed448 signature algorithm key pairsOKPsignatureRFC8037, Section 3.1
X25519X25519 function key pairsOKPencryptionRFC8037, Section 3.2
X448X448 function key pairsOKPencryptionRFC8037, Section 3.2

Why a new lib?

There are already multiple modules implementing JOSE and Json Web Crypto related specifications in Python. However, I have been dissatisfied by all of them so far, so I decided to come up with my own module.

Not to say that those are bad libs (I actually use jwcrypto myself for jwskate unit tests), but they either don't support some important features, lack documentation, or more generally have APIs that don't feel easy-enough, Pythonic-enough to use. See Design below for some of the design decisions that lead to jwskate.

Design

Tokens are objects

Since JSON Web Tokens (JWT) are more and more used, JWT generation and validation must be as easy to do as possible. The Jwt class wraps around a JWT value to allow easy access to its headers, claims and signature, and exposes methods to easily verify the signature.

from jwskate import Jwt

jwt = Jwt(
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
)
assert jwt.headers == {"alg": "HS256", "typ": "JWT"}

assert jwt.claims == {"sub": "1234567890", "name": "John Doe", "iat": 1516239022}

assert (
    jwt.signature
    == b"I\xf9J\xc7\x04IH\xc7\x8a(]\x90O\x87\xf0\xa4\xc7\x89\x7f~\x8f:N\xb2%_\xdau\x0b,\xc3\x97"
)

Jwt instances always represent a syntactically valid JWT. If you try to initialize one with a malformed value, you will get a InvalidJwt exception, with an helpful error message:

jwt = Jwt(
    "eyJhbGci-malformedheader.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
)
# jwskate.jwt.base.InvalidJwt: Invalid JWT header: it must be a Base64URL-encoded JSON object

Jwt may be objects, but they are easy to serialize into their representation. Use either str() or bytes() depending on what type of value you need, or the value attribute:

jwt = Jwt(
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
)
str(jwt)
# 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
bytes(jwt)
# b'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
assert jwt.value == bytes(jwt)

The same is true for JWS and JWE tokens.

Headers are auto-generated

When signing a JWS, JWE or JWT, the headers are autogenerated by default, based on the used key and algorithm.

You may add your own custom headers using the extra_headers parameter, and/or set a custom typ header with the parameter of the same name:

from jwskate import SymmetricJwk, Jwt

jwk = SymmetricJwk.from_bytes(b"T0t4llyR@nd0M", kid="symmetric_key1")
jwt = Jwt.sign(
    claims={"my": "claims"},
    key=jwk,
    alg="HS256",
    typ="CustomJWT",
    extra_headers={"custom_header": "custom_value"},
)
print(jwt)
# eyJhbGciOiJIUzI1NiIsImN1c3RvbV9oZWFkZXIiOiJjdXN0b21fdmFsdWUiLCJ0eXAiOiJDdXN0b21KV1QiLCJraWQiOiJzeW1tZXRyaWNfa2V5MSJ9.eyJteSI6ImNsYWltcyJ9.ZqCp8Crq-mdCXLoy5NiEdPTSUlIFEjrzexA6mKHrMAc
print(jwt.headers)
# {'alg': 'HS256', 'custom_header': 'custom_value', 'typ': 'CustomJWT', 'kid': 'symmetric_key1'}

If, for testing purposes, you need to fully control which headers are included in the JWT, even if they are inconsistent, you can use Jwt.sign_arbitrary():

from jwskate import SymmetricJwk, Jwt

jwk = SymmetricJwk.from_bytes(b"T0t4llyR@nd0M", kid="symmetric_key1")
jwt = Jwt.sign_arbitrary(
    headers={
        "custom_header": "custom_value",
        "typ": "WeirdJWT",
        "kid": "R@nd0m_KID",
        "alg": "WeirdAlg",
    },
    claims={"my": "claims"},
    key=jwk,
    alg="HS256",
)
print(jwt)
# eyJjdXN0b21faGVhZGVyIjoiY3VzdG9tX3ZhbHVlIiwidHlwIjoiV2VpcmRKV1QiLCJraWQiOiJSQG5kMG1fS0lEIiwiYWxnIjoiV2VpcmRBbGcifQ.eyJteSI6ImNsYWltcyJ9.bcTFqCSiVIbyJhxClgsBDIyhbvLXTOXOV55QGqo2mhw
print(jwt.headers)  # you asked for inconsistent headers, you have them:
# {'custom_header': 'custom_value', 'typ': 'WeirdJWT', 'kid': 'R@nd0m_KID', 'alg': 'WeirdAlg'}

Jwk as thin wrapper around cryptography keys

Jwk keys are just thin wrappers around keys from the cryptography module, or, in the case of symmetric keys, around bytes. But, unlike cryptographykeys, they present a consistent interface for signature creation/verification, key management, and encryption/decryption, with all available algorithms.

Everywhere a key is required as parameter, you may pass either a raw cryptography key instance, or a Jwk instance (which is actually a thin wrapper around a cryptography key), or a Mapping representing the JWK key.

Jwk are UserDict instances

JWK are specified as JSON objects, which are parsed as dict in Python. The Jwk class in jwskate is actually a UserDict subclass, which is very similar to a standard dict. So you can use it exactly like you would use a dict: you can access its members, dump it back as JSON, etc. The same is true for Signed or Encrypted Json Web tokens in JSON format. However, you cannot change the key cryptographic materials, since that would lead to unusable keys.

Note that the keys with a JwkSet are converted to instances of Jwk on initialization. This may introduce an issue if you try to serialize it to JSON with the standard json module, which does not handle UserDict by default. You may either use JwkSet.to_json() to get a JSON-serialized string, or JwkSet.to_dict() to get a standard dict, that is serializable by the standard json module.

JWA Wrappers

You can use cryptography to do the cryptographic operations that are described in JWA, but since cryptography is a general purpose library, its usage is not straightforward and gives you plenty of options to carefully select and combine, leaving room for mistakes, errors and confusion. It also has a quite inconsistent API to handle the different key types and algorithms. To work around this, jwskate comes with a set of consistent wrappers that implement the exact JWA specifications, with minimum risk of mistakes.

Safe Signature Verification

As advised in JWT Best Practices $3.1:

For every signature verification method in jwskate, the expected signature(s) algorithm(s) must be specified. That is to avoid a security flaw where your application accepts tokens with a weaker encryption scheme than what your security policy mandates; or even worse, where it accepts unsigned tokens, or tokens that are symmetrically signed with an improperly used public key, leaving your application exposed to exploitation by attackers.

To specify which signature algorithms are accepted, each signature verification method accepts, in order of preference:

  • an alg parameter which contains the expected algorithm, or an algs parameter which contains a list of acceptable algorithms
  • the alg parameter from the signature verification Jwk, if present. This alg is the algorithm intended for use with that key.

Note that you cannot use alg and algs at the same time. If your Jwk contains an alg parameter, and you provide an alg or algs which does not match that value, a Warning will be emitted.

TODO

  • Complete/enhance/proof-read documentation
  • Better exceptions (create dedicated exception classes, better messages, etc.)
  • Support for JWE in JSON format
  • Better tests
  • Support for Selective-Disclosure JWT

Credits

All cryptographic operations are handled by cryptography.

FAQs


Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc