PyHSM Documentation

Complete reference for the PyHSM software Key Management Service. Generate keys, encrypt data, sign messages, manage lifecycles, and audit every operation.

pip install vectorguard-pyhsm

Overview

PyHSM is a production-ready software Key Management Service (KMS) that provides:

  • AES-256-GCM authenticated encryption with Argon2id key derivation
  • 9 key types: AES-128/256, RSA-2048/4096, EC P-256/P-384/P-521/secp256k1, Ed25519
  • Double-encrypted keystore (AES-GCM envelope + AES-KWP per-key wrapping)
  • HMAC-chained tamper-evident audit logs
  • Shamir M-of-N secret sharing over GF(256)
  • Key rotation with version tracking and automatic decryption routing
  • Caller ACLs, rate limiting, and operation caps
  • Process isolation mode (Unix domain socket IPC)
  • Dual implementation: Python and TypeScript with full feature parity
  • Self-test Known-Answer Tests (KATs) on startup

PyHSM is MIT licensed open-source software. It requires no hardware, no cloud dependency, and no per-operation fees.

Installation

From PyPI (Recommended)

pip install vectorguard-pyhsm

From Source

git clone https://github.com/pavondunbar/PyHSM.git
cd PyHSM
pip install .

Development Install

pip install ".[dev]"    # includes pytest + pytest-cov
pip install -e .        # editable mode

Requirements

DependencyVersion
Python≥ 3.11
cryptography≥ 43.0.0, < 48.0.0
argon2-cffi≥ 23.1.0, < 25.0.0

Quickstart

Get up and running in 3 steps:

1. Install

pip install vectorguard-pyhsm

2. Generate a Key and Encrypt

Python
from hsm import PyHSM

with PyHSM("my-keystore.enc", master_password="strong-password-here") as hsm:
    # Generate an AES-256 encryption key
    hsm.generate_key("my-key", key_type="aes-256")

    # Encrypt sensitive data
    ciphertext = hsm.encrypt("my-key", "secret data")
    print(f"Ciphertext: {ciphertext}")

    # Decrypt it back
    plaintext = hsm.decrypt("my-key", ciphertext)
    print(f"Plaintext: {plaintext.decode()}")

3. Or Use the CLI

Terminal
# Generate an AES-256 key
vectorguard-pyhsm --store my-keystore.enc generate my-key --type aes-256

# Encrypt data
vectorguard-pyhsm --store my-keystore.enc encrypt my-key -d "secret data"

# Decrypt data
vectorguard-pyhsm --store my-keystore.enc decrypt my-key -d "<ciphertext-hex>"

Python API — PyHSM Class

The PyHSM class is the primary interface. It manages the keystore, enforces policies, handles auditing, and exposes all cryptographic operations.

Constructor

from hsm import PyHSM

hsm = PyHSM(
    storage_path="keystore.enc",        # Path to encrypted keystore file
    master_password="your-password",    # Required, minimum 12 characters
    audit_log_path="audit.jsonl",       # Optional audit log path
    session_timeout_s=300.0,            # Session idle timeout (seconds)
    rate_limit_max_ops=100,             # Max operations per key per window
    rate_limit_window_s=60.0,           # Rate limit window (seconds)
)

Supports context manager usage for automatic session cleanup:

with PyHSM("keystore.enc", master_password="...") as hsm:
    # operations here
    pass
# Session closed, memory zeroized

Import Paths

# Primary class
from hsm import PyHSM

# Storage and errors
from hsm import KeyStore, TamperError
from hsm.backends import StorageBackend, FileBackend, MemoryBackend

# Security utilities
from hsm import SecureBytes, zeroize_bytearray
from hsm import AuditLog, RateLimiter, MetricsCollector
from hsm import run_self_tests

# Shamir secret sharing
from hsm import split_secret, reconstruct_secret, zeroize

# JWK support
from hsm import export_symmetric_jwk, export_ec_jwk
from hsm import export_rsa_jwk, export_ed25519_jwk

Key Generation

Method Signature

