Claude Code for crypt: Python Unix Password Hashing — Claude Skills 360 Blog
Blog / AI / Claude Code for crypt: Python Unix Password Hashing
AI

Claude Code for crypt: Python Unix Password Hashing

Published: January 23, 2029
Read time: 5 min read
By: Claude Skills 360

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.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free