Python’s crypt module (Unix only, deprecated Python 3.11, removed Python 3.13) wraps the C library crypt(3) function to hash passwords in the Modular Crypt Format used by /etc/shadow. import crypt. Hash: crypt.crypt("password", crypt.mksalt(crypt.METHOD_SHA512)) → "$6$salt$hash...". Verify: crypt.crypt("password", stored_hash) == stored_hash — passing the stored hash as the salt re-derives the same hash. Methods: crypt.methods — list of supported methods, strongest first; crypt.METHOD_SHA512 → $6$, crypt.METHOD_SHA256 → $5$, crypt.METHOD_MD5 → $1$, crypt.METHOD_CRYPT → DES (insecure). Salt: crypt.mksalt(method) → random salt string. The $id$salt$hash format: id is the algorithm identifier (6=SHA-512, 5=SHA-256, 1=MD5, 2a=bcrypt). Always use hmac.compare_digest() for constant-time comparison. For new code use hashlib.scrypt(), hashlib.pbkdf2_hmac(), or passlib. Claude Code generates Unix password validators, login authenticators, passwd file updaters, and shadow file auditors.
CLAUDE.md for crypt
## crypt Stack
- Stdlib: import crypt, hmac (Unix, deprecated 3.11, removed 3.13)
- Hash: crypt.crypt("pass", crypt.mksalt(crypt.METHOD_SHA512))
- Verify: hmac.compare_digest(
- crypt.crypt("pass", stored_hash), stored_hash)
- Methods: crypt.methods # [METHOD_SHA512, SHA256, MD5, CRYPT]
- Salt: crypt.mksalt(crypt.METHOD_SHA512)
- Format: $6$salt$hash (SHA-512) $5$ (SHA-256) $1$ (MD5) DES (CRYPT)
- Note: Removed 3.13; use passlib.hash or hashlib.scrypt for new code
crypt Password Hashing Pipeline
# app/cryptutil.py — hash, verify, benchmark, policy, bulk audit, migration
from __future__ import annotations
import hmac
import os
import time
from dataclasses import dataclass, field
_CRYPT_AVAILABLE = False
try:
import crypt as _crypt
_CRYPT_AVAILABLE = True
except ImportError:
pass
# Modern fallback: hashlib (always available)
import hashlib
import secrets
import base64
# ─────────────────────────────────────────────────────────────────────────────
# 1. Quick hash / verify helpers
# ─────────────────────────────────────────────────────────────────────────────
def hash_password(password: str, method: str = "sha512") -> str:
"""
Hash a password using the strongest available method.
method: 'sha512' | 'sha256' | 'md5' | 'des' | 'scrypt' (fallback)
Example:
h = hash_password("s3cr3t")
h = hash_password("s3cr3t", method="scrypt") # modern fallback
"""
if _CRYPT_AVAILABLE:
method_map = {
"sha512": _crypt.METHOD_SHA512,
"sha256": _crypt.METHOD_SHA256,
"md5": _crypt.METHOD_MD5,
"des": _crypt.METHOD_CRYPT,
}
m = method_map.get(method, _crypt.METHOD_SHA512)
salt = _crypt.mksalt(m)
return _crypt.crypt(password, salt)
else:
return _scrypt_hash(password)
def verify_password(password: str, stored_hash: str) -> bool:
"""
Verify a plaintext password against a stored hash.
Uses constant-time comparison to prevent timing attacks.
Example:
h = hash_password("secret")
assert verify_password("secret", h)
assert not verify_password("wrong", h)
"""
if stored_hash.startswith("$scrypt$"):
return _scrypt_verify(password, stored_hash)
if not _CRYPT_AVAILABLE:
return False
try:
candidate = _crypt.crypt(password, stored_hash)
return hmac.compare_digest(candidate, stored_hash)
except Exception:
return False
# ─────────────────────────────────────────────────────────────────────────────
# 2. Modern scrypt fallback (hashlib, no crypt dependency)
# ─────────────────────────────────────────────────────────────────────────────
def _scrypt_hash(password: str,
n: int = 2**14, r: int = 8, p: int = 1) -> str:
"""Hash a password with scrypt; returns a portable $scrypt$... string."""
salt = secrets.token_bytes(32)
dk = hashlib.scrypt(
password.encode("utf-8"), salt=salt, n=n, r=r, p=p, dklen=32)
salt_b64 = base64.b64encode(salt).decode("ascii")
dk_b64 = base64.b64encode(dk).decode("ascii")
return f"$scrypt${n}${r}${p}${salt_b64}${dk_b64}"
def _scrypt_verify(password: str, stored: str) -> bool:
"""Verify a password against a $scrypt$... hash string."""
try:
parts = stored.split("$")
# $scrypt$N$r$p$salt_b64$dk_b64
n, r, p = int(parts[2]), int(parts[3]), int(parts[4])
salt = base64.b64decode(parts[5])
dk_stored = base64.b64decode(parts[6])
dk_candidate = hashlib.scrypt(
password.encode("utf-8"), salt=salt, n=n, r=r, p=p, dklen=32)
return hmac.compare_digest(dk_candidate, dk_stored)
except Exception:
return False
# ─────────────────────────────────────────────────────────────────────────────
# 3. MCF (Modular Crypt Format) parser
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class McfInfo:
raw_hash: str
algorithm: str # "sha512", "sha256", "md5", "des", "bcrypt", "unknown"
salt: str
hash_part: str
is_locked: bool # True if hash starts with "!" or "*"
# Algorithm identifier → name
_MCF_IDS = {
"6": "sha512",
"5": "sha256",
"1": "md5",
"2a": "bcrypt",
"2b": "bcrypt",
"2y": "bcrypt",
"y": "yescrypt",
"gy": "gost-yescrypt",
}
def parse_mcf(stored_hash: str) -> McfInfo:
"""
Parse a Modular Crypt Format hash string.
Example:
info = parse_mcf("$6$salt$hash...")
print(info.algorithm) # "sha512"
"""
locked = stored_hash.startswith("!") or stored_hash.startswith("*")
cleaned = stored_hash.lstrip("!")
if not cleaned.startswith("$"):
return McfInfo(stored_hash, "des", cleaned[:2], cleaned[2:], locked)
parts = cleaned.split("$")
if len(parts) < 4:
return McfInfo(stored_hash, "unknown", "", cleaned, locked)
algo_id = parts[1]
algo = _MCF_IDS.get(algo_id, f"id_{algo_id}")
salt = parts[2] if len(parts) > 2 else ""
hash_part = parts[3] if len(parts) > 3 else ""
return McfInfo(stored_hash, algo, salt, hash_part, locked)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Password policy checker
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class PolicyResult:
ok: bool
score: int # 0–5
issues: list[str] = field(default_factory=list)
def check_password_policy(password: str,
min_length: int = 8,
require_upper: bool = True,
require_digit: bool = True,
require_special: bool = True) -> PolicyResult:
"""
Check a plaintext password against a configurable policy.
Example:
r = check_password_policy("Secret1!")
print(r.ok, r.score, r.issues)
"""
issues: list[str] = []
score = 0
if len(password) >= min_length:
score += 1
else:
issues.append(f"too short (min {min_length} chars)")
if any(c.isupper() for c in password):
score += 1
elif require_upper:
issues.append("missing uppercase letter")
if any(c.islower() for c in password):
score += 1
if any(c.isdigit() for c in password):
score += 1
elif require_digit:
issues.append("missing digit")
special = set("!@#$%^&*()_+-=[]{}|;':\",./<>?")
if any(c in special for c in password):
score += 1
elif require_special:
issues.append("missing special character")
return PolicyResult(ok=len(issues) == 0, score=score, issues=issues)
# ─────────────────────────────────────────────────────────────────────────────
# 5. Bulk hash upgrader
# ─────────────────────────────────────────────────────────────────────────────
def upgrade_hash(stored_hash: str, password: str) -> str | None:
"""
If stored_hash uses a weak algorithm (DES, MD5), verify the password
and return a new SHA-512 hash. Returns None if verification fails
or the hash is already strong.
Example:
new_hash = upgrade_hash(old_md5_hash, "password") or old_md5_hash
"""
info = parse_mcf(stored_hash)
if info.algorithm in ("sha512", "sha256", "bcrypt", "yescrypt", "scrypt"):
return None # already strong
if verify_password(password, stored_hash):
return hash_password(password, method="sha512")
return None
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== crypt demo ===")
print(f" crypt available: {_CRYPT_AVAILABLE}")
if _CRYPT_AVAILABLE:
print(f" methods: {[m.name for m in _crypt.methods]}")
# ── hash_password / verify_password ───────────────────────────────────────
print("\n--- hash + verify ---")
for pw in ["secret", "P@ssw0rd!", ""]:
h = hash_password(pw)
ok = verify_password(pw, h)
wrong = verify_password("wrong", h)
algo = parse_mcf(h).algorithm
print(f" {pw!r:12s} algo={algo:8s} match={ok} wrong-match={wrong}")
# ── scrypt fallback ────────────────────────────────────────────────────────
print("\n--- scrypt fallback ---")
h = _scrypt_hash("fallback_test")
print(f" hash prefix: {h[:30]}...")
print(f" verify ok : {_scrypt_verify('fallback_test', h)}")
print(f" verify bad : {_scrypt_verify('wrongpassword', h)}")
# ── parse_mcf ──────────────────────────────────────────────────────────────
print("\n--- parse_mcf ---")
test_hashes = [
hash_password("test", method="sha512") if _CRYPT_AVAILABLE else _scrypt_hash("test"),
"!$6$lockedsalt$lockedhash",
"$5$rounds=5000$salt$sha256hash",
"AaBbccDD", # DES
]
for h in test_hashes:
info = parse_mcf(h)
print(f" {info.algorithm:12s} locked={info.is_locked} "
f"salt={info.salt[:10]!r}")
# ── policy check ───────────────────────────────────────────────────────────
print("\n--- policy check ---")
for pw in ["abc", "Abcdef1!", "password", "S3cr3t!X"]:
r = check_password_policy(pw)
status = "OK" if r.ok else ", ".join(r.issues)
print(f" {pw!r:12s} score={r.score} {status}")
print("\n=== done ===")
For the passlib (PyPI) replacement — passlib.hash.sha512_crypt.hash("password") and passlib.hash.sha512_crypt.verify("password", stored_hash) implement the full range of Unix and application hashing schemes (bcrypt, Argon2, scrypt, PBKDF2, SHA-512-crypt, MD5-crypt) with a unified API across platforms — use passlib for all production password hashing; crypt was deprecated in 3.11 and removed in 3.13 and is Unix-only. For the hashlib.scrypt stdlib alternative — hashlib.scrypt(password.encode(), salt=os.urandom(32), n=2**14, r=8, p=1, dklen=32) is available since Python 3.6 (with OpenSSL support) and provides a memory-hard KDF suitable for password storage — use hashlib.scrypt or hashlib.pbkdf2_hmac for new password hashing code when you cannot use passlib; combine with secrets.token_bytes(32) for the salt and hmac.compare_digest for constant-time verification. The Claude Skills 360 bundle includes crypt skill sets covering hash_password()/verify_password() round-trip helpers, _scrypt_hash()/_scrypt_verify() modern fallback, McfInfo/parse_mcf() Modular Crypt Format parser, PolicyResult/check_password_policy() policy validator, and upgrade_hash() weak-hash upgrader. Start with the free tier to try Unix password hashing patterns and crypt pipeline code generation.