JWS Algorithms
A simple library for signing and verifying messages using common JWS (JSON Web Signature) algorithms without the overhead of a full JWT/JWS library or a lot of setup and reading documentation when you do not need JWT/JWS.
This library provides two enums:
SymmetricAlgorithm: for symmetric algorithms like HMAC, where the same secret is used for signing and verifying.AsymmetricAlgorithm: for asymmetric algorithms like RSA and ECDSA, where a private key is used for signing and a public key is used for verification.
Select the algorithm you want to use from the enum, then call its sign and verify methods, or if you still need a key generate_secret or generate_keypair depending on the algorithm.
Installation
You can install the package via pip:
pip install jws-algorithms
Basic Usage
Symmetric algorithms use shared secrets that are simple random byte strings:
from jws_algorithms import SymmetricAlgorithm
# Our message we want to sign and verify using HMAC-SHA256
message = b"Hello, World!"
# Generate a new secret random bytes
key = SymmetricAlgorithm.HS256.generate_secret()
# Sign the message
signature = SymmetricAlgorithm.HS256.sign(key, message)
# Verify the signature
assert SymmetricAlgorithm.HS256.verify(key, message, signature)
For more security, asymmetric algorithms use a private key to sign messages and a public key to verify signatures:
from jws_algorithms import AsymmetricAlgorithm
# Our message we want to sign and verify using RSA-SHA256
message = b"Hello, World!"
# We need a public and private key pair
public_key, private_key = AsymmetricAlgorithm.RS256.generate_keypair()
# Sign the message with the private key
signature = AsymmetricAlgorithm.RS256.sign(private_key, message)
# Verify the signature with the public key
assert AsymmetricAlgorithm.RS256.verify(public_key, message, signature)
Keys from files
The private keys can also be loaded from files by passing a pathlib.Path object to the loading functions.
from pathlib import Path
from jws_algorithms import AsymmetricAlgorithm
# The message to sign using RSA-SHA256
message = b'Hello, World!'
# Sign with a private key loaded from a file
signature = AsymmetricAlgorithm.RS256.sign(
Path('path/to/private_key.pem'),
message
)
# Verify with a public key loaded from a file
assert AsymmetricAlgorithm.RS256.verify(
Path('path/to/public_key.pem'),
message,
signature
)
From raw text or environment
Keys can also be passed as raw text (often from environment variables) by calling the functions with a str or bytes instead of a Path or compiled representation of the cryptography package.
import os
from jws_algorithms import AsymmetricAlgorithm
# The message to sign using RSA-SHA256
message = b'Hello, World!'
# Sign with a private key loaded from an environment variable
signature = AsymmetricAlgorithm.RS256.sign(os.environ['PRIVATE_KEY'], message)
# Verify with a public key loaded from an environment variable
assert AsymmetricAlgorithm.RS256.verify(os.environ['PUBLIC_KEY'], message, signature)
Encrypted private keys
When loading private keys, you can provide an optional password if the private key is encrypted.
Important: You have to install with all optional dependencies or specifically the encryption extra to use this feature, as it depends on the bcrypt package.
from pathlib import Path
from jws_algorithms import AsymmetricAlgorithm
# Sign with an encrypted private key loaded from a file
signature = AsymmetricAlgorithm.RS256.sign(
Path('path/to/encrypted_private_key.pem'),
b'Hello, World!',
password='my_secret_password'
)
# Public keys are not encrypted, so no password is needed here
assert AsymmetricAlgorithm.RS256.verify(
Path('path/to/public_key.pem'),
b'Hello, World!',
signature
)
Using this enum in your own code
You can use the SymmetricAlgorithm and AsymmetricAlgorithm enums in your own code to select algorithms dynamically. For example, when your client has a signature, they can send the algorithm name along it and you can parse it using the enum:
from jws_algorithms import SymmetricAlgorithm, AsymmetricAlgorithm
def index(request):
alg_name = request.headers.get("X-Signature-Algorithm")
algorithm = SymmetricAlgorithm[alg_name] if alg_name in SymmetricAlgorithm else AsymmetricAlgorithm[alg_name]
message = request.body
signature = request.headers.get("X-Signature")
key = get_key_somehow(alg_name) # Load the key from a database
if not algorithm.verify(key, message, signature):
raise ValueError("Invalid signature")
# Process the request
JWK (JSON Web Key) export
You can export the keys to JWK format according to the algorithm used when calling the to_jwk method on the key object.
It will return a dictionary with the JWK representation of the key, that you then can publish or use in other libraries that support JWK.
from jws_algorithms import AsymmetricAlgorithm
# Generate a new RSA key pair
public_key, private_key = AsymmetricAlgorithm.RS256.generate_keypair()
# Export the public key to JWK format
public_jwk = AsymmetricAlgorithm.RS256.to_jwk(public_key)
As a safety precaution, passing the private key will also yield the public key's JWK representation.
public_jwk = AsymmetricAlgorithm.RS256.to_jwk(private_key)
If you know what you are doing, you can export the private key by explicitly enabling the include_private flag
private_jwk = AsymmetricAlgorithm.RS256.to_jwk(private_key, include_private=True)
How to generate keys
In case you don't have keys yet, here are some examples of how to generate them.
HMAC
Symmetric HMAC-SHA is just some random bytes:
# Using openssl
openssl rand -base64 32 > hmac_secret.key
# Using python
python -c "import os; print(os.urandom(32).hex())" > hmac_secret.key
RSA
Using openssl:
# Generate a 2048-bit RSA private key in PEM format
openssl genpkey -algorithm RSA -out rsa_private.pem -pkeyopt rsa_keygen_bits:2048
# Extract the public key from the private key
openssl rsa -pubout -in rsa_private.pem -out rsa_public.pem
Using ssh-keygen:
# Generate a 2048-bit RSA private key in PEM format
ssh-keygen -t rsa -b 2048 -m PEM -f rsa_private.pem
# Extract the public key from the private key
ssh-keygen -y -f rsa_private.pem > rsa_public.pem
ECDSA
Using openssl:
# Generate a private key for the P-256 curve in PEM format
openssl ecparam -name prime256v1 -genkey -noout -out ecdsa_private.pem
# Extract the public key from the private key
openssl ec -in ecdsa_private.pem -pubout -out ecdsa_public.pem
Using ssh-keygen:
# Generate a private key for the P-256 curve in PEM format
ssh-keygen -t ecdsa -b 256 -m PEM -f ecdsa_private.pem
# Extract the public key from the private key
ssh-keygen -y -f ecdsa_private.pem > ecdsa_public.pem
EdDSA (Ed25519)
Using openssl:
# Generate an Ed25519 private key in PEM format
openssl genpkey -algorithm ED25519 -out ed25519_private.pem
# Extract the public key from the private key
openssl pkey -in ed25519_private.pem -pubout -out ed25519_public.pem
Using ssh-keygen:
# Generate an Ed25519 private key in PEM format
ssh-keygen -t ed25519 -m PEM -f ed25519_private.pem
# Extract the public key from the private key
ssh-keygen -y -f ed25519_private.pem > ed25519_public.pem
Metadata
Release files for jws-algorithms 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jws_algorithms-1.3.0.tar.gz | 13.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jws_algorithms-1.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 27.7 kB
Release files / jws_algorithms-1.3.0.tar.gz
| Download URL | jws_algorithms-1.3.0.tar.gz |
|---|---|
| Size | 13.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a0b66a4780c377a657a796bbff9979009a09b19c2aa79a8f5173210d5c2f4c2a
|
|
BLAKE2b-256 checksum How to use checksums |
e8d03c9c5ccaf95998549d40d6ad30283bfdad6de443df8118bbace664d2f777
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 27, 2026.
Transparency logRelease files / jws_algorithms-1.3.0-py3-none-any.whl
| Download URL | jws_algorithms-1.3.0-py3-none-any.whl |
|---|---|
| Size | 14.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5ac3a0fb8ff095021e32afee0f1173ba4a93c3a5a05ab30f41610da651c5e03e
|
|
BLAKE2b-256 checksum How to use checksums |
ab11860fe46a481ec6b88acd5451f37aa494f604665b99484eae85e1d17e83fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 27, 2026.
Transparency log