Metadata-Version: 2.4
Name: purepycrypto
Version: 3.2.0
Summary: Pure-Python cryptography library (zero dependencies): SHA-2/SHA-3/BLAKE2b, AES-GCM(-SIV), ChaCha20-Poly1305, X25519/Ed25519, RSA-OAEP/PSS, with built-in TUI and command-line tool
Home-page: https://git.dvscloud.net/dvs/purepycrypto
Author: Dvs(DvsXT)
Author-email: "Dvs(DvsXT)" <dvs6666@163.com>
License: MIT
Project-URL: Homepage, https://git.dvscloud.net/dvs/purepycrypto
Project-URL: Documentation, https://git.dvscloud.net/dvs/purepycrypto#documentation
Project-URL: Source, https://git.dvscloud.net/dvs/purepycrypto
Keywords: cryptography,crypto,pure-python,aes,aes-gcm,aes-gcm-siv,chacha20,poly1305,x25519,ed25519,rsa,oaep,pss,sha256,sha512,sha3,keccak,shake,blake2b,argon2,scrypt,hkdf,pbkdf2,hmac,cli,tui
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Provides-Extra: compare
Requires-Dist: pycryptodome; extra == "compare"
Requires-Dist: cryptography>=45; extra == "compare"
Dynamic: author
Dynamic: home-page
Dynamic: license-file
Dynamic: requires-python

# purepycrypto

