Yurei (幽霊 - ghost) provides cryptographic primitives for modern Python applications without external dependencies. Built for prototyping secure systems, internal tools, and environments where dependency installation is restricted.
$ git clone https://github.com/ogkae/yurei
$ cd yurei
$ pip install -e .from yurei import (
uuid4, hash_password, verify_password,
create_token, verify_token,
encrypt_bytes, decrypt_bytes,
KVStore
)
# generate secure id
user_id = uuid4() # '550e8400-e29b-41d4-a716-446655440000'
# hash password
pwd_hash = hash_password("SecurePass123")
# create session token
token = create_token({"uid": user_id}, b"secret-key", ttl_seconds=3600)
# encrypt data
blob = encrypt_bytes(b"sensitive data", b"passphrase")
plain = decrypt_bytes(blob, b"passphrase")| module | description | key functions |
|---|---|---|
uid |
uuid4, deterministic sha256 ids, short tokens | uuid4(), sha256_id(), short_id() |
auth |
password hashing (pbkdf2-hmac-sha256) | hash_password(), verify_password() |
session |
signed tokens with hmac | create_token(), verify_token() |
cipher |
symmetric encryption + authentication | encrypt_bytes(), decrypt_bytes() |
cipher_parallel |
parallel chunked encryption | encrypt_parallel(), decrypt_parallel() |
store |
key-value storage (sqlite/memory) | KVStore.set(), KVStore.get() |
obfusc |
xor obfuscation utilities | xor_obfuscate(), xor_deobfuscate() |
utils |
base64url, pbkdf2, timing-safe compare | b64u_encode(), constant_time_eq() |
uuid4() -> strGenerate random UUID4 (format: 8-4-4-4-12).
is_uuid4(s: str) -> boolValidate UUID4 format.
sha256_id(namespace: Optional[str], name: str, salt: Optional[str] = None) -> strDeterministic 64-character hex ID from SHA256.
short_id(length: int = 12) -> strURL-safe alphanumeric token.
Example:
from yurei import uuid4, sha256_id, short_id
user_id = uuid4() # random
doc_id = sha256_id("documents", "report_2024.pdf") # deterministic
token = short_id(16) # url-safehash_password(password: str, iterations: int = 200_000) -> strPBKDF2-HMAC-SHA256 hash. Returns format:
pbkdf2$<iterations>$<salt_b64url>$<hash_b64url>
verify_password(stored: str, attempt: str) -> boolConstant-time password verification.
Example:
from yurei import hash_password, verify_password
# registration
pwd_hash = hash_password("MySecret123")
# login
is_valid = verify_password(pwd_hash, "MySecret123") # Truecreate_token(payload: Dict[str, str], secret: bytes, ttl_seconds: int = 3600) -> strCreate signed token. Format: <payload_b64>.<signature_b64>
verify_token(token: str, secret: bytes) -> Optional[Dict[str, str]]Verify signature and expiration. Returns payload dict.
Example:
from yurei import create_token, verify_token
import os
secret = os.urandom(32)
# create
token = create_token(
{
"uid": "user_123",
"role": "admin"
},
secret,
ttl_seconds=3600
)
# verify
payload = verify_token(token, secret)
if payload:
print(f"User: {payload['uid']}, Role: {payload['role']}")encrypt_bytes(plaintext: bytes, key: bytes) -> strEncrypt with HMAC-SHA256 authentication. Returns base64url string.
Key types:
- Passphrase (any length) → derives encryption + MAC keys via PBKDF2
- Raw 32-byte key → used directly
Format: salt(16) + nonce(12) + ciphertext + mac(32) → base64url
decrypt_bytes(blob_b64: str, key: bytes) -> bytesDecrypt and verify MAC (constant-time).
Example:
from yurei import encrypt_bytes, decrypt_bytes
# with passphrase
blob = encrypt_bytes(b"sensitive data", b"my-passphrase")
plain = decrypt_bytes(blob, b"my-passphrase")
# with raw key
key = os.urandom(32)
blob = encrypt_bytes(b"data", key)
plain = decrypt_bytes(blob, key)encrypt_parallel(
plaintext: bytes,
password: bytes,
chunk_size: int = 128 * 1024,
workers: Optional[int] = None
) -> strMulti-process encryption for large data. Returns base64url blob.
decrypt_parallel(
blob_b64: str,
password: bytes,
workers: Optional[int] = None
) -> bytesParallel decryption with global MAC verification.
Example:
from yurei import encrypt_parallel, decrypt_parallel
large_file = b"x" * (50 * 1024 * 1024) # 50 MB
# encrypt w 4 workers, 256KB chunks
blob = encrypt_parallel(large_file, b"key", chunk_size=256*1024, workers=4)
# decrypt
plain = decrypt_parallel(blob, b"key", workers=4)Performance: ~4x faster on quad-core systems for files >10MB.
class KVStore(path: Optional[str] = None)Methods:
set(key: str, value: Dict) -> None
get(key: str) -> Optional[Dict]
delete(key: str) -> Nonepath=None→ in-memory dictpath="file.db"→ persistent SQLite
Example:
from yurei import KVStore
# in-memory
store = KVStore()
store.set("user_123", {"name": "alice", "role": "admin"})
# persistent
db = KVStore("data.db")
db.set("session_456", {"uid": "user_123", "exp": 1234567890})
record = db.get("session_456")xor_obfuscate(s: str, key: Union[str, bytes]) -> strXOR with repeating key + base64url encoding.
xor_deobfuscate(s_enc: str, key: Union[str, bytes]) -> strReverse XOR obfuscation.
Example:
from yurei import xor_obfuscate, xor_deobfuscate
# hide connection string
conn_str = "postgres://user:pass@localhost/db"
hidden = xor_obfuscate(conn_str, b"salt")
# restore
original = xor_deobfuscate(hidden, b"salt")Warning
[XOR obfuscation is NOT encryption. Use only for deterring casual inspection.]
now_millis() -> int # current unix timestamp (ms)
to_hex(b: bytes) -> str # bytes to hex
b64u_encode(b: bytes) -> str # base64url encode
b64u_decode(s: str) -> bytes # base64url decode
constant_time_eq(a: bytes, b: bytes) -> bool # timing-safe comparison
pbkdf2_sha256(password: bytes, salt: bytes, iterations: int, dklen: int) -> bytesfrom yurei import (
uuid4, hash_password, verify_password,
create_token, verify_token, KVStore
)
import os
# setup
store = KVStore("users.db")
session_secret = os.urandom(32)
# --- REGISTRATION ---
def register(username: str, password: str):
user_id = uuid4()
pwd_hash = hash_password(password)
store.set(user_id, {
"username": username,
"pwd": pwd_hash,
"created": now_millis()
})
return user_id
# --- LOGIN ---
def login(user_id: str, password: str):
record = store.get(user_id)
if not record:
return None
if verify_password(record["pwd"], password):
token = create_token(
{"uid": user_id, "username": record["username"]},
session_secret,
ttl_seconds=86400 # 24h
)
return token
return None
# --- VERIFY SESSION ---
def verify_session(token: str):
payload = verify_token(token, session_secret)
return payload # None if invalid/expired
# usage
user_id = register("alice", "SecurePass123")
token = login(user_id, "SecurePass123")
session = verify_session(token)
print(f"Logged in: {session['username']}")from yurei import encrypt_bytes, decrypt_bytes, sha256_id
import os
class SecureFileStore:
def __init__(self, key: bytes):
self.key = key
def save(self, filename: str, data: bytes) -> str:
# generate deterministic id
file_id = sha256_id("files", filename)
# encrypt
blob = encrypt_bytes(data, self.key)
# store (simplified - use real storage)
with open(f"vault/{file_id}.enc", "w") as f:
f.write(blob)
return file_id
def load(self, file_id: str) -> bytes:
with open(f"vault/{file_id}.enc") as f:
blob = f.read()
return decrypt_bytes(blob, self.key)
# usage
vault = SecureFileStore(os.urandom(32))
file_id = vault.save("report.pdf", b"pdf content...")
content = vault.load(file_id)from yurei import xor_obfuscate, xor_deobfuscate
# at build time - obfuscate sensitive config
config = {
"db": "postgres://user:pass@host/db",
"api_key": "sk_live_abc123xyz",
"secret": "dont-commit-this"
}
OBFUSC_KEY = b"random-build-salt"
obfuscated = {
k: xor_obfuscate(v, OBFUSC_KEY)
for k, v in config.items()
}
# at runtime - deobfuscate
runtime_config = {
k: xor_deobfuscate(v, OBFUSC_KEY)
for k, v in obfuscated.items()
}pbkdf2$<iterations>$<salt_b64url>$<hash_b64url>
Example:
pbkdf2$200000$aGVsbG93b3JsZA$dGhpc2lzYWhhc2g
<payload_b64>.<signature_b64>
Payload format: k=v;k2=v2;exp=<unix_ms>
salt(16) + nonce(12) + ciphertext + mac(32) → base64url
Header:
MAGIC: 'FCRT' (4 bytes)
VERSION: 1 (1 byte)
salt: 16 bytes
gnonce: 12 bytes
chunk_size: 4 bytes (uint32 big-endian)
iterations: 4 bytes (uint32)
num_chunks: 4 bytes (uint32)
Body:
[chunk_len(4)][ciphertext][mac(32)] × num_chunks
Footer:
global_mac: 32 bytes
| component | algorithm | parameters |
|---|---|---|
| password hashing | PBKDF2-HMAC-SHA256 | 200k iterations, 16-byte salt |
| key derivation | PBKDF2-HMAC-SHA256 | 100k iterations |
| mac | HMAC-SHA256 | 32-byte output |
| encryption | hmac-based stream | 12-byte nonce |
| random | os.urandom |
cryptographically secure |
| comparison | hmac.compare_digest |
constant-time |
✅ Constant-time password/MAC verification
✅ Secure random generation (os.urandom)
✅ Authenticated encryption (encrypt-then-MAC)
✅ Perchunk + global MAC in parallel mode
✅ Salt + nonce included in ciphertext
Warning
- Cipher uses HMAC-SHA256 stream, not AES-GCM or ChaCha20-Poly1305
- No hardware acceleration (AES-NI)
- PBKDF2 is weaker than Argon2id against GPU attacks
- XOR obfuscation provides minimal security
[Not a replacement for audited libraries]
| concern | recommendation |
|---|---|
| production systems | migrate to cryptography or pynacl |
| password hashing | use Argon2id for new passwords |
| key storage | use vault/secret manager (AWS Secrets Manager, HashiCorp Vault) |
| transport | always use TLS/HTTPS |
| key rotation | implement version field in tokens |
| auditing | fuzz test critical paths |
# ❌ NO
key = b"hardcoded-secret" # committed to git
token = create_token(payload, key)
# ✅ YES
key = os.environ["SESSION_SECRET"].encode()
if len(key) < 32:
raise ValueError("SECRET must be 32+ bytes")# generate secure secret
$ python -c "import os; print(os.urandom(32).hex())"
# set environment
export YUREI_IDS_SESSION_SECRET="your-64-char-hex-string"
export YUREI_IDS_DB_PATH="/secure/path/users.db"FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install -e .
# restrict permissions
RUN chmod 600 /app/data.db
ENV YUREI_IDS_SESSION_SECRET=""
CMD ["python", "main.py"]| scenario | recommendation |
|---|---|
| files < 1MB | use encrypt_bytes() |
| files > 10MB | use encrypt_parallel() with 4+ workers |
| chunk size | 128KB-512KB (balance memory/speed) |
| iterations | 200k for passwords, 100k for KDF |
| database | use WAL mode for SQLite (PRAGMA journal_mode=WAL) |
# BEFORE (yurei)
from yurei import encrypt_bytes, decrypt_bytes
blob = encrypt_bytes(data, key)
# AFTER (cryptography)
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
cipher = ChaCha20Poly1305(key)
blob = cipher.encrypt(nonce, data, None)# new users: argon2
import argon2
ph = argon2.PasswordHasher()
hash = ph.hash(password)
# existing users: keep yurei hashes, migrate on next login
if stored_hash.startswith("pbkdf2$"):
if verify_password(stored_hash, password):
# update to argon2
new_hash = ph.hash(password)
store.set(user_id, {"pwd": new_hash})Contributions welcome! Please read CONTRIBUTING.md before submitting PRs.
Priority areas:
- Comprehensive test suite (pytest)
- Benchmark suite (encryption throughput)
- Streaming encryption API
- Key rotation utilities
- Optional Argon2id support