hsm.generate_key(
    key_id: str,                    # Unique identifier for the key
    key_type: str = "aes-256",      # Key type (see Key Types table)
    metadata: dict = None,          # Optional metadata dict
    policy: dict = None,            # Optional policy dict
    caller_id: str = None,          # Caller identity for ACL/audit
) -> str                            # Returns key_id

Examples

Generate keys of different types
# AES-256 for symmetric encryption
hsm.generate_key("encryption-key", key_type="aes-256")

# Ed25519 for fast signing (Solana, SSH)
hsm.generate_key("signing-key", key_type="ed25519")

# secp256k1 for Ethereum/Bitcoin transaction signing
hsm.generate_key("eth-wallet", key_type="ec-secp256k1")

# RSA-4096 for legacy systems
hsm.generate_key("legacy-rsa", key_type="rsa-4096")

# With policy constraints
hsm.generate_key("limited-key", key_type="aes-256", policy={
    "max_operations": 10000,
    "allowed_callers": ["payment-service", "auth-service"],
    "allow_encrypt": True,
    "allow_decrypt": True,
    "expires_at": "2027-01-01T00:00:00Z",
    "rotate_every_days": 90,
})

Other Key Lifecycle Methods

MethodDescription
list_keys()List all stored keys with metadata
has_key(key_id)Check if a key exists
search_keys(key_type=, metadata=, status=, policy_filter=)Search keys by criteria
destroy_key(key_id)Permanently destroy a key (zeroizes material)
enforce_expiry()Archive all expired keys

Encryption & Decryption

AES keys support authenticated encryption using AES-256-GCM with hybrid nonces (random + counter + random) to eliminate birthday-bound collisions.

Method Signatures

hsm.encrypt(
    key_id: str,                    # Key to encrypt with
    plaintext: str | bytes,         # Data to encrypt
    caller_id: str = None,          # Caller identity
) -> str                            # Hex-encoded ciphertext

hsm.decrypt(
    key_id: str,                    # Key to decrypt with
    ciphertext_hex: str,            # Hex ciphertext from encrypt()
    caller_id: str = None,          # Caller identity
) -> bytes                          # Decrypted plaintext

Example

with PyHSM("keystore.enc", master_password="...") as hsm:
    hsm.generate_key("data-key", key_type="aes-256")

    # Encrypt a private key, API token, or any sensitive data
    ciphertext = hsm.encrypt("data-key", "sk_live_abc123xyz")

    # Later, decrypt it
    plaintext = hsm.decrypt("data-key", ciphertext)
    # plaintext = b"sk_live_abc123xyz"
Version routing: The ciphertext includes a version prefix. After key rotation, decrypt() automatically selects the correct key version for each ciphertext.

Signing & Verification

Asymmetric keys (EC, RSA, Ed25519) support digital signatures. The private key never leaves the HSM; you get back only the signature.

Method Signatures

hsm.sign(
    key_id: str,                    # Asymmetric key to sign with
    message: str | bytes,           # Data to sign
    caller_id: str = None,
) -> str                            # Hex-encoded signature

hsm.verify(
    key_id: str,                    # Key to verify with
    message: str | bytes,           # Original message
    signature_hex: str,             # Hex signature from sign()
    caller_id: str = None,
) -> bool                           # True if valid

hsm.get_public_key(key_id: str) -> str  # PEM-encoded public key

Blockchain Transaction Signing

Ethereum transaction signing with secp256k1
with PyHSM("keystore.enc", master_password="...") as hsm:
    # Generate or import a secp256k1 wallet key
    hsm.generate_key("eth-wallet", key_type="ec-secp256k1")

    # Sign a transaction hash (32 bytes)
    tx_hash = bytes.fromhex("a1b2c3...")  # Your tx hash
    signature_hex = hsm.sign("eth-wallet", tx_hash)

    # Parse DER signature into (r, s, v) for broadcasting
    # The private key was unwrapped, used, and zeroized internally

Supported Signing Algorithms

Key TypeAlgorithmHashUse Case
ec-p256ECDSASHA-256General-purpose, TLS
ec-p384ECDSASHA-384Higher security
ec-p521ECDSASHA-512Maximum NIST security
ec-secp256k1ECDSASHA-256Ethereum, Bitcoin
ed25519EdDSABuilt-inSolana, SSH, high-perf
rsa-2048RSA-PSSSHA-256Legacy compatibility
rsa-4096RSA-PSSSHA-256Legacy, high-security