[![Repository](https://img.shields.io/badge/gitea-git.dvscloud.net-blue)](https://git.dvscloud.net/dvs/purepycrypto)
[![Python](https://img.shields.io/badge/python-3.7%2B-blue)](https://www.python.org/)
[![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
[![Dependencies](https://img.shields.io/badge/dependencies-zero-brightgreen)](https://git.dvscloud.net/dvs/purepycrypto)
[![Author](https://img.shields.io/badge/author-Dvs(DvsXT)-orange)](https://git.dvscloud.net/dvs/purepycrypto)

> **Pure Python Cryptography Toolkit — a zero-dependency pure-Python cryptography library.**
> SHA-2 / SHA-3 / SHAKE / BLAKE2b · PBKDF2 / HKDF / scrypt / Argon2id · AES (ECB/CBC/CTR/CFB/OFB/GCM) · AES-GCM-SIV · ChaCha20-Poly1305 / XChaCha20-Poly1305 · X25519 · Ed25519 · RSA-OAEP / PSS · PKCS#1 / PKCS#8 / SPKI / PEM

Current version: **v3.2.0** (this release adds the `kdf2` / `encoding` / `rand` modules, see [§ New in v3.2.0](#-new-in-v320)).

---

## 🆕 What's New in This Release (v3.2.0)

> Version numbering rule: `v{major}.{feature upgrades}.{bug fixes}` → this release contains both **feature upgrades** and **bug fixes**.

### 1. Unified Error System (⚠️ Breaking Change)

Library-wide error handling is unified into two categories, semantically clear and never mixed:

- **Fatal error → raise `PPYC_ERR(code, message, suggestion)`**
  Raised only when the program can no longer continue safely (internal invariant broken, fatal environment problem, unable to construct an object during initialization).
- **Non-fatal error → return a 3-tuple `(error_code, error_message, suggestion)`**
  Expected failures (invalid input, failed validation, authentication failure, missing file, parse failure) **no longer raise exceptions**; the caller can handle them gracefully by checking the return value.
- **41 standard error codes**, unified prefix `PPYC_ERR_`, covering 9 major categories.
- Helper functions: `ok()` / `err()` / `is_ok()` / `is_err()` / `raise_ppyc()` / `OK_TUPLE` / `PPYCResult`.
- Legacy exception names `CryptoError` / `DecryptionError` / `Ed25519Error` are kept as **subclasses** of `PPYC_ERR` (backward compatible).
- CLI exit codes: `0` success / `2` `PPYC_ERR` / `3` file not found / `4` argument error.
- See [`docs/errors.md`](docs/errors.md) for details.

### 2. TUI CN/EN Language Switch (default EN)

The TUI supports **Chinese / English interface switching**, defaulting to English, and can be overridden with the environment variable `PUREPYCRYPTO_LANG=cn`.

### 3. Performance Optimization

| Algorithm | Before | After | Speedup |
|-----------|--------|-------|---------|
| SHA-3 `keccak_f1600` | 1410 µs/perm | **191 µs/perm** | **7.4x** |
| SHA-3 `sha3_256` throughput | 425 KB/s | **838 KB/s** | 1.97x |
| SHA-3 `hmac_sha3_256` | 277 KB/s | **628 KB/s** | 2.27x |
| AES-GCM-SIV large message | 146 KB/s | **1101 KB/s** | **7.5x** |
| AES-GCM-SIV 32B small message | 1693 µs | **198 µs** | **8.5x** |
| Ed25519 `sign` | 3.52 ms | **1.79 ms** | 2.0x |
| Ed25519 `verify` | 3.89 ms | **1.95 ms** | 2.0x |

> Techniques: SHA-3 uses **SWAR packed integers**; AES-GCM-SIV adds a **POLYVAL table cache** + derived-key cache + CTR multiprocessing; Ed25519 window and precomputation optimizations.

### 4. Fixes for 6 Real Defects

Including the `sha3.py` hex wrapper crash, the CLI `_write_binary()` crash, the CLI `x25519` wrong exit code,
TUI hash / KDF menu crashes, TUI `x25519`/`rsa` unpacking crashes, TUI self-test false positives, etc. See [`CHANGELOG.md`](./CHANGELOG.md) for details.

---

## 📖 Table of Contents

- [Project Overview](#-project-overview)
- [Feature Overview](#-feature-overview)
- [Requirements](#-requirements)
- [Installation](#-installation)
- [Three Usage Modes](#-three-usage-modes-one-command-three-forms)
- [Quick Start](#-quick-start)
- [Unified Error System](#-unified-error-system)
- [New in v3.2.0](#-new-in-v320)
- [Windows Multiprocessing Notes](#-windows-multiprocessing-notes-important)
- [TUI Language Switch](#-tui-language-switch-cn--en)
- [API Cheat Sheet](#-api-cheat-sheet)
- [Security Notes](#-security-notes)
- [Known Limitations](#-known-limitations)
- [Testing](#-testing)
- [FAQ](#-faq)
- [Contributing](#-contributing)
- [License](#-license)
- [Author](#-author)

---

## 🎯 Project Overview

`purepycrypto` is a cryptography toolkit **implemented entirely in pure Python with zero third-party dependencies**. Its goal is not to replace heavily audited libraries such as
`cryptography` / OpenSSL that call high-performance C implementations, but to provide:

- **Readable, auditable, portable** — every algorithm is written line by line in pure Python, with no reliance on any C extension or system library;
- **Teaching- and embedded-friendly** — runs in any environment that can run CPython, with no compilation;
- **Correctness first** — strictly follows RFC / FIPS / NIST standards and self-tests against official vectors;
- **Modern algorithm portfolio** — covers hashing, message authentication, key derivation, symmetric encryption, authenticated encryption, key exchange, and public-key signatures.

> ⚠️ The performance and side-channel characteristics of pure Python make it **unsuitable** for high-throughput, strongly adversarial, long-running production-critical scenarios.
> Be sure to read the [Security Notes](#-security-notes) and [`docs/security.md`](docs/security.md).

Author: **Dvs(DvsXT)** · License: **MIT** · Current version: **v3.2.0**

---

## ✨ Feature Overview

| Category | Algorithm / Feature | Standard | Status |
|----------|---------------------|----------|--------|
| **Hashing** | SHA-256 / SHA-512 | FIPS 180-4 | ✅ |
| | BLAKE2b (variable digest length 1–64 bytes) | RFC 7693 | ✅ |
| | SHA3-224 / 256 / 384 / 512 | FIPS 202 | ✅ |
| | SHAKE128 / SHAKE256 (extendable-output XOF) | FIPS 202 | ✅ |
| **Message Authentication** | HMAC-SHA256 / HMAC-SHA512 | RFC 2104 | ✅ |
| | HMAC-SHA3-256 / HMAC-SHA3-512 | RFC 2104 / FIPS 198-1 | ✅ |
| | Poly1305 (one-time authenticator) | RFC 8439 | ✅ |
| **Key Derivation** | PBKDF2-HMAC-SHA256 / SHA512 | RFC 8018 / RFC 6070 | ✅ |
| | HKDF (extract / expand / one-shot) | RFC 5869 | ✅ |
| | scrypt | RFC 7914 | ✅ |
| | Argon2id (with secret / associated data) | RFC 9106 | ✅ |
| **Symmetric Encryption** | AES-128 / 192 / 256 | FIPS 197 | ✅ |
| | Modes: ECB / CBC / CTR / CFB / OFB | NIST SP 800-38A | ✅ |
| | GCM (AEAD, with AAD) | NIST SP 800-38D | ✅ |
| **Authenticated Encryption** | ChaCha20-Poly1305 | RFC 8439 | ✅ |
| | XChaCha20-Poly1305 (192-bit nonce) | draft-irtf-cfrg-xchacha | ✅ |
| | AES-GCM-SIV (nonce-misuse resistant) | RFC 8452 | ✅ |
| **Key Exchange** | X25519 | RFC 7748 | ✅ |
| **Public-Key Encryption** | RSA-OAEP | RFC 8017 | ✅ |
| **Digital Signatures** | RSA-PSS | RFC 8017 | ✅ |
| | Ed25519 (Pure EdDSA) | RFC 8032 | ✅ |
| **Serialization** | PKCS#1 / PKCS#8 / SPKI / PEM | RFC 8017 / 5958 / 5280 | ✅ |
| **Engineering Utilities** | Constant-time comparison, secure random | — | ✅ |
| | Multiprocessing acceleration (pool reuse + SharedMemory) | — | ✅ |
| | Interactive TUI (CN/EN bilingual) / command-line CLI | — | ✅ |
| | Single-file bundle `purepycrypto/single/purepycrypto.py` | — | ✅ |
| **New in v3.2.0** | `kdf2` (hardened KDF) / `encoding` (encoding) / `rand` (CSPRNG) | — | ✅ |
| **Not Included** | Post-quantum algorithms (Kyber / Dilithium / Falcon / SPHINCS+, etc.) | — | ❌ Not provided |

> **Explicitly excludes** any post-quantum (PQC) algorithm. For signatures, choose **Ed25519** (recommended for new protocols) or **RSA-PSS**;
> for key exchange, use the **X25519** + **HKDF** + **AES-GCM** / **AES-GCM-SIV** / **ChaCha20-Poly1305** combination.

---

## 🧩 Requirements

- **Python ≥ 3.7** (`pyproject.toml` declares `requires-python = ">=3.7"`; CI covers 3.7–3.13)
- **No third-party runtime dependencies** (uses only the standard library: `struct`, `os`, `math`, `base64`, `multiprocessing`, etc.)
- Optional: multiprocessing acceleration is enabled automatically on platforms that support `multiprocessing.shared_memory` (Python ≥ 3.8), otherwise it falls back to serial execution automatically

---

## 📦 Installation

Install from PyPI:

```bash
pip install purepycrypto
```

Upgrade to the latest version:

```bash
pip install --upgrade purepycrypto
```

Install from source (development mode):

```bash
git clone https://git.dvscloud.net/dvs/purepycrypto.git
cd purepycrypto
pip install -e .
```

After installation, a single command `purepycrypto` is available (no arguments enters the TUI, arguments go to the command line).

---

## 🎛️ Three Usage Modes (One Command, Three Forms)

After installation only **one** command, `purepycrypto`, is registered; its behavior is dispatched automatically based on whether arguments are present:

```bash
# (1) no arguments -> interactive TUI (default English; PUREPYCRYPTO_LANG=cn switches to Chinese)
purepycrypto
purepycrypto tui                 # explicitly launch the TUI (equivalent)

# (2) with arguments -> command-line mode (subcommands: hash / hmac / kdf / aes / chacha / x25519 / rsa / rand / selftest / version)
purepycrypto hash --text abc --hex
purepycrypto --help              # show command-line help

# (3) import as a library
python -c "import purepycrypto; print(purepycrypto.__version__)"
```

When not installed (inside the source directory), the equivalent form is:

```bash
python -m purepycrypto                          # TUI
python -m purepycrypto hash --text abc --hex    # command line
python -c "import purepycrypto"                 # library
```

> ⚠️ **Important**: The command-line entry point has been unified into a **single command** `purepycrypto` (no arguments = TUI, arguments = CLI).
> The only single-file artifact is the in-package `purepycrypto/single/purepycrypto.py` (generated by
> `scripts/build_single.py --mode one`), whose behavior is likewise "no arguments = TUI, arguments = CLI".

### Single-File Bundle

```bash
# generate the single file in the source directory (no arguments = TUI, arguments = CLI)
python purepycrypto/scripts/build_single.py --mode one -o purepycrypto/single/purepycrypto.py

# run it directly
python purepycrypto/single/purepycrypto.py                      # TUI
python purepycrypto/single/purepycrypto.py hash --text abc --hex # command line
```

Used as a library (when there is no entry point):

```python
import purepycrypto as ppc
print(ppc.sha3_256(b"abc").hex())
```

---

## 🚀 Quick Start

All examples are based on the `purepycrypto v3.2.0` public API and can be run directly (Python ≥ 3.8).

### 1. Hashing

```python
from purepycrypto import sha256, sha512, blake2b

print(sha256(b"abc").hex())
# ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

print(len(sha512(b"abc")))                    # 64
print(len(blake2b(b"abc")))                   # 64 (default)
print(len(blake2b(b"abc", digest_size=32)))   # 32
```

> Input must be `bytes`; for strings, call `.encode("utf-8")` first. When `blake2b`'s `digest_size` is invalid (outside 1–64),
> it **returns an error tuple** instead of raising (see [Unified Error System](#-unified-error-system)).

### 2. Key Derivation (KDF)

```python
from purepycrypto import (
    hmac_sha256, pbkdf2_hmac, hkdf, hkdf_extract, hkdf_expand, scrypt, argon2id,
)
import os

# --- HMAC ---
mac = hmac_sha256(b"key", b"message")

# --- PBKDF2 ---
salt = os.urandom(16)
dk = pbkdf2_hmac(b"password", salt, iterations=200_000, dklen=32, hash_name="sha256")

# --- HKDF (RFC 5869) ---
prk = hkdf_extract(salt=salt, ikm=b"input key material")          # extract
okm = hkdf_expand(prk=prk, info=b"app-context", length=32)        # expand
okm2 = hkdf(ikm=b"input key material", salt=salt, info=b"app-context", length=32)

# --- scrypt (RFC 7914) ---
sk = scrypt(b"password", b"NaCl", N=2**14, r=8, p=1, dklen=32)

# --- Argon2id (RFC 9106, recommended for new systems) ---
tag = argon2id(
    password=b"password",
    salt=os.urandom(16),     # recommended ≥ 16 bytes
    t=3,                     # number of iterations
    m=65536,                 # memory (KiB)
    p=4,                     # parallelism
    tag_length=32,
)
```

### 3. AES Symmetric Encryption

```python
from purepycrypto import AES
import os

key = os.urandom(32)                 # 16 / 24 / 32 bytes -> AES-128 / 192 / 256
aes = AES(key)

# --- GCM (AEAD, recommended) ---
nonce = os.urandom(12)               # 96-bit nonce
ct, tag = aes.gcm_encrypt(nonce, b"secret data", aad=b"header")
pt = aes.gcm_decrypt(nonce, ct, tag, aad=b"header")     # returns bytes on success
# on auth failure returns an (error_code, message, suggestion) tuple, see "Unified Error System"

# --- CBC (automatic PKCS#7 padding) ---
iv = os.urandom(16)
ct = aes.encrypt_cbc(b"secret data", iv)
pt = aes.decrypt_cbc(ct, iv)

# --- CTR (nonce is 8 bytes) ---
ct = aes.encrypt_ctr(b"secret data", os.urandom(8))
```

Other modes: `encrypt_ecb / decrypt_ecb`, `encrypt_cfb / decrypt_cfb`, `encrypt_ofb / decrypt_ofb`.

### 4. ChaCha20-Poly1305 / XChaCha20-Poly1305

```python
from purepycrypto import (
    chacha20poly1305_encrypt, chacha20poly1305_decrypt,
    xchacha20poly1305_encrypt, xchacha20poly1305_decrypt,
    poly1305_mac,
)
import os

key = os.urandom(32)

# ChaCha20-Poly1305 (12-byte nonce, RFC 8439) -- returns ciphertext || tag
blob = chacha20poly1305_encrypt(key, os.urandom(12), b"secret", aad=b"aad")
pt = chacha20poly1305_decrypt(key, nonce, blob, aad=b"aad")   # returns a tuple on failure

# XChaCha20-Poly1305 (24-byte nonce, safe to generate randomly)
blob = xchacha20poly1305_encrypt(key, os.urandom(24), b"secret", aad=b"aad")
pt = xchacha20poly1305_decrypt(key, nonce24, blob, aad=b"aad")

# raw Poly1305 authenticator
mac = poly1305_mac(os.urandom(32), b"message")
```

### 5. X25519 Key Exchange

```python
from purepycrypto import x25519, x25519_base, x25519_shared, x25519_generate_keypair

priv_a, pub_a = x25519_generate_keypair()     # both 32 bytes
priv_b, pub_b = x25519_generate_keypair()

shared_a, is_zero_a = x25519_shared(priv_a, pub_b)
shared_b, is_zero_b = x25519_shared(priv_b, pub_a)
assert shared_a == shared_b
if is_zero_a or is_zero_b:                    # must check for a low-order point (all zero)
    raise RuntimeError("low-order or forged public key detected")

pub = x25519_base(priv_a)                     # base point u=9
raw = x25519(priv_a, pub_b)                   # same as shared_a
```

### 6. Ed25519 Digital Signatures

```python
from purepycrypto import ed25519_keygen, ed25519_publickey, ed25519_sign, ed25519_verify

seed, pub = ed25519_keygen()                # seed (private key) 32 bytes, pub 32 bytes
sig = ed25519_sign(b"hello", seed)          # 64-byte signature (R || S)
assert ed25519_verify(sig, b"hello", pub) is True
assert ed25519_verify(sig, b"hell0", pub) is False   # verification failure returns False, does not raise

# derive the public key from a known seed (RFC 8032 §7.1 TEST 1)
seed = bytes.fromhex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")
print(ed25519_publickey(seed).hex())
# d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a
```

### 7. RSA Public-Key Encryption and Signing

```python
from purepycrypto import (
    rsa_keygen, rsa_oaep_encrypt, rsa_oaep_decrypt,
    rsa_pss_sign, rsa_pss_verify,
)

pub, priv = rsa_keygen(bits=2048, parallel=True)   # parallel=True uses multiprocessing to speed up prime search

# OAEP encryption (RFC 8017)
ct = rsa_oaep_encrypt(b"secret message", pub, label=b"ctx")
pt = rsa_oaep_decrypt(ct, priv, label=b"ctx")

# PSS sign / verify
sig = rsa_pss_sign(b"message to sign", priv)
assert rsa_pss_verify(b"message to sign", sig, pub) is True
assert rsa_pss_verify(b"tampered", sig, pub) is False
```

> RSA keys must be **at least 521 bits** (below this a `PPYC_ERR` is raised; OAEP/PSS need a sufficient modulus length). 2048 / 3072 / 4096 are recommended.

### 8. PEM / DER Serialization

```python
from purepycrypto import (
    rsa_keygen, private_key_to_pem, public_key_to_pem, parse_private_pem,
    private_key_to_pkcs8_der, public_key_to_spki_der,
)

pub, priv = rsa_keygen(2048)

pem_priv = private_key_to_pem(priv)     # -----BEGIN PRIVATE KEY----- (PKCS#8)
pem_pub = public_key_to_pem(pub)        # -----BEGIN PUBLIC KEY----- (SPKI)

der_priv = private_key_to_pkcs8_der(priv)
der_pub = public_key_to_spki_der(pub)

priv2 = parse_private_pem(pem_priv)     # parse a PKCS#8 PEM private key
assert priv2.n == priv.n

with open("private_key.pem", "w", encoding="ascii") as f:
    f.write(pem_priv)
```

### 9. SHA-3 / SHAKE

```python
from purepycrypto import sha3_256, sha3_512, shake128, hmac_sha3_256, sha3_batch

print(sha3_256(b"abc").hex())
# 3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532

print(shake128(b"abc", 32).hex())    # XOF: outputs an arbitrary outlen bytes
# 5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc8

mac = hmac_sha3_256(b"key", b"message")             # 32 bytes

# batch parallel hashing (multiple independent messages; n_workers=None auto-detects CPU count)
digests = sha3_batch([b"a", b"b", b"c"], algo="sha3_256", n_workers=4)
```

### 10. AES-GCM-SIV

```python
from purepycrypto import (
    aes128_gcm_siv_encrypt, aes128_gcm_siv_decrypt,   # 16-byte key
    aes256_gcm_siv_encrypt, aes256_gcm_siv_decrypt,   # 32-byte key
    aes_gcm_siv_encrypt, aes_gcm_siv_decrypt,         # unified entry (dispatches by key length)
    set_cache_enabled, cache_enabled, clear_caches,
)
import os

key = os.urandom(16)          # 16 -> AES-128; 32 -> AES-256
nonce = os.urandom(12)        # fixed 12 bytes
ct, tag = aes128_gcm_siv_encrypt(key, nonce, b"secret", aad=b"header")
pt = aes128_gcm_siv_decrypt(key, nonce, ct, tag, aad=b"header")
assert pt == b"secret"

# cache control (enabled by default; disable if the threat model disallows derived keys residing in memory)
set_cache_enabled(False)      # disable cache
assert cache_enabled() is False
clear_caches()                # clear cached content
```

> ⚠️ GCM-SIV only **reduces** the harm of nonce reuse; it does not permit unlimited reuse. When possible, still keep nonces unique.

### 11. Unified Error Handling (Overview)

```python
from purepycrypto import AES, is_err, PPYC_ERR, PPYC_ERR_INVALID_KEY_LEN

# fatal error -> raise PPYC_ERR
try:
    AES(b"short")                         # invalid key length
except PPYC_ERR as e:
    print(e.code)        # 'PPYC_ERR_INVALID_KEY_LEN'
    print(e.message)     # human-readable reason
    print(e.suggestion)  # human-readable fix suggestion

# non-fatal error -> return a 3-tuple
aes = AES(b"0" * 16)
out = aes.gcm_decrypt(b"\x00" * 12, b"ct", b"\x00" * 16)   # tag mismatch
if is_err(out):
    code, msg, hint = out
    print(code, msg, hint)    # PPYC_ERR_AUTH_FAILED ...
else:
    plaintext = out
```

---

## 🔐 Unified Error System

As of v3.2.0, library-wide errors are unified into two categories (see [`docs/errors.md`](docs/errors.md) for details):

| Type | Handling | Applicable scenarios |
|------|----------|----------------------|
| **Fatal error** | **Raise `PPYC_ERR`** | Internal invariant broken (library bug), fatal environment problem, unable to construct an object during initialization |
| **Non-fatal error** | **Return a 3-tuple** `(error_code, error_message, suggestion)` | Invalid input, failed validation, authentication failure, missing file, parse failure |

```python
from purepycrypto import PPYC_ERR, err, ok, is_ok, is_err, OK_TUPLE, PPYC_ERR_INVALID_KEY_LEN

# fatal error
raise PPYC_ERR(PPYC_ERR_INTERNAL, "state machine inconsistent", "please file an issue")

# non-fatal error (returns a tuple)
return err(PPYC_ERR_INVALID_KEY_LEN, "AES key must be 16/24/32 bytes", "pass a correct length")
#   -> ('PPYC_ERR_INVALID_KEY_LEN', 'AES key must be 16/24/32 bytes', 'pass a correct length')

# success (validation-style API)
return OK_TUPLE        # (None, None, None)
```

**Caller check**:

```python
result = some_api(...)
if is_err(result):                 # generic check: works for any API
    code, msg, hint = result
    ...
else:
    value = result
```

**Legacy exception names are still compatible** (all are subclasses of `PPYC_ERR`): `CryptoError` (default `PPYC_ERR_INTERNAL`),
`DecryptionError` (default `PPYC_ERR_AUTH_FAILED`), `Ed25519Error` (default `PPYC_ERR_INVALID_ARG`).

**CLI exit codes**:

| Exit code | Meaning |
|-----------|---------|
| `0` | success |
| `1` | other (e.g. `rsa --op verify` verification FAIL) |
| `2` | `PPYC_ERR` (fatal error) |
| `3` | file not found |
| `4` | argument error |

---

## ✅ New in v3.2.0

> The following modules are officially available as of v3.2.0 and are all merged into the top-level exports.

| Module | Responsibility | Representative interfaces |
|--------|----------------|---------------------------|
| `purepycrypto.kdf2` | **Hardened key derivation** (stricter validation on top of `kdf`: minimum salt length, Argon2 encoded strings, etc.) | `pbkdf2`, `argon2id_hash` / `argon2id_verify`, `argon2id_hash_encoded` / `argon2_verify_encoded`, `scrypt_simple`, `hkdf_expand_multi` |
| `purepycrypto.encoding` | **Pure-Python encoding** (byte-for-byte identical to the standard library) | `b64encode` / `b64decode` (including URL-safe / nopad), `b32encode` / `b32decode`, `hex_encode` / `hex_decode`, `b58encode` / `b58decode` |
| `purepycrypto.rand` | **Cryptographically secure random (CSPRNG)** toolkit | `random_bytes` / `random_int`, `token_bytes` / `token_hex` / `token_urlsafe`, `random_password`, `random_uuid4`, `random_choice` / `random_sample` |

```python
# example
from purepycrypto import b64encode, b58encode, token_hex, random_password

print(b64encode(b"abc"))            # b'YWJj'
print(b58encode(b"\x00\x01\x02hello"))  # b'13DgQxdPmp'
print(token_hex(4))                 # 8-char hex string
print(random_password(16))          # random password
```

---

## ⚠️ Windows Multiprocessing Notes (Important)

Some APIs use a `multiprocessing` process pool for acceleration in **large-input** or **batch** scenarios:

| API | Condition that triggers multiprocessing |
|-----|-----------------------------------------|
| `sha3_batch(items, algo, n_workers)` | `n_workers > 1` and enough messages |
| `aes128/256_gcm_siv_encrypt(...)` | large input and `n_workers` is `None` (auto) or `> 1` |
| `rsa_keygen(..., parallel=True)` | multiprocessing prime search enabled by default |

**Windows starts child processes via `spawn` (which re-imports the main module)**, so caller scripts **must** be wrapped in
`if __name__ == "__main__":`, otherwise a `RuntimeError: ... bootstrapping phase` is raised.

### ✅ Correct pattern

```python
# my_script.py
from purepycrypto import sha3_batch, aes256_gcm_siv_encrypt

def main():
    digests = sha3_batch([b"a", b"b"], "sha3_256", n_workers=4)
    ct, tag = aes256_gcm_siv_encrypt(key, nonce, big_data)

if __name__ == "__main__":        # <- mandatory on Windows
    main()
```

### ⛔ Incorrect pattern (raises an error or silently slows down)

```python
from purepycrypto import sha3_batch, aes256_gcm_siv_encrypt

# called directly at module level -- on Windows the child process re-executes this module, causing a RuntimeError
digests = sha3_batch(messages, "sha3_256", n_workers=4)
ct, tag = aes256_gcm_siv_encrypt(key, nonce, big_data)
```

### 🛡️ Two ways to avoid it

1. **Add the `if __name__ == "__main__":` guard** (recommended; you keep the parallel speedup);
2. **Explicitly pass `n_workers=1` to force serial** (no startup overhead; actually faster for small data).

> **Fault tolerance note**: `sha3_batch` has a built-in fallback — if multiprocessing fails to start it automatically degenerates to serial and returns the correct result,
> without crashing (but loses the speedup); the AES-GCM-SIV multiprocessing branch raises a `RuntimeError` in the same situation,
> **so be sure to add the guard**. Linux / macOS default to `fork` and usually have no such limitation, but for cross-platform consistency it is still recommended to add the guard uniformly.

---

## 🌐 TUI Language Switch (CN / EN)

The TUI supports **Chinese/English interface switching**, **defaulting to English**.

```bash
# Windows (cmd)
set PUREPYCRYPTO_LANG=cn && purepycrypto
# Windows (PowerShell)
$env:PUREPYCRYPTO_LANG="cn"; purepycrypto
# Linux / macOS
PUREPYCRYPTO_LANG=cn purepycrypto
# default (English)
purepycrypto
```

You can also switch it in the TUI menu, or call it in code:

```python
from purepycrypto.cli import set_language, get_language
set_language("cn")      # or "en"
print(get_language())   # 'cn' / 'en'
```

> Values: `en` (default) / `cn`. An invalid value falls back to `en`.

---

## 📚 API Cheat Sheet

The complete API is in [`docs/api.md`](docs/api.md). Basic interfaces can be imported from the top-level `purepycrypto`,
while SHA-3 / Ed25519 / AES-GCM-SIV etc. can be imported from their own submodules (or from the top level).

### Hashing

| Function | Signature |
|----------|-----------|
| `sha256` / `sha512` | `(message: bytes) -> bytes` |
| `blake2b` | `(data: bytes, digest_size: int = 64) -> bytes` |
| `sha3_224 / sha3_256 / sha3_384 / sha3_512` | `(data: bytes) -> bytes` |
| `shake128 / shake256` | `(data: bytes, outlen: int) -> bytes` |
| `sha3_batch` | `(items, algo="sha3_256", n_workers=None) -> list[bytes]` (v3.2.0) |

### KDF / MAC

| Function | Signature |
|----------|-----------|
| `hmac_sha256` / `hmac_sha512` | `(key: bytes, message: bytes) -> bytes` |
| `hmac_sha3_256` / `hmac_sha3_512` | `(key: bytes, message: bytes) -> bytes` |
| `pbkdf2_hmac` | `(password, salt, iterations, dklen, hash_name="sha256") -> bytes` |
| `hkdf_extract` | `(salt, ikm, hash_name="sha256") -> bytes` |
| `hkdf_expand` | `(prk, info, length, hash_name="sha256") -> bytes` |
| `hkdf` | `(ikm, salt=b"", info=b"", length=32, hash_name="sha256") -> bytes` |
| `scrypt` | `(password, salt, N=4096, r=8, p=1, dklen=32) -> bytes` |
| `argon2id` | `(password, salt, t=1, m=256, p=1, tag_length=32, secret=b"", ad=b"", version=0x13) -> bytes` |
| `poly1305_mac` | `(key32: bytes, msg: bytes) -> bytes` |

### Symmetric Encryption

| Interface | Description |
|-----------|-------------|
| `AES(key)` | construct an AES object (key is 16/24/32 bytes) |
| `AES.gcm_encrypt(nonce, plaintext, aad=b"", n_workers=None)` | returns `(ciphertext, tag)` |
| `AES.gcm_decrypt(nonce, ct, tag, aad=b"")` | returns the plaintext bytes on success, an error tuple on authentication failure |
| `AES.encrypt_cbc / decrypt_cbc` | CBC (16-byte IV, automatic PKCS#7 padding) |
| `AES.encrypt_ecb / decrypt_ecb` | ECB |
| `AES.encrypt_ctr / decrypt_ctr` | CTR (8-byte nonce) |
| `AES.encrypt_cfb / decrypt_cfb` | CFB |
| `AES.encrypt_ofb / decrypt_ofb` | OFB |
| `chacha20poly1305_encrypt / decrypt` | ChaCha20-Poly1305 (12-byte nonce) |
| `xchacha20poly1305_encrypt / decrypt` | XChaCha20-Poly1305 (24-byte nonce) |
| `aes_gcm_siv_encrypt / decrypt` | unified entry dispatched by key length (16/32) |
| `aes128_gcm_siv_encrypt / decrypt` | AES-128-GCM-SIV (submodule `purepycrypto.aes_gcm_siv`) |
| `aes256_gcm_siv_encrypt / decrypt` | AES-256-GCM-SIV (submodule) |
| `set_cache_enabled / cache_enabled / clear_caches` | GCM-SIV cache control (v3.2.0) |

### Public Key

| Interface | Signature |
|-----------|-----------|
| `x25519` | `(k: bytes, u: bytes) -> bytes` (invalid input returns an error tuple) |
| `x25519_base` | `(scalar: bytes) -> bytes` |
| `x25519_shared` | `(priv, peer_pub) -> (shared, is_zero)` |
| `x25519_generate_keypair` | `() -> (priv, pub)` |
| `rsa_keygen` | `(bits=2048, parallel=True) -> (RSAPublicKey, RSAPrivateKey)` |
| `rsa_oaep_encrypt` | `(message, pub, label=b"") -> bytes` |
| `rsa_oaep_decrypt` | `(ciphertext, priv, label=b"") -> bytes` |
| `rsa_pss_sign` | `(message, priv, salt_len=None) -> bytes` |
| `rsa_pss_verify` | `(message, signature, pub, salt_len=None) -> bool` |
| `ed25519_keygen` | `() -> (seed32, pub32)` |
| `ed25519_publickey` | `(seed32: bytes) -> bytes` |
| `ed25519_sign` | `(message: bytes, seed32: bytes) -> bytes` (64 bytes) |
| `ed25519_verify` | `(signature64, message, pub32) -> bool` |
| `private_key_to_pem` / `public_key_to_pem` | `(key) -> str` |
| `parse_private_pem` | `(pem: str) -> RSAPrivateKey` |
| `private_key_to_pkcs8_der` / `public_key_to_spki_der` | `(key) -> bytes` |
| `private_key_to_pkcs1_der` / `public_key_to_pkcs1_der` | `(key) -> bytes` |

### Utilities and Errors

| Interface | Description |
|-----------|-------------|
| `rand_bytes(n)` | returns `n` bytes of cryptographically secure random data (`os.urandom`) |
| `rand_int(a, b)` | returns a uniformly random integer in `[a, b]` (rejection sampling) |
| `PPYC_ERR` / `PPYCError` | fatal-error exception (carries code/message/suggestion) |
| `CryptoError` / `DecryptionError` / `Ed25519Error` | legacy exception names (subclasses of `PPYC_ERR`) |
| `err` / `ok` / `is_ok` / `is_err` / `raise_ppyc` / `OK_TUPLE` / `PPYCResult` | non-fatal error helpers |
| `ALL_ERROR_CODES` | all 41 error code constants (tuple) |

---

## 🔐 Security Notes

This section is a summary; the full statement is in [`docs/security.md`](docs/security.md).

- **Randomness source**: all random bytes come from `os.urandom()` (the OS CSPRNG); the library does **not** contain any PRNG that can be seeded.
- **Constant-time comparison**: sensitive comparisons (Tag / MAC / shared key) use `_consttime_eq` / `_consttime_int_eq` to avoid early-return timing differences.
- **X25519 low-order point detection**: `x25519_shared` returns `is_zero`; callers **must** check it and reject an all-zero result.
- **AEAD authentication**: GCM / ChaCha20-Poly1305 / XChaCha20-Poly1305 / AES-GCM-SIV **return an error tuple**
  on authentication failure (`PPYC_ERR_AUTH_FAILED`) and **never return unauthenticated plaintext**.
- **Error codes do not leak sensitive information**: error codes and messages describe only the **category and fix suggestion**, never key / nonce / plaintext content (see `docs/security.md`).
- **RSA blinding**: the private-key decryption path includes blinding to mitigate timing side-channel risk.
- **Ed25519 signatures**: Pure EdDSA (RFC 8032); scalar multiplication uses a masked lookup-table constant-time approach (best-effort).
- ⚠️ **Do not use it for highly adversarial, long-running production-critical systems**. Pure Python cannot guarantee memory zeroization and cannot fully eliminate timing / branch / cache side channels.

---

## ⚠️ Known Limitations

1. **Performance**: pure-Python throughput is far below C implementations that call OpenSSL; large-file encryption/decryption and high-parameter PBKDF2/Argon2id will be noticeably slow.
2. **Side channels**: core comparisons and X25519/Ed25519 scalar multiplication have constant-time handling, but the Python interpreter's own branching, object allocation, and big-integer arithmetic timing cannot be fully controlled, making it **unsuitable for strongly adversarial scenarios that directly face untrusted input**.
3. **Memory zeroization**: Python has no reliable memory-erasure mechanism; keys may remain in heap memory until GC.
4. **Algorithm scope**: includes SHA-3/SHAKE, Ed25519, and AES-GCM-SIV; **excludes any post-quantum (PQC) algorithm**.
5. **nonce length**: `gcm_encrypt` uses a 96-bit (12-byte) nonce; CTR uses an 8-byte nonce; do not reuse a nonce under the same key.
6. **PEM support scope**: `parse_private_pem` only parses **PKCS#8 RSA private keys** (label `PRIVATE KEY`); it does not support the legacy PKCS#1 label `RSA PRIVATE KEY`, nor EC / Ed25519.
7. **Minimum RSA size**: `rsa_keygen` requires `bits >= 521`; below this a `PPYC_ERR` is raised.

---

## 🧪 Testing

```bash
# run all tests (tests live in the in-package purepycrypto/tests)
pytest purepycrypto/tests -q
```

Standard vectors covered by the tests (NIST / RFC):

- SHA-256 / SHA-512 (FIPS 180-4), BLAKE2b (RFC 7693)
- HMAC-SHA256 / 512 (RFC 4231), PBKDF2 (RFC 6070), HKDF (RFC 5869), scrypt (RFC 7914), Argon2id (RFC 9106)
- SHA-3 / SHAKE (FIPS 202, cross-verified against `hashlib`)
- AES (FIPS 197) and GCM (NIST SP 800-38D)
- ChaCha20 / Poly1305 / ChaCha20-Poly1305 (RFC 8439)
- X25519 (RFC 7748), Ed25519 (RFC 8032 §7.1), AES-GCM-SIV (RFC 8452)
- RSA-OAEP / PSS (RFC 8017)
- Unified error system `test_errors.py` / `test_errors_edge.py`

---

## ❓ FAQ

**Q: Why not use the `hashlib` / `hmac` standard libraries?**
A: This library is positioned as a "pure-Python auditable implementation"; hashing and HMAC are also implemented from scratch to keep the algorithm logic fully visible and portable. If you only care about performance, the standard library or `cryptography` is more suitable.

**Q: How does the `AES(key)` key length determine the variant?**
A: 16 → AES-128; 24 → AES-192; 32 → AES-256. Any other length raises `PPYC_ERR(PPYC_ERR_INVALID_KEY_LEN)`.

**Q: Does authentication failure raise an exception or return an error?**
A: As of v3.2.0, `gcm_decrypt` / `chacha*decrypt` etc. **return a 3-tuple** `(code, msg, hint)` (`code` is `PPYC_ERR_AUTH_FAILED`); check with `is_err()`. The historical name `DecryptionError` can still be caught.

**Q: Why does X25519 return `is_zero`?**
A: When the shared secret is all zeros it means the peer public key is a low-order point and the shared secret is unsafe, so it **must be rejected** (RFC 7748 §6.1).

**Q: Can it be used for both signing and encryption?**
A: Yes. Use RSA-PSS or Ed25519 for signing, and RSA-OAEP for encryption; the two use different padding schemes, so do not mix them.

**Q: Are there multiple command-line entry points?**
A: There is only one command, `purepycrypto`: no arguments launches the TUI, arguments enter the command line (subcommands `hash` / `hmac` / `kdf` / `aes` / `chacha` / `x25519` / `rsa` / `rand` / `selftest` / `version`).

**Q: Is Ed25519 available?**
A: **Yes.** `ed25519_keygen` / `ed25519_sign` / `ed25519_verify`. It still does **not provide** any post-quantum algorithm.

---

## 🤝 Contributing

1. New algorithms must come with **standard test vectors** (RFC / FIPS / NIST) and test cases added under `purepycrypto/tests/`.
2. Keep **zero third-party dependencies** (standard library only).
3. Use constant-time functions for sensitive comparisons; use `os.urandom` for randomness.
4. **Follow the unified error system**: parameter validation / authentication failure → `return err(...)`; internal contradiction → `raise PPYC_ERR(...)`; do not invent error codes.
5. Code must pass `pytest -q`. Commit messages and documentation may be in Chinese or English; code identifiers stay in English.

---

## 📄 License

This project is released under the **MIT License**. See [`LICENSE`](LICENSE) for details.

---

## 👤 Author

**Dvs(DvsXT)** — self-developed cryptography library · purepycrypto

- Email: dvs6666@163.com
- Repository: https://git.dvscloud.net/dvs/purepycrypto

> 💡 **Disclaimer**: This library is designed for learning, experimentation, teaching, and embedded prototypes, and the author accepts no liability for any direct or indirect loss caused by its use.
> For production-critical scenarios, please prefer implementations that have been widely audited and maintained by professional cryptography engineering teams.

---

<div align="center">

**purepycrypto v3.2.0** · Made with ❤️ by **Dvs(DvsXT)**

</div>
