HSM-backed cryptographic keys compatible with Python cryptography library
Project description
HSMKey
HSM-backed cryptographic keys compatible with Python's cryptography and jwcrypto library.
Documentation: https://hsmkey.readthedocs.io
Overview
HSMKey provides key implementations that perform all cryptographic operations on a Hardware Security Module (HSM) via PKCS#11. Private keys never leave the HSM - signing, decryption, and other private key operations are executed on the hardware.
Features
- Drop-in replacement - Keys implement
cryptographylibrary interfaces - Secure by design - Private keys cannot be exported from HSM
- Full algorithm support:
- RSA (2048, 3072, 4096 bits) - PKCS#1 v1.5 and PSS signing, OAEP decryption
- ECDSA (P-256, P-384, P-521)
- EdDSA (Ed25519, Ed448)
- Thread-safe session management - Connection pooling with proper cleanup
Installation
python3 -m pip install hsmkey
Or with uv:
uv add hsmkey
Quick Start
from hsmkey import SessionPool, PKCS11RSAPrivateKey
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
# Create session pool
pool = SessionPool(
module_path="/usr/lib/softhsm/libsofthsm2.so",
token_label="my-token",
user_pin="123456",
)
# Use session to access keys
with pool.session() as session:
# Load RSA key by label
key = PKCS11RSAPrivateKey(session, key_label="my-rsa-key")
# Sign data (signing happens on HSM)
signature = key.sign(
b"data to sign",
padding.PKCS1v15(),
hashes.SHA256(),
)
# Get public key for verification
public_key = key.public_key()
public_key.verify(signature, b"data to sign", padding.PKCS1v15(), hashes.SHA256())
Supported Key Types
RSA Keys
from hsmkey import PKCS11RSAPrivateKey
from cryptography.hazmat.primitives.asymmetric import padding
with pool.session() as session:
key = PKCS11RSAPrivateKey(session, key_label="rsa-2048")
# PKCS#1 v1.5 signing (RS256, RS384, RS512)
sig = key.sign(data, padding.PKCS1v15(), hashes.SHA256())
# PSS signing (PS256, PS384, PS512)
pss = padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.AUTO)
sig = key.sign(data, pss, hashes.SHA256())
# OAEP decryption
oaep = padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None)
plaintext = key.decrypt(ciphertext, oaep)
ECDSA Keys
from hsmkey import PKCS11EllipticCurvePrivateKey
from cryptography.hazmat.primitives.asymmetric import ec
with pool.session() as session:
key = PKCS11EllipticCurvePrivateKey(session, key_label="ec-p256")
# Sign with ECDSA (ES256, ES384, ES512)
signature = key.sign(data, ec.ECDSA(hashes.SHA256()))
# Verify
key.public_key().verify(signature, data, ec.ECDSA(hashes.SHA256()))
EdDSA Keys
from hsmkey import PKCS11Ed25519PrivateKey, PKCS11Ed448PrivateKey
with pool.session() as session:
# Ed25519
key = PKCS11Ed25519PrivateKey(session, key_label="ed25519")
signature = key.sign(data) # 64-byte signature
# Ed448
key = PKCS11Ed448PrivateKey(session, key_label="ed448")
signature = key.sign(data) # 114-byte signature
Key Lookup
Keys can be found by ID or label:
# By label
key = PKCS11RSAPrivateKey(session, key_label="my-key")
# By ID
key = PKCS11RSAPrivateKey(session, key_id=bytes([0x01]))
# By both (must match)
key = PKCS11RSAPrivateKey(session, key_id=bytes([0x01]), key_label="my-key")
Security
Private key material is protected:
key = PKCS11RSAPrivateKey(session, key_label="my-key")
# These operations raise HSMUnsupportedError:
key.private_numbers() # Cannot extract private numbers
key.private_bytes(...) # Cannot export private key
Public keys can be extracted and serialized:
public_key = key.public_key()
pem = public_key.public_bytes(
serialization.Encoding.PEM,
serialization.PublicFormat.SubjectPublicKeyInfo,
)
Development
Prerequisites
- SoftHSM2 installed (for testing).
- kryoptic built in
~/kryoptic(for testing). - OpenSSL for key generation
- just command runner
Setup
# Clone and install
git clone https://github.com/kushaldas/hsmkey
cd hsmkey
uv sync --all-extras
# Generate test keys and set up HSM
just setup
Running Tests
# Run all tests
just test
# Run with coverage
just test-cov
Available Commands
just # List all commands
just recreate-keys # Generate test keys on disk
just init-hsm # Initialize SoftHSM2 token
just import-keys # Import keys to HSM
just setup # Full setup
just test # Run tests
just list-keys # List keys in HSM
just reset # Reset everything
Documentation
Full documentation is available at https://hsmkey.readthedocs.io, including:
- Installation Guide
- Quick Start
- Key Classes Reference
- JWCrypto Integration
- Session Management
- API Reference
License
BSD-2-Clause
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hsmkey-0.2.0.tar.gz.
File metadata
- Download URL: hsmkey-0.2.0.tar.gz
- Upload date:
- Size: 130.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
30de3861727a189aaa80ae56541bc7fbd167d6d976a5f86ed40c890425fbc02c
|
|
| MD5 |
0678f947781acc35900802785b09ff3a
|
|
| BLAKE2b-256 |
3722ecd87cda7594c27a6709b9fe74c91d107ee525c4d7e8dbdf59db171e4426
|
File details
Details for the file hsmkey-0.2.0-py3-none-any.whl.
File metadata
- Download URL: hsmkey-0.2.0-py3-none-any.whl
- Upload date:
- Size: 26.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b320b246e7619424005afb8a437f70fc9e4665ec854e2492e442f43ec883c62
|
|
| MD5 |
5451101dd6610a8ff81eec7bbe8bb8bf
|
|
| BLAKE2b-256 |
b5825f38c6b5071ff19dbb03e91c5bcdc26285f4c5066b021ff144387d9f22f6
|