Key Rotation

AES keys support rotation with automatic version tracking. Old ciphertexts remain decryptable because the version number is embedded in the ciphertext format.

Manual Rotation

with PyHSM("keystore.enc", master_password="...") as hsm:
    # Rotate to a new version
    new_version = hsm.rotate_key("data-key")
    # new_version = 2

    # New encryptions use version 2
    ct_new = hsm.encrypt("data-key", "new data")

    # Old ciphertexts still decrypt (version 1 auto-selected)
    plaintext = hsm.decrypt("data-key", old_ciphertext)

Automatic Rotation

Set rotation policy at key creation
hsm.generate_key("auto-rotate-key", key_type="aes-256", policy={
    "rotate_every_days": 90,  # Auto-rotate every 90 days
})
How auto-rotation works: On each encrypt() call, PyHSM checks if the current key version's age exceeds the policy. If so, it rotates before encrypting. This is lazy (on-demand) rotation.

JWK Import & Export

Import existing keys from standard JWK format (RFC 7517) or export keys for interoperability.

Import

# Import an existing EC key from JWK
jwk_data = {
    "kty": "EC",
    "crv": "secp256k1",
    "x": "...",
    "y": "...",
    "d": "..."  # private key component
}
hsm.import_key_jwk("imported-wallet", jwk_data)

Export

# Export requires allow_export policy
hsm.generate_key("exportable-key", key_type="ed25519", policy={
    "allow_export": True,
})

jwk = hsm.export_jwk("exportable-key")
# Returns standard JWK dict
Security: Export is an explicit, audited operation. Keys without allow_export: True in their policy cannot be exported.

CLI Reference

The vectorguard-pyhsm CLI provides full access to all PyHSM operations from the terminal.

Global Options

FlagDefaultDescription
--store PATHkeystore.encPath to the encrypted keystore file

The master password is prompted interactively via getpass, or can be provided via the PYHSM_MASTER_PASSWORD environment variable for scripting.

All Commands

CommandDescription
generate <key_id>Generate a new cryptographic key
listList all keys in the keystore
stores [dir]List keystore files in a directory
rotate <key_id>Rotate an AES key to a new version
delete <key_id>Permanently destroy a key
encrypt <key_id>Encrypt data with an AES key
decrypt <key_id>Decrypt hex ciphertext
sign <key_id>Sign data with an asymmetric key
verify <key_id> <msg> <sig>Verify a signature (exit 0=valid, 1=invalid)
pubkey <key_id>Export the public key in PEM format
splitSplit a secret into Shamir shares
reconstructReconstruct a secret from Shamir shares
metricsShow operational metrics
auditInspect or verify the audit log

CLI: generate

Generate a new cryptographic key inside the keystore.

Usage
vectorguard-pyhsm --store <path> generate <key_id> [options]

Options

FlagDescription
--type TYPEKey type: aes-128, aes-256, rsa-2048, rsa-4096, ec-p256, ec-p384, ec-p521, ec-secp256k1, ed25519
--no-encryptDisallow encryption with this key
--no-decryptDisallow decryption with this key
--max-operations NMaximum total operations allowed
--expires-at ISO8601Key expiration timestamp

Examples

# Generate an AES-256 key
vectorguard-pyhsm generate vault-key --type aes-256

# Generate an Ed25519 signing key with max 5000 operations
vectorguard-pyhsm generate signer --type ed25519 --max-operations 5000

# Generate a key that expires
vectorguard-pyhsm generate temp-key --type aes-256 --expires-at 2027-06-01T00:00:00Z

CLI: encrypt / decrypt

Encrypt
# Encrypt with -d flag
vectorguard-pyhsm encrypt my-key -d "sensitive data"

# Encrypt from stdin
echo "sensitive data" | vectorguard-pyhsm encrypt my-key
Decrypt
# Decrypt hex ciphertext
vectorguard-pyhsm decrypt my-key -d "0200000001f383ad67..."

