KarpelesLab/purecrypto
GitHub: KarpelesLab/purecrypto
Stars: 0 | Forks: 0
# purecrypto
[](https://github.com/KarpelesLab/purecrypto/actions/workflows/ci.yml)
[](https://crates.io/crates/purecrypto)
[](https://docs.rs/purecrypto)
[](LICENSE)
A cryptography toolkit written **entirely in Rust**, depending on no foreign
code. `purecrypto` is built from the ground up — starting at constant-time
primitives and working up through hashing, ciphers, bignum arithmetic, the
classical and post-quantum asymmetric stacks, ASN.1, X.509 and TLS — and is
usable three ways:
- as a **Rust library**,
- as a **C library** (`cdylib` with a C ABI), and
- as a **standalone command-line tool** (`purecrypto`: hashing, randomness, key
generation including PQ, CSRs, a small CA, a TLS 1.3 test client, …).
## Design principles
- **No foreign code.** No C, no assembly pulled from other libraries, and no
third-party crypto crates. Everything is implemented here, in Rust.
- **Constant time by default.** Secret-dependent values flow through the
[`ct`](src/ct) layer (branchless equality, selection, ordering) so higher
layers avoid timing side channels. Where an algorithm is intrinsically
non-constant-time (RSA keygen, modular inverse), it's used only on
one-time/key-generation paths and documented as such.
- **`no_std` core.** The crate is `#![no_std]`; `alloc` and `std` are opt-in
features (`std` is the default and implies `alloc`).
- **Validated.** Where a standard publishes test vectors we run them — RFC
8439, RFC 8032, RFC 8448, FIPS 203/204/205 ACVP — and cross-check the X.509
/ TLS / PQC stacks against OpenSSL 3.5.
## Layout
Single crate, modules gated by Cargo features:
| Layer | Module | Status |
| ---------------- | ----------- | ------ |
| Constant-time | `ct` | ✅ implemented |
| Hashing | `hash` | ✅ SHA-2, SHA-3 + Keccak-256, SHAKE/cSHAKE/KMAC/TupleHash/ParallelHash, TurboSHAKE/KangarooTwelve, BLAKE2b/2s (+keyed/X), BLAKE3, SM3, MD4/MD5/SHA-1/RIPEMD-160; HMAC + `Mac` trait (constant-time verify, drop-zeroizing). Ascon-Hash256/XOF128/CXOF128 live in `ascon` |
| Randomness | `rng` | ✅ RngCore/CryptoRng, HMAC-DRBG (NIST SP 800-90A), OsRng (Unix + Windows) |
| Symmetric cipher | `cipher` | ✅ AES-128/192/256 (constant-time, table-free); SM4 (GB/T 32907); CBC/CFB/OFB/CTR; GCM, CCM, ChaCha20-Poly1305, XChaCha20-Poly1305, AEGIS-128L/256, AES-GCM-SIV (RFC 8452) and AES-SIV (RFC 5297, nonce-misuse-resistant) (AEAD); XTS (disk encryption); AES-KW + AES-KWP (RFC 3394 / 5649); DES + 3-DES (EDE3 / EDE2) with `Cbc64` for legacy interop. Ascon-AEAD128 lives in `ascon` |
| MAC | `mac` | ✅ AES-CMAC (RFC 4493); GMAC (NIST SP 800-38D); UMAC-64 / UMAC-128 (RFC 4418); HMAC lives in `hash` |
| Bignum (CT) | `bignum` | ✅ `Uint` and runtime-sized `BoxedUint`, widening mul, Montgomery modular arith, modexp, Fermat & extended-Euclid inverse |
| Asymmetric keys | `rsa` | ✅ RSA keygen (compile-time + runtime, 512–65536 bits), raw, PKCS#1 v1.5 enc/sign, OAEP enc, PSS sign/verify, PKCS#1 DER/PEM |
| Key derivation | `kdf` | ✅ PBKDF2, HKDF, scrypt (RFC 7914), Argon2id/2d/2i (RFC 9106), SP 800-108 KBKDF (counter + feedback, HMAC/CMAC PRF) |
| Elliptic curve | `ec` | ✅ ECDSA/ECDH on P-256/P-384/P-521/secp256k1 (runtime multi-curve) + fast const-generic P-256, X25519, Ed25519 (EdDSA, RFC 8032), X448 (RFC 7748), Ed448 (EdDSA, RFC 8032), SM2 signature + encryption (GB/T 32918 / RFC 8998) |
| Post-quantum KEM | `mlkem` | ✅ ML-KEM-512 / 768 / 1024 (FIPS 203), `no_std`/no-alloc; OpenSSL-interop on -768 |
| Post-quantum sig | `mldsa` | ✅ ML-DSA-44/65/87 (FIPS 204); hedged + deterministic; FIPS 204 ACVP + OpenSSL-interop |
| Post-quantum sig | `slhdsa` | ✅ SLH-DSA, all 12 sets (FIPS 205, SHA-2/SHAKE × 128/192/256 × s/f); FIPS 205 ACVP + OpenSSL-interop |
| Stateful HBS | `lms` | ✅ LMS / HSS (RFC 8554, NIST SP 800-208); LM-OTS W{1,2,4,8} × LMS H{5,10,15,20,25}; **stateful** advancing key; RFC 8554 KATs |
| Stateful HBS | `xmss` | ✅ XMSS / XMSS^MT (RFC 8391, NIST SP 800-208); **stateful** advancing key; RFC 8391 / reference KATs |
| Lightweight | `ascon` | ✅ Ascon (NIST SP 800-232): Ascon-AEAD128 + Ascon-Hash256 / XOF128 / CXOF128 from one 320-bit permutation |
| Diffie-Hellman | `dh` | ✅ Finite-field DH over RFC 3526 MODP groups (group14..group18) + RFC 4419 group-exchange, for SSH / legacy TLS / IKE interop (new code: ECDH in `ec`) |
| ASN.1 / DER | `der` | ✅ DER reader/writer, base64, PEM |
| X.509 | `x509` | ✅ self-signed + CA issuance (RSA, ECDSA, Ed25519 & Ed448), PKCS#10 CSRs, parse, verify; PKIX SPKI; RFC 5280 nameConstraints enforcement across the chain; OpenSSL-interop |
| TLS | `tls` | ✅ TLS 1.2 and 1.3, DTLS 1.2 and 1.3 client + server (sans-I/O core + blocking `Stream`); x25519/secp256r1 + X25519MLKEM768 hybrid (1.3); AES-GCM & ChaCha20-Poly1305; Ed25519/Ed448/ECDSA/RSA auth; ALPN, record_size_limit (RFC 8449), TLS-Exporter (RFC 5705); PSK session resumption + 0-RTT (early_data) with an anti-replay window (1.3); RFC 5077 session tickets (1.2); mTLS / client certificate authentication; HelloRetryRequest (client + server); bidirectional KeyUpdate; RFC 8448 KATs; DTLS HelloVerifyRequest / cookie DoS guard, handshake fragmentation + reassembly, 64-bit sliding-window anti-replay; DTLS 1.3 encrypted sequence numbers + ACK-driven retransmission. |
| HPKE | `hpke` | ✅ RFC 9180 hybrid public-key encryption — 4 KEMs × 3 KDFs × 3 AEADs + ExportOnly, all four modes (Base/PSK/Auth/AuthPSK) |
| ECH | `ech` | ✅ draft-ietf-tls-esni-22 Encrypted Client Hello — client + server, retry_configs, HRR confirmation signal, bit-shape GREASE |
| QUIC | `quic` | ✅ QUIC v1 (RFC 9000) + QUIC-TLS (RFC 9001) + recovery / congestion (RFC 9002) + DATAGRAM extension (RFC 9221), sans-I/O |
| Cert compression | `cert-compression` | ✅ RFC 8879 TLS 1.3 certificate compression (zlib via the `compcol` sibling crate) |
| C ABI | `ffi` | ✅ hashing/HMAC + AES-CMAC + GMAC, KBKDF, RNG, AEAD (incl. AEGIS, Ascon) + AES-KW, RSA, ECDSA, Ed25519, Ed448, X25519, X448, SM2, ML-KEM, ML-DSA, SLH-DSA, LMS/XMSS, X.509, TLS / DTLS (sans-I/O); opaque handles + caller buffers; `include/purecrypto.h` |
| CLI | (binary) | ✅ `hash`, `rand`, `genpkey` (classical + PQ), `pkey`, `req`, `x509` (CA), `s_client`, `s_server`, `s_dtls_client`, `s_dtls_server` |
## CLI + C-API coverage matrix
Each functional area below is callable from the Rust library, the
`purecrypto` CLI, and the C ABI (`include/purecrypto.h`).
| Area | CLI | C API |
| ------------------------------------- | ------------------------------------ | ------------------------------------------------------------------ |
| Hashing (SHA-2/3, BLAKE2/3, SM3, Ascon, …) | `hash` | `pc_digest`, `pc_hash_*`, `pc_ascon_xof`/`pc_ascon_cxof` |
| HMAC (SHA-1, SHA-2, SHA-3, SM3, …) | `mac` | `pc_hmac` |
| AES-CMAC (RFC 4493) | `mac -alg cmac` | `pc_cmac` |
| GMAC (NIST SP 800-38D) | `mac -alg gmac -nonce …` | `pc_gmac` |
| KDFs (HKDF, PBKDF2, scrypt, Argon2) | `kdf hkdf\|pbkdf2\|scrypt\|argon2` | `pc_hkdf`, `pc_pbkdf2`, `pc_scrypt`, `pc_argon2` |
| KBKDF (SP 800-108, counter/feedback) | `kdf kbkdf` | `pc_kbkdf_counter`, `pc_kbkdf_feedback` |
| AEAD (AES-GCM/CCM, ChaCha20-Poly1305, XChaCha20-Poly1305, AES-GCM-SIV, AES-SIV, AEGIS-128L/256, Ascon-AEAD128) | `enc` | `pc_aead_encrypt`, `pc_aead_decrypt` |
| AES key wrap (RFC 3394/5649) | `enc -alg AES-KW\|AES-KWP` | `pc_aes_kw_wrap/unwrap`, `pc_aes_kwp_wrap/unwrap` |
| Randomness | `rand` | `pc_rand_bytes` |
| RSA keygen + PKCS#1 sign/verify | `genpkey`, `req`, `x509`, `pkeyutl` | `pc_rsa_generate`, `pc_rsa_sign_pkcs1`, `pc_rsa_verify_pkcs1` |
| RSA-PSS sign/verify | `pkeyutl sign/verify -pkeyopt pss` | `pc_rsa_sign_pss`, `pc_rsa_verify_pss` |
| RSA-OAEP encrypt/decrypt | `pkeyutl encrypt/decrypt -pkeyopt oaep` | `pc_rsa_encrypt_oaep`, `pc_rsa_decrypt_oaep` |
| ECDSA keygen + sign/verify | `genpkey -alg EC`, `pkeyutl` | `pc_ec_generate`, `pc_ec_sign`, `pc_ec_verify` |
| Ed25519 sign/verify | `genpkey -alg ED25519`, `pkeyutl` | `pc_ed25519_*` |
| Ed448 sign/verify | `genpkey -alg ED448`, `pkeyutl` | `pc_ed448_*` |
| SM2 sign/verify + encrypt/decrypt | `genpkey -alg SM2`, `pkeyutl` | `pc_sm2_*` |
| ECDH on NIST curves | `kex -alg ECDH-P{256,384,521}` | `pc_ecdh` |
| X25519 | `kex -alg X25519` | `pc_x25519`, `pc_x25519_public` |
| X448 | `kex -alg X448` | `pc_x448`, `pc_x448_public` |
| ML-KEM (FIPS 203) | `kem keygen\|encaps\|decaps` | `pc_mlkem_*` |
| ML-DSA (FIPS 204) | `pkeyutl sign/verify` (ML-DSA keys) | `pc_mldsa_*` |
| SLH-DSA (FIPS 205) | `pkeyutl sign/verify` (SLH-DSA keys) | `pc_slhdsa_*` |
| LMS / HSS (RFC 8554, stateful) | `genpkey -alg LMS-…\|HSS-…`, `pkeyutl` | `pc_lms_*`, `pc_hss_*` |
| XMSS / XMSS^MT (RFC 8391, stateful) | `genpkey -alg XMSS-…\|XMSSMT-…`, `pkeyutl` | `pc_xmss_*`, `pc_xmssmt_*` |
| CSR (PKCS#10) | `req` | `pc_csr_create_rsa`, `pc_csr_from_pem`, `pc_csr_verify_self_signed`|
| X.509 certificate parse + verify | `x509`, `ca` | `pc_cert_*`, `pc_ec_self_signed_pem` |
| CRL parse + verify | `crl` | `pc_crl_*` |
| TLS 1.2 / 1.3 client + server | `s_client`, `s_server` | `pc_tls_cfg_*`, `pc_tls_*` (memory-BIO style) |
| DTLS 1.2 / 1.3 client + server | `s_dtls_client`, `s_dtls_server` | `pc_tls_cfg_*` (`PC_DTLS_1_*` selector), `pc_dtls_next_timeout/on_timeout` |
The C ABI is sans-I/O for TLS/DTLS: the caller pumps wire bytes through
`pc_tls_feed` / `pc_tls_pop` and application bytes through `pc_tls_send` /
`pc_tls_recv` (mirrors OpenSSL's `BIO_s_mem`).
## Cargo features
Default is `std + cli` with every module on. Disable defaults for a `no_std`
build and re-enable only what you need:
# Bare no_std, no allocator: just `ct` and primitives that fit.
purecrypto = { version = "0.3", default-features = false }
# no_std core + ML-KEM-768 (no alloc):
purecrypto = { version = "0.3", default-features = false, features = ["mlkem"] }
# Library with PQ signing only:
purecrypto = { version = "0.3", default-features = false, features = ["mldsa", "slhdsa"] }
Module gates: `hash`, `cipher`, `mac`, `kdf`, `bignum`, `rng`,
`linux-getrandom`, `rsa`, `dh`, `der`, `ec`, `x509`, `tls`, `dtls`, `quic`,
`mlkem`, `mldsa`, `slhdsa`, `hpke`, `ech`, `cert-compression`, `ffi`, `cli`.
Each pulls in only its own dependencies. `alloc` is required by anything that
needs heap (most things except `ct`, `hash`, `cipher`, and the no-alloc
`mlkem` core).
## Building
cargo build # default: std + CLI binary
cargo build --no-default-features # bare no_std
cargo build --no-default-features --features alloc # no_std + alloc
cargo test # full suite
cargo test --release -- --ignored # heavy KATs (SLH-DSA 's' sets, RSA keygen)
Requires Rust 1.95+ (edition 2024).
## Command-line tool
The `purecrypto` binary (built by default; or `cargo build --features cli`).
Every subcommand reads `stdin` when no `-in` is given and writes to `stdout`
when no `-out` is given, so commands compose with pipes.
### `hash` — message digests
purecrypto hash sha256 file.txt # one-shot digest
echo -n abc | purecrypto hash sha3-256 # any algorithm from the `hash` module
Algorithms: `sha224`, `sha256`, `sha384`, `sha512`, `sha512-224`, `sha512-256`,
`sha3-224`, `sha3-256`, `sha3-384`, `sha3-512`, `keccak256`, `blake2b256`,
`blake2b384`, `blake2b512`, `blake2s256`, `blake3`, `sm3`, `sha1`, `md5`,
`ripemd160`. (The XOFs `shake128`/`shake256` and the BLAKE2X/cSHAKE/KMAC
variants are exposed through the Rust library, not the CLI.)
### `rand` — randomness
purecrypto rand 32 # 32 random bytes as hex
purecrypto rand 16 --binary # raw bytes to stdout
### `genpkey` — key generation (classical and post-quantum)
# Classical
purecrypto genpkey -algorithm RSA -bits 2048 -out rsa.pem # also 3072, 4096
purecrypto genpkey -algorithm RSA -bits 8192 -out rsa8k.pem # any even size, 512..=65536
purecrypto genpkey -algorithm EC -curve P-256 -out ec.pem # or P-384, P-521, secp256k1
purecrypto genpkey -algorithm ED25519 -out ed.pem
# Post-quantum signatures (FIPS 204 / FIPS 205)
purecrypto genpkey -algorithm ML-DSA-44 -out mldsa44.pem
purecrypto genpkey -algorithm ML-DSA-65 -out mldsa65.pem
purecrypto genpkey -algorithm ML-DSA-87 -out mldsa87.pem
purecrypto genpkey -algorithm SLH-DSA-SHA2-128f -out slh128f.pem
purecrypto genpkey -algorithm SLH-DSA-SHAKE-256s -out slh256s.pem
# Post-quantum KEM (FIPS 203) — all three security levels
purecrypto genpkey -algorithm ML-KEM-512 -out mlkem512.pem
purecrypto genpkey -algorithm ML-KEM-768 -out mlkem768.pem
purecrypto genpkey -algorithm ML-KEM-1024 -out mlkem1024.pem
The full SLH-DSA matrix is supported:
`SLH-DSA-{SHA2,SHAKE}-{128,192,256}{s,f}` (12 parameter sets).
Output format:
- RSA → `-----BEGIN RSA PRIVATE KEY-----` (PKCS#1)
- EC → `-----BEGIN EC PRIVATE KEY-----` (SEC1)
- Ed25519 / ML-DSA / ML-KEM / SLH-DSA → `-----BEGIN PRIVATE KEY-----` (PKCS#8,
algorithm identified by the embedded OID)
### `pkey` — inspect or convert a key
purecrypto pkey -in key.pem -text # describe the key
purecrypto pkey -in key.pem -pubout # emit the SPKI public-key PEM
purecrypto pkey < key.pem # re-emit the private key (round-trip)
`pkey` auto-detects every supported flavor (RSA PKCS#1, EC SEC1, and the PKCS#8
types above) and routes by the embedded OID for PKCS#8 inputs.
### `req` — PKCS#10 certificate signing requests
purecrypto req -key leaf.pem -subj "/CN=leaf.example/O=Acme" \
-addext "subjectAltName=DNS:leaf.example,DNS:www.leaf.example" \
-out leaf.csr
purecrypto req -in leaf.csr -verify # check the CSR self-signature
### `x509` — self-signed certificates and a small CA
# Build a self-signed CA cert
purecrypto x509 -new --ca -key ca.pem -subj "/CN=Internal CA" -out ca.crt
# Issue a leaf certificate from a CSR
purecrypto x509 -req -in leaf.csr -CA ca.crt -CAkey ca.pem -out leaf.crt
# Inspect a certificate
purecrypto x509 -in leaf.crt -text
### `s_client` — TLS 1.3 test client
purecrypto s_client -connect example.com:443
purecrypto s_client -connect 127.0.0.1:8443 -CAfile ca.crt -servername leaf.example
purecrypto s_client -connect 127.0.0.1:8443 -insecure -quiet # skip cert verify, stdin → server
# Negotiate HTTP/2 (or fall back to http/1.1) via ALPN
purecrypto s_client -connect example.com:443 -alpn h2,http/1.1
# Dump the negotiated secrets in NSS SSLKEYLOGFILE format — Wireshark can
# then decrypt the captured pcap.
purecrypto s_client -connect example.com:443 -keylogfile sslkeys.log
# Present a client certificate (mTLS). The key may be Ed25519 (PKCS#8) or
# ECDSA (SEC1).
purecrypto s_client -connect server:443 -cert client.pem -key client.key
The client offers `X25519MLKEM768` (post-quantum hybrid) first, then `x25519`
and `secp256r1`; all three TLS 1.3 cipher suites
(`TLS_AES_128_GCM_SHA256`, `TLS_AES_256_GCM_SHA384`,
`TLS_CHACHA20_POLY1305_SHA256`); and Ed25519, Ed448, ECDSA, and RSA peer signatures.
### `s_server` — TLS 1.3 echo / `-www` server
A one-shot test server: it binds, accepts one connection, performs the
handshake, exchanges data, and exits.
# Plain TLS echo:
purecrypto s_server -cert server.pem -key server.key -accept 4433
# Serve a fixed HTTP response (text/plain) for one request:
purecrypto s_server -cert server.pem -key server.key -accept 4433 -www
# Negotiate ALPN, listen on 8443:
purecrypto s_server -cert server.pem -key server.key -accept 8443 -alpn h2,http/1.1
# mTLS: require + verify a client cert against the bundle in `client-ca.pem`.
purecrypto s_server -cert server.pem -key server.key -accept 8443 \
-Verify client-ca.pem
### TLS 1.2
`s_client` / `s_server` default to TLS 1.3. Pass `-tls1_2` on either side
to force TLS 1.2. The TLS 1.2 path is ECDHE-AEAD only (AES-GCM and
ChaCha20-Poly1305) and supports mTLS plus RFC 5077 session tickets.
# Server (TLS 1.2)
purecrypto s_server -tls1_2 -accept 0.0.0.0:4443 -cert cert.pem -key key.pem
# Client (TLS 1.2)
purecrypto s_client -tls1_2 -connect example.com:443 -CAfile roots.pem
### DTLS — `s_dtls_client` / `s_dtls_server`
DTLS runs the TLS handshake over UDP. Either use the dedicated
`s_dtls_client` / `s_dtls_server` binaries, or pass `-dtls1_2` / `-dtls1_3`
to `s_client` / `s_server`. The two forms are equivalent.
# DTLS 1.2 echo
purecrypto s_dtls_server -dtls1_2 -accept 0.0.0.0:5684 -cert cert.pem -key key.pem
purecrypto s_dtls_client -dtls1_2 -connect localhost:5684
# DTLS 1.3 echo
purecrypto s_dtls_server -dtls1_3 -accept 0.0.0.0:5685 -cert cert.pem -key key.pem
purecrypto s_dtls_client -dtls1_3 -connect localhost:5685
# Equivalent via s_client / s_server with version flags
purecrypto s_server -dtls1_3 -accept 0.0.0.0:5685 -cert cert.pem -key key.pem
purecrypto s_client -dtls1_3 -connect localhost:5685
The DTLS server stands up a HelloVerifyRequest cookie exchange (1.2) or
HelloRetryRequest cookie (1.3) before allocating any per-connection
state, and both directions install a 64-bit sliding-window replay
filter once the handshake-protected keys are in place. The default
record size is 1200 bytes to stay below common path MTUs; override with
`-mtu`.
### Cookbook
End-to-end CA + leaf with EC keys:
purecrypto genpkey -algorithm EC -curve P-256 -out ca.pem
purecrypto x509 -new --ca -key ca.pem -subj "/CN=My CA" -out ca.crt
purecrypto genpkey -algorithm EC -curve P-256 -out leaf.pem
purecrypto req -key leaf.pem -subj "/CN=leaf.example" \
-addext "subjectAltName=DNS:leaf.example" -out leaf.csr
purecrypto x509 -req -in leaf.csr -CA ca.crt -CAkey ca.pem -out leaf.crt
A post-quantum signature key and its public counterpart:
purecrypto genpkey -algorithm ML-DSA-65 -out mldsa.pem
purecrypto pkey -in mldsa.pem -text # ML-DSA-65 private key
purecrypto pkey -in mldsa.pem -pubout > mldsa.pub.pem # PKIX SPKI
A two-process mTLS handshake on a single host (client cert presented to the
server, both keys Ed25519):
# CA + server cert + client cert
purecrypto genpkey -algorithm ED25519 -out ca.pem
purecrypto x509 -new --ca -key ca.pem -subj "/CN=Local CA" -out ca.crt
purecrypto genpkey -algorithm ED25519 -out server.pem
purecrypto req -key server.pem -subj "/CN=127.0.0.1" \
-addext "subjectAltName=DNS:127.0.0.1" -out server.csr
purecrypto x509 -req -in server.csr -CA ca.crt -CAkey ca.pem -out server.crt
purecrypto genpkey -algorithm ED25519 -out client.pem
purecrypto req -key client.pem -subj "/CN=alice" -out client.csr
purecrypto x509 -req -in client.csr -CA ca.crt -CAkey ca.pem -out client.crt
# In one terminal — server requires + verifies client certs against ca.crt:
purecrypto s_server -cert server.crt -key server.pem -accept 8443 -Verify ca.crt -www
# In another terminal — client presents its cert + key:
purecrypto s_client -connect 127.0.0.1:8443 -CAfile ca.crt \
-cert client.crt -key client.pem -alpn http/1.1 \
-keylogfile keys.log
## Library usage
Idiomatic Rust API — see [docs.rs/purecrypto](https://docs.rs/purecrypto) for
the full reference. A few common patterns:
use purecrypto::hash::{Digest, Sha256};
let d = Sha256::digest(b"abc");
use purecrypto::ec::Ed25519PrivateKey;
use purecrypto::rng::OsRng;
let sk = Ed25519PrivateKey::generate(&mut OsRng);
let sig = sk.sign(b"hello");
sk.public_key().verify(b"hello", &sig).unwrap();
use purecrypto::mldsa::MlDsa65PrivateKey;
let (sk, pk) = MlDsa65PrivateKey::generate(&mut OsRng);
let sig = sk.sign(&mut OsRng, b"hello", b"").unwrap();
assert!(pk.verify(&sig, b"hello", b""));
use purecrypto::mlkem::MlKem768DecapsKey;
let (dk, ek) = MlKem768DecapsKey::generate(&mut OsRng);
let (ct, ss_a) = ek.encapsulate(&mut OsRng);
let ss_b = dk.decapsulate(&ct);
assert_eq!(ss_a, ss_b);
### Versions and transports
`purecrypto` ships both TLS (TCP) and DTLS (UDP) at two protocol
versions each:
All four versions (TLS 1.2, TLS 1.3, DTLS 1.2, DTLS 1.3) and both roles
(client, server) share **one** public API: [`tls::Config`] +
[`tls::Connection`]. The version is selected by
`Config::builder().versions(min, max).build()`; the role is selected at
connection-construction time via `Connection::client(&cfg)` or
`Connection::server(&cfg)`.
- **TLS 1.2** is ECDHE-AEAD only (AES-128/256-GCM, ChaCha20-Poly1305) —
no static RSA, no static DH, no CBC. Forward secrecy by construction.
Includes mTLS and RFC 5077 stateless session tickets.
- **TLS 1.3** is the full RFC 8446 with PSK resumption, 0-RTT,
exporter, ALPN, mTLS, and downgrade-detection.
- **DTLS 1.2** (RFC 6347) carries the TLS 1.2 handshake over UDP with
HelloVerifyRequest cookies, handshake fragmentation/reassembly,
replay protection, and retransmission. Negotiates the same
ECDHE-AEAD suites × groups × signature schemes the TLS 1.2 path
supports.
- **DTLS 1.3** (RFC 9147) carries the TLS 1.3 handshake over UDP with
selective ACK reliability, encrypted sequence numbers, and a
HelloRetryRequest cookie. Negotiates the same TLS 1.3 suites,
groups (including `X25519MLKEM768`), and signature schemes as the
TLS 1.3 path.
### TLS 1.3
The `tls` module is a sans-I/O TLS 1.3 implementation with a thin
`std::io::Read + Write` adapter for blocking TCP. The full feature surface,
configured per side:
// Client (TLS or DTLS, any version):
Config::builder()
.versions(ProtocolVersion::TLSv1_2, ProtocolVersion::TLSv1_3)
.roots(roots)
.server_name("example.com")
.alpn(vec![b"h2".to_vec(), b"http/1.1".to_vec()])
.record_size_limit(4096) // RFC 8449
.identity(client_chain, client_key) // mTLS (any SigningKey)
.build();
// Server (TLS or DTLS, any version):
Config::builder()
.tls_only() // shorthand for versions(TLSv1_2, TLSv1_3)
.identity(chain, SigningKey::Rsa(rsa) | SigningKey::Ecdsa(ec) | ...)
.alpn(...)
.ticket_key([0u8; 32]) // enables NewSessionTicket emission
.max_early_data(16384) // accept up to N bytes of 0-RTT
.client_auth(ClientAuth { roots, required: true }) // mTLS
.build();
// DTLS variant:
Config::builder()
.dtls() // shorthand for versions(DTLSv1_2, DTLSv1_3)
.identity(chain, key)
.cookie_secret([0u8; 32]) // amplification defense
.max_record_size(1200) // MTU ceiling
.build();
let mut conn = Connection::client(&cfg)?; // or Connection::server(&cfg)
After a handshake completes, both sides expose:
- `connection.alpn_protocol()` — the negotiated ALPN name, if any.
- `connection.tls_exporter(label, context, out)` — RFC 8446 §7.5 / RFC 5705
application-layer keying material.
- `connection.peer_certificates()` — the validated chain (leaf first).
- (client) `connection.take_session()` — moves out a `StoredSession`
derived from the server's NewSessionTicket; pass it to
`ClientConfig::with_session` next time you connect to the same server.
- (client) `connection.write_early_data(&[u8])` — sends application data
under the early-traffic key before `ServerHello` arrives, valid only on a
resumed connection whose session enabled 0-RTT.
**0-RTT replay caveat.** RFC 8446 §8: 0-RTT data is replayable by an active
attacker, since the server cannot bind the early bytes to a unique
client-server handshake instance. The provided `ReplayWindow` blocks repeated
binders within a process, but cross-process / cross-server replay defenses
are application-level. Mark any data sent via `write_early_data` as
idempotent (a HEAD/GET, an idempotent RPC, …) and never as a state-changing
write.
use purecrypto::tls::{Config, Connection, HandshakeStatus, RootCertStore};
let roots = RootCertStore::new(); // populate from a PEM bundle …
let cfg = Config::builder()
.tls_only()
.roots(roots)
.server_name("example.com")
.alpn(vec![b"h2".to_vec(), b"http/1.1".to_vec()])
.build();
let mut conn = Connection::client(&cfg).unwrap();
// Drive the handshake: pop wire bytes from `conn`, send them, recv from
// the peer, feed them back. The sans-I/O surface is the same for TLS and
// DTLS — the only difference is "stream" vs "datagram" framing.
# fn _h(_: Connection) -> std::io::Result<()> { Ok(()) }
### Signature algorithms
X.509 chain validation and TLS 1.3 `CertificateVerify` both dispatch through
the [`signature_registry`](src/signature_registry.rs) module. Every signature
primitive purecrypto can do appears as a registry entry; a strict whitelist
[`SignaturePolicy`] controls which ones a verifier will accept.
#### Registry
| `id` (whitelist key) | X.509 OID | TLS 1.3 scheme | Default `modern()` |
| --------------------------- | ------------------------------- | -------------- | ------------------ |
| `rsa-pkcs1-sha1` | `1.2.840.113549.1.1.5` | (none) | opt-in |
| `rsa-pkcs1-sha256` | `1.2.840.113549.1.1.11` | `0x0401` | ✅ |
| `rsa-pkcs1-sha384` | `1.2.840.113549.1.1.12` | `0x0501` | ✅ |
| `rsa-pkcs1-sha512` | `1.2.840.113549.1.1.13` | (none) | opt-in |
| `rsa-pss-rsae-sha256` | `1.2.840.113549.1.1.11` (RSAE) | `0x0804` | ✅ |
| `rsa-pss-rsae-sha384` | `1.2.840.113549.1.1.12` (RSAE) | `0x0805` | ✅ |
| `rsa-pss-rsae-sha512` | `1.2.840.113549.1.1.13` (RSAE) | `0x0806` | ✅ |
| `rsa-pss-pss-sha256` | `1.2.840.113549.1.1.10` (PSS-keys) | (none) | opt-in |
| `ecdsa-with-sha256` | `1.2.840.10045.4.3.2` (any curve) | (none) | ✅ |
| `ecdsa-with-sha384` | `1.2.840.10045.4.3.3` (any curve) | (none) | ✅ |
| `ecdsa-with-sha512` | `1.2.840.10045.4.3.4` (any curve) | (none) | ✅ |
| `ecdsa-secp256r1-sha256` | (TLS-only — strict curve) | `0x0403` | ✅ |
| `ecdsa-secp384r1-sha384` | (TLS-only — strict curve) | `0x0503` | ✅ |
| `ecdsa-secp521r1-sha512` | (TLS-only — strict curve) | `0x0603` | ✅ |
| `ecdsa-secp256r1-sha384/512`, `ecdsa-secp384r1-sha256/512`, `ecdsa-secp521r1-sha256/384` | cross-hash, policy-only | (none) | opt-in |
| `ecdsa-secp256k1-sha256/384/512` | secp256k1, policy-only | (none) | opt-in |
| `ed25519` | `1.3.101.112` | `0x0807` | ✅ |
| `ml-dsa-44` / `-65` / `-87` | `2.16.840.1.101.3.4.3.17/18/19` | `0x0904/05/06` | ✅ (NIST FIPS 204) |
| `slh-dsa-sha2-128s/128f/192s/192f/256s/256f`, `slh-dsa-shake-128s/128f/192s/192f/256s/256f` | `2.16.840.1.101.3.4.3.20..31` | (none) | opt-in (FIPS 205) |
The matched-curve / matched-hash ECDSA pairs (e.g. P-256 + SHA-256) have IANA
TLS scheme codes; cross-hash pairs and all secp256k1 entries are reachable for
chain dispatch via the OID-keyed `ecdsa-with-shaN` entries — which accept any
supported curve — and as fine-grained policy-keyed entries for TLS opt-in.
ML-DSA is on the default whitelist (the modern PQC future). SLH-DSA's twelve
parameter sets are registered but never on the default whitelist: signatures
are 7–50 KB and rarely the right default for X.509 leaves.
#### Configuring the policy
use purecrypto::signature_registry::SignaturePolicy;
use purecrypto::tls::{Config, RootCertStore};
let roots = RootCertStore::new();
// Default — modern IANA-blessed set, RSA ≥ 2048 bits.
let cfg = Config::builder().roots(roots).build();
// Legacy interop: accept SHA-1 RSA and lower the RSA-bit floor to 1024.
let roots = RootCertStore::new();
let cfg = Config::builder()
.roots(roots)
.signature_policy(
SignaturePolicy::modern()
.permit("rsa-pkcs1-sha1")
.with_min_rsa_bits(1024),
)
.build();
// PQC-strict: only ML-DSA + Ed25519, refuse everything classical.
let roots = RootCertStore::new();
let cfg = Config::builder()
.roots(roots)
.signature_policy(
SignaturePolicy::empty()
.permit("ml-dsa-65")
.permit("ml-dsa-87")
.permit("ed25519"),
)
.build();
// SLH-DSA chains: opt in to a single set the application expects.
let roots = RootCertStore::new();
let cfg = Config::builder()
.roots(roots)
.signature_policy(SignaturePolicy::modern().permit("slh-dsa-sha2-128f"))
.build();
`signature_policy` on the unified [`Config`] applies to both client and
server roles — for the server it gates client-certificate validation under
mTLS. The policy is a strict whitelist:
adding an entry to the registry does NOT auto-permit it — the caller has to
add the id explicitly.
## C library
Prebuilt archives — the `purecrypto` CLI, the static (`.a`/`.lib`) and shared
(`.so`/`.dylib`/`.dll`) C libraries, and the header — are attached to each
[GitHub release](https://github.com/KarpelesLab/purecrypto/releases) for Linux,
macOS, and Windows.
The same code is callable from C via the `ffi` feature. Because the crate stays
`rlib` by default (so the `no_std` build is unaffected), produce the C library
with `cargo rustc`:
cargo rustc --lib --release --features ffi --crate-type cdylib # → target/release/libpurecrypto.so
cargo rustc --lib --release --features ffi --crate-type staticlib # → target/release/libpurecrypto.a
# Static link (self-contained):
cc app.c -I include target/release/libpurecrypto.a -lpthread -ldl -lm -o app
The API is declared in [`include/purecrypto.h`](include/purecrypto.h): one-shot
and streaming hashing, HMAC, OS randomness, RSA/ECDSA/Ed25519 key generation,
signing, verification and PEM I/O, ML-KEM (FIPS 203) / ML-DSA (FIPS 204) /
SLH-DSA (FIPS 205) keys, X.509 parsing/verification, and a sans-I/O TLS /
DTLS surface (`pc_tls_cfg_*`, `pc_tls_*`) including post-handshake
accessors for the negotiated cipher suite and peer SNI. Functions return a
`pc_status` code; variable-length output uses an in/out length buffer;
stateful objects are opaque handles freed by the library; panics never
cross the boundary.
## License
Licensed under the [MIT License](LICENSE).
标签:通知系统