Batteries-included, pure-Python cryptography. Zero dependencies.
The primitives you actually reach for — hashing, HMAC, key derivation, password hashing, authenticated encryption, key agreement, and signatures — behind one small, hard-to-misuse import surface, with nothing to compile and nothing to install.
from larzcrypt import ed25519, seal, unseal, hash_password, check_password
# sign & verify
priv, pub = ed25519.generate_keypair()
sig = ed25519.sign(priv, b"ship it")
assert ed25519.verify(pub, b"ship it", sig)
# encrypt to someone's public key (they alone can open it)
blob = seal(their_x25519_pubkey, b"for your eyes only")
# store passwords safely
stored = hash_password("correct horse battery staple")
assert check_password("correct horse battery staple", stored)Everything is verified against the relevant standard's official test vectors.
| area | what | standard |
|---|---|---|
| hashing / HMAC | SHA-2/3, BLAKE2, RIPEMD-160, sha256d, hash160, constant-time compare |
FIPS / RFC 4231 |
| key derivation | HKDF, PBKDF2, scrypt | RFC 5869 |
| password hashing | scrypt → self-describing storable strings | — |
| AEAD | ChaCha20-Poly1305 | RFC 8439 |
| key agreement | X25519 Diffie-Hellman | RFC 7748 |
| signatures | Ed25519; ECDSA over secp256k1 (deterministic RFC 6979) | RFC 8032 / 6979 |
| public-key encryption | anonymous seal/unseal + authenticated Box |
— |
| tokens | CSPRNG tokens, URL-safe base64 | — |
| JWT | HS256/384/512, EdDSA, ES256K — with safe verify | RFC 7519 |
pip install larzcryptZero required dependencies. Optionally pip install larzcrypt[fast] to pull in
coincurve — larzcrypt will auto-use it for secp256k1 only after a self-test
proves it produces byte-identical signatures, so it speeds things up without ever
changing your output.
from larzcrypt import chacha20poly1305 as aead, token_bytes
key, nonce = token_bytes(32), token_bytes(12) # nonce unique per message!
ct = aead.encrypt(key, nonce, b"secret", aad=b"header")
pt = aead.decrypt(key, nonce, ct, aad=b"header") # raises if tamperedfrom larzcrypt import x25519, seal, unseal
priv, pub = x25519.generate_keypair()
blob = seal(pub, b"hello") # anyone can seal to `pub`
unseal(priv, blob) # only `priv` can open it -> b"hello"Or an authenticated two-way channel where each side knows the other:
from larzcrypt import x25519, Box
a_priv, a_pub = x25519.generate_keypair()
b_priv, b_pub = x25519.generate_keypair()
a, b = Box(a_priv, b_pub), Box(b_priv, a_pub)
b.decrypt(a.encrypt(b"ping")) # -> b"ping", and forgery is impossibledecode() takes the expected algorithm as a required argument and ignores
the token's own alg header — so the classic alg:none and RS256→HS256
confusion attacks simply can't happen.
from larzcrypt import jwt, ed25519
priv, pub = ed25519.generate_keypair()
token = jwt.encode({"sub": "42", "exp": 1893456000}, priv, alg="EdDSA")
jwt.decode(token, pub, alg="EdDSA") # {"sub": "42", ...}, or raisesfrom larzcrypt import hash_password, check_password
stored = hash_password("hunter2") # 'scrypt$16384$8$1$<salt>$<hash>'
check_password("hunter2", stored) # True — params are in the stringPure-Python crypto is auditable and dependency-free, and every primitive
here matches its standard's test vectors. It is not, and does not claim to be,
side-channel hardened to the level of a C library like libsodium. Use it freely
for signing, tokens, KDFs, app-level encryption, JWTs, and blockchain keys. For
deployments where an attacker can measure precise timing of your secret-key
operations at scale, pair it with a hardened backend (secp256k1 already
auto-uses coincurve when available).
Don't invent protocols on top of these primitives without knowing what you're
doing — reach for the high-level seal/Box/jwt/hash_password helpers,
which compose them correctly for you.
python -m unittest discover -s tests -v # 37 tests incl. RFC vectors, zero depsPure-Python, zero-dependency building blocks:
- larz — money-native web framework
- larzchain — from-scratch PoW blockchain
- larzmoney — exact, penny-perfect money
- larzcrypt — this library
MIT © larz-scripter