# Decrypt from stdin
echo "0200000001f383ad67..." | vectorguard-pyhsm decrypt my-key

CLI: sign / verify

Sign a message
vectorguard-pyhsm sign my-signing-key -d "message to sign"
# Output: hex-encoded signature
Verify a signature
vectorguard-pyhsm verify my-signing-key "message to sign" "3045022100..."
# Exit code: 0 = VALID, 1 = INVALID
Export public key
vectorguard-pyhsm pubkey my-signing-key
# Output: PEM-encoded public key

CLI: split / reconstruct

Split a secret into Shamir shares or reconstruct from shares.

Split a secret into 5 shares (3 required to reconstruct)
vectorguard-pyhsm split -k 3 -n 5 -s "64656d6f2d6d61737465722d70617373776f7264"

# Output:
# Share 1: {"index": 1, "data": "d18a992fab..."}
# Share 2: {"index": 2, "data": "0985e4adb3..."}
# Share 3: {"index": 3, "data": "bc6a10ed35..."}
# Share 4: {"index": 4, "data": "ce2a97e318..."}
# Share 5: {"index": 5, "data": "7bc563a39e..."}
Reconstruct from any 3 shares
vectorguard-pyhsm reconstruct \
  --share '{"index": 1, "data": "d18a992fab..."}' \
  --share '{"index": 3, "data": "bc6a10ed35..."}' \
  --share '{"index": 5, "data": "7bc563a39e..."}'
# Output: 64656d6f2d6d61737465722d70617373776f7264

Split Options

FlagDescription
-k, --thresholdMinimum shares needed to reconstruct (2–255)
-n, --sharesTotal number of shares to generate (k–255)
-s, --secretHex-encoded secret to split

CLI: audit / metrics

View audit log
# Show all audit entries
vectorguard-pyhsm audit

# Verify audit log integrity (HMAC chain)
vectorguard-pyhsm audit --verify

# Filter by operation type
vectorguard-pyhsm audit --operation encrypt --since 2026-01-01T00:00:00Z

# Raw JSON output
vectorguard-pyhsm audit --raw
View metrics
# Human-readable metrics
vectorguard-pyhsm metrics

# Prometheus text format
vectorguard-pyhsm metrics --prometheus

Audit Options

FlagDescription
--verifyVerify HMAC chain integrity
--rawOutput raw JSON Lines
--operation TYPEFilter by operation type
--key-id IDFilter by key ID
--since ISO8601Filter entries after this timestamp
--until ISO8601Filter entries before this timestamp

Key Types

PyHSM supports 9 key types covering symmetric encryption, ECDSA signing, EdDSA signing, and RSA signing.

Key TypeCLI NameOperationsUse Case
AES-128aes-128encrypt, decryptLightweight symmetric encryption
AES-256aes-256encrypt, decryptStandard symmetric encryption (default)
RSA-2048rsa-2048sign, verifyLegacy RSA-PSS signatures
RSA-4096rsa-4096sign, verifyHigh-security RSA-PSS signatures
EC P-256ec-p256sign, verifyNIST standard, TLS, general-purpose
EC P-384ec-p384sign, verifyHigher NIST security level
EC P-521ec-p521sign, verifyMaximum NIST security level
EC secp256k1ec-secp256k1sign, verifyEthereum, Bitcoin, blockchain
Ed25519ed25519sign, verifySolana, Cosmos, SSH, high-performance

Access Control

PyHSM provides three layers of access control that are enforced on every operation:

1. Caller ACLs

Restrict which services can use a key:

hsm.generate_key("payment-key", key_type="aes-256", policy={
    "allowed_callers": ["payment-service", "billing-service"],
})

# This succeeds:
hsm.encrypt("payment-key", data, caller_id="payment-service")

# This raises ValueError + logs accessDenied:
hsm.encrypt("payment-key", data, caller_id="unknown-service")

2. Rate Limiting

Sliding-window rate limiting per key prevents abuse:

# Configured at HSM initialization
hsm = PyHSM(
    "keystore.enc",
    master_password="...",
    rate_limit_max_ops=100,     # 100 operations...
    rate_limit_window_s=60.0,   # ...per 60-second window
)

3. Operation Caps

Hard limit on total operations per key (lifetime):

hsm.generate_key("limited-key", key_type="aes-256", policy={
    "max_operations": 10000,  # After 10,000 ops, key is unusable
})

Policy Enforcement Order

  1. Caller ACL check (no side effects)
  2. Operation permission (allow_encrypt, allow_decrypt, allow_sign)
  3. Max operations check
  4. Expiry check
  5. Rate limiter (consumes a token only after all other checks pass)

Audit Log

Every operation is recorded in an HMAC-chained, tamper-evident audit log. Each entry's HMAC is computed over the entry content plus the previous entry's HMAC, forming a cryptographic chain similar to a blockchain.

Configuration

from hsm import AuditLog

audit = AuditLog(
    log_path="pyhsm-audit.jsonl",
    hmac_key=derived_key,              # Derived from master via HKDF
    webhook_url="https://siem.example.com/ingest",  # Optional
    max_bytes=50 * 1024 * 1024,        # 50 MB rotation threshold
    max_rotated_files=10,              # Keep 10 rotated files
)

Tracked Operations

OperationDescription
encryptData encrypted with a key
decryptData decrypted with a key
signMessage signed with a key
verifySignature verified
generateKeyNew key generated
importKeyKey imported from JWK
destroyKeyKey permanently destroyed
rotateKeyKey rotated to new version
exportKeyKey exported as JWK
exportKeyDeniedExport attempt denied by policy
accessDeniedCaller ACL violation
rateLimitedRate limit exceeded
tamperDetectedKeystore integrity violation detected
selfTestPass / selfTestFailStartup self-test results

Verification

# Programmatic verification
result = audit.verify()
# Returns -1 if chain is clean
# Returns sequence number of first corrupted entry otherwise

# CLI verification
vectorguard-pyhsm audit --verify

Webhook Shipping

When configured, each audit entry is POSTed to the webhook URL with an X-PyHSM-Signature HMAC header for authenticity verification.

Shamir Secret Sharing

Split the master password into N shares where any K shares can reconstruct it, but K-1 or fewer reveal zero information. Implemented over GF(256) using the AES irreducible polynomial.

Python API

from hsm import split_secret, reconstruct_secret, zeroize

# Split a secret into 5 shares, threshold of 3
secret = b"my-master-password"
shares = split_secret(secret, k=3, n=5)
# shares = [{"index": 1, "data": "hex..."}, ...]

# Reconstruct from any 3 shares
recovered = reconstruct_secret(shares[:3])
# recovered = bytearray(b"my-master-password")

# Always zeroize after use
zeroize(recovered)

Constraints

  • 2 ≤ k ≤ n ≤ 255
  • Secret must be non-empty
  • Shares from different split ceremonies are not interchangeable
  • K-1 shares reveal mathematically zero information (information-theoretic security)

Production Use

In production, distribute shares to separate custodians in different locations. At startup, gather the threshold number of shares to reconstruct the master password and unlock the HSM.

TypeScript: The TypeScript layer provides splitMasterPassword() and reconstructMasterPassword() helpers, plus support for injecting shares via the PYHSM_SHARES environment variable as comma-separated JSON.

Process Isolation

The highest security mode separates key material into a dedicated OS process communicating over a Unix domain socket. Even a full RCE in your application cannot read the HSM process's memory without a kernel exploit.

Architecture

Process isolation model
┌─────────────────────┐         ┌─────────────────────┐
│  Your Application   │  IPC    │   PyHSM Process     │
│                     │ ──────► │                     │
│  PyHSMClient        │  Unix   │  Keys in memory     │
│  (sends requests)   │  Socket │  Crypto operations  │
│                     │ ◄────── │  Zeroization        │
└─────────────────────┘         └─────────────────────┘

TypeScript Client

import { PyHSMClient } from "pyhsm-ts/client";

const client = new PyHSMClient("/tmp/pyhsm.sock", "my-service");

// All operations go through IPC
const ciphertext = await client.encrypt("data-key", "secret");
const plaintext = await client.decrypt("data-key", ciphertext);
const health = await client.health();
// { status: "ok", uptime: 3600 }

Security Properties

  • Socket permissions restricted to 0600
  • Max IPC message size: 1 MB (prevents memory exhaustion)
  • Caller authentication via HMAC over request payload
  • Graceful shutdown on SIGTERM/SIGINT
  • Your application never sees raw key bytes

Configuration

VariableDefaultDescription
PYHSM_SOCKET_PATH/tmp/pyhsm.sockUnix socket path for IPC
PYHSM_CALLER_SECRETnoneShared HMAC secret for caller auth

Environment Variables

Python

VariableDefaultDescription
PYHSM_MASTER_PASSWORDnoneMaster password for CLI scripting (avoid in production)
PYHSM_LOG_LEVELWARNINGLogging level: DEBUG, INFO, WARNING, ERROR, CRITICAL
PYHSM_ALLOW_PBKDF2_FALLBACK0Set to 1 to allow PBKDF2 fallback (testing only)
PYHSM_AUDIT_HMAC_KEYderivedHex 32-byte audit HMAC key override
PYHSM_AUDIT_WEBHOOKnoneURL for audit event webhook delivery

TypeScript

VariableDefaultDescription
PYHSM_MASTER_PASSWORDnoneRequired (or use PYHSM_SHARES)
PYHSM_SHARESnoneComma-separated Shamir share JSONs for unlock
PYHSM_KEYSTORE_PATH./pyhsm-keystore.encKeystore file location
PYHSM_AUDIT_LOG_PATH<store>.audit.jsonlAudit log file path
PYHSM_BACKUP_DIRnoneBackup directory
PYHSM_SOCKET_PATH/tmp/pyhsm.sockIPC socket path (process isolation)
PYHSM_CALLER_SECRETnoneShared secret for IPC caller auth
PYHSM_SESSION_TIMEOUT_MS300000Session idle timeout (milliseconds)
PYHSM_RATE_LIMIT100Max operations per key per window
PYHSM_RATE_WINDOW_MS60000Rate limit window (milliseconds)
PYHSM_KEY_IDpyhsm-masterDefault key ID for singleton helpers

Key Policies

Policies are set at key generation time and enforce security constraints throughout the key's lifecycle.

Policy FieldTypeDescription
allow_encryptboolAllow encryption operations (default: true)
allow_decryptboolAllow decryption operations (default: true)
allow_signboolAllow signing operations (default: true)
allow_exportboolAllow JWK export (default: false)
max_operationsintMaximum total operations (lifetime cap)
expires_atISO 8601Key expiration timestamp
rotate_every_daysintAuto-rotation interval in days (AES only)
allowed_callerslist[str]Authorized caller IDs (ACL)

Example: Full Policy

hsm.generate_key("production-key", key_type="aes-256", policy={
    "allow_encrypt": True,
    "allow_decrypt": True,
    "allow_export": False,
    "max_operations": 1000000,
    "expires_at": "2028-01-01T00:00:00Z",
    "rotate_every_days": 90,
    "allowed_callers": ["api-gateway", "worker-service"],
})

TypeScript Layer

PyHSM includes a full TypeScript/Node.js implementation with feature parity. It uses AES-256-GCM-SIV (nonce-misuse resistant) via @noble/ciphers.

Installation

cd pyhsm-ts
npm install
npm run build

Usage

TypeScript
import { PyHSM } from "./core";

// Async factory (uses Argon2id)
const hsm = await PyHSM.create({
  keystorePath: "./keystore.enc",
  masterPassword: "strong-password",
  auditLogPath: "./audit.jsonl",
});

// Generate a key
await hsm.generateKey("my-key", { type: "aes-256" });

// Encrypt and decrypt
const ct = await hsm.encrypt("my-key", "secret data");
const pt = await hsm.decrypt("my-key", ct);

// Clean up
hsm.close();

Key Differences from Python

  • Uses AES-256-GCM-SIV (nonce-misuse resistant) instead of AES-256-GCM
  • Async factory pattern: PyHSM.create() for Argon2id, sync constructor for PBKDF2 fallback
  • Deferred persistence with dirty-flag flushing
  • SecureBuffer class for deterministic zeroization
  • Pinned exact dependency versions in package.json