Skip to main content

hashward

Modern password hashing for Python. A drop-in replacement for passlib, which has been unmaintained since 2020 and is broken on Python 3.13+.

CI PyPI version Python versions License: MIT

Why hashward?

  • passlib is dead. Last release was October 2020. It crashes on Python 3.13+ due to the removed crypt module.
  • Zero required dependencies. Pure Python implementations for all hashlib-based schemes. Optional deps for argon2 and bcrypt.
  • Python 3.9+ only. No Python 2 baggage. Full type hints with py.typed marker.
  • passlib-compatible API. CryptContext, hash identification, and verify_and_update() all work the same way.
  • Secure defaults. Argon2id as the default scheme with safe parameter defaults and timing-safe comparisons.

Installation

pip install hashward

With argon2 support (recommended):

pip install hashward[argon2]

With bcrypt support:

pip install hashward[bcrypt]

With everything:

pip install hashward[all]

Quick Start

Simple hashing and verification

import hashward

# Hash a password (uses argon2id by default)
hashed = hashward.hash("my secret password")
# '$argon2id$v=19$m=65536,t=2,p=2$...'

# Verify a password
hashward.verify("my secret password", hashed)   # True
hashward.verify("wrong password", hashed)        # False

# Use a specific scheme
hashed = hashward.hash("my secret password", scheme="bcrypt")
# '$2b$12$...'

CryptContext for policy management

from hashward import CryptContext

# Create a context with your preferred schemes
ctx = CryptContext(
    schemes=["argon2", "bcrypt", "pbkdf2_sha256"],
    default="argon2",
    deprecated=["pbkdf2_sha256"],
    argon2__time_cost=3,
    argon2__memory_cost=65536,
)

# Hash and verify
hashed = ctx.hash("password")
assert ctx.verify("password", hashed)

# Automatic scheme identification
ctx.identify(hashed)  # "argon2"

# Check if a hash needs upgrading (deprecated scheme or outdated params)
ctx.needs_update(hashed)  # False (it's current)

# Verify and get a new hash if the old one needs upgrading
valid, new_hash = ctx.verify_and_update("password", old_bcrypt_hash)
if valid and new_hash:
    # Save new_hash to your database — it's been upgraded to argon2
    save_to_db(new_hash)

Hash identification

from hashward import identify

identify("$argon2id$v=19$m=65536,t=2,p=2$...")  # "argon2"
identify("$2b$12$...")                            # "bcrypt"
identify("$6$rounds=656000$...")                  # "sha512_crypt"
identify("$1$...")                                # "md5_crypt"
identify("pbkdf2_sha256$600000$...")              # "django_pbkdf2_sha256"

INI configuration (passlib-compatible)

from hashward import CryptContext

# Load from INI-format string
config = """
[hashward]
schemes = argon2, bcrypt, pbkdf2_sha256
default = argon2
deprecated = pbkdf2_sha256
argon2__time_cost = 3
argon2__memory_cost = 65536
"""
ctx = CryptContext.from_string(config)

# Serialize back
print(ctx.to_string())

Supported Schemes

Modern (recommended for new hashes)

Scheme Backend Notes
argon2 argon2-cffi Default. Argon2id, memory-hard. Requires pip install hashward[argon2].
bcrypt bcrypt Industry standard. Requires pip install hashward[bcrypt].
bcrypt_sha256 bcrypt Bcrypt with SHA-256 pre-hash (no 72-byte limit).
scrypt hashlib.scrypt (stdlib) Memory-hard. No extra dependencies.
pbkdf2_sha256 hashlib.pbkdf2_hmac (stdlib) NIST-approved. No extra dependencies.
pbkdf2_sha512 hashlib.pbkdf2_hmac (stdlib) NIST-approved. No extra dependencies.

Legacy (verification and migration only)

Scheme Notes
sha512_crypt Common in /etc/shadow. Pure Python, no crypt module needed.
sha256_crypt Common in /etc/shadow. Pure Python, no crypt module needed.
md5_crypt Insecure. Always reports needs_update.
des_crypt Insecure. Always reports needs_update.

Django compatibility

Scheme Notes
django_pbkdf2_sha256 Django's default hasher format.
django_bcrypt Django bcrypt format.
django_bcrypt_sha256 Django bcrypt+SHA-256 format.
django_argon2 Django argon2 format.
django_scrypt Django scrypt format.

Migrating from passlib

hashward provides a compatibility layer for gradual migration.

Step 1: Replace imports

# Before (passlib):
from passlib.context import CryptContext

# After (hashward — direct):
from hashward import CryptContext

# Or use the compat shim for minimal changes:
from hashward.compat.passlib import CryptContext

Step 2: Update context configuration

# passlib config:
ctx = CryptContext(schemes=["argon2", "bcrypt", "pbkdf2_sha256"])

# hashward config (identical API):
ctx = CryptContext(schemes=["argon2", "bcrypt", "pbkdf2_sha256"])

Step 3: Verify existing hashes still work

hashward can verify hashes generated by passlib. All passlib hash formats are recognized, including bcrypt_sha256 v1 and v2 formats.

from hashward import CryptContext

ctx = CryptContext(schemes=["argon2", "bcrypt", "pbkdf2_sha256"], deprecated=["pbkdf2_sha256"])

# Verify a passlib-generated hash
old_hash = "$pbkdf2-sha256$29000$..."  # generated by passlib
valid = ctx.verify("password", old_hash)

# Automatically upgrade old hashes
valid, new_hash = ctx.verify_and_update("password", old_hash)
if valid and new_hash:
    # new_hash is argon2id — save it
    save_to_db(new_hash)

API Reference

Module-level functions

hashward.hash(secret, scheme="argon2")

Hash a password using the specified scheme (default: argon2).

hashward.verify(secret, hash_string)

Verify a password against a hash string. Automatically identifies the scheme.

hashward.identify(hash_string)

Detect the hashing scheme from a hash string. Returns the scheme name or None.

CryptContext

CryptContext(schemes, default, deprecated, **settings)

Create a policy manager for password hashing.

  • schemes — list of allowed scheme names
  • default — scheme to use for new hashes
  • deprecated — list of schemes that trigger needs_update()
  • min_verify_time — minimum wall-clock seconds for verify() (default: 0, disabled). Useful for preventing timing-based user enumeration.
  • truncate_error — if True, raise PasswordValueError when a bcrypt password exceeds 72 bytes instead of silently truncating (default: False)
  • **settings — per-scheme settings using scheme__param=value syntax

ctx.hash(secret, scheme=None, **settings)

Hash a password. Uses the default scheme unless overridden.

ctx.verify(secret, hash)

Verify a password against a hash. Returns False for unrecognized hashes.

ctx.identify(hash)

Identify the scheme of a hash string. Only returns schemes configured in this context.

ctx.needs_update(hash)

Check if a hash needs re-hashing (deprecated scheme or outdated parameters).

ctx.verify_and_update(secret, hash)

Verify and return (valid, new_hash). new_hash is None if no update is needed.

ctx.using(**overrides)

Return a new CryptContext with overridden settings.

ctx.copy(**overrides)

Alias for ctx.using().

ctx.to_string() / CryptContext.from_string(ini_str)

Serialize to / deserialize from INI-format configuration strings.

Security Considerations

  • Default scheme is argon2id with memory-hard parameters (64 MiB, 2 iterations, 2 threads).
  • Timing-safe comparisons via hmac.compare_digest for all hash verification.
  • No crypt module dependency. All legacy schemes (SHA-crypt, MD5-crypt, DES-crypt) use pure Python implementations.
  • DES-crypt always reports needs_update. Any existing DES-crypt hash will trigger automatic re-hashing on the next verify_and_update() call. Do not use it for new hashes.
  • bcrypt 72-byte limit is handled: passwords are silently truncated. Use bcrypt_sha256 or truncate_error=True in CryptContext for explicit control.

Development

# Clone and set up
git clone https://github.com/agentine/hashward.git
cd hashward
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

# Run tests
pytest tests/ -v

# Run benchmarks
python benchmarks/bench_hashing.py

License

MIT

Metadata

Release files for hashward 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hashward 0.1.0
File Size Uploaded
hashward-0.1.0.tar.gz 34.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for hashward 0.1.0
File Interpreter ABI Platform
hashward-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 63.9 kB

Release files / hashward-0.1.0.tar.gz

Download URL hashward-0.1.0.tar.gz
Size 34.6 kB
Tags Source
SHA-256 checksum
How to use checksums
af549cd0d7c64f9e5e54be85d87fa4c0ad896037846b37316eaabb6f28196d42
BLAKE2b-256 checksum
How to use checksums
b4324a9c4f286dab082719dd433da65c97a5e9d26fe445009e60b674abb3a569
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 12, 2026.

Transparency log

Release files / hashward-0.1.0-py3-none-any.whl

Download URL hashward-0.1.0-py3-none-any.whl
Size 29.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5ac6a41126f133364741e1d07160cfd7d71da27905340e7c8e5af7671846985f
BLAKE2b-256 checksum
How to use checksums
86fa55978c91cb46a3838162cede13d7269d39f3fe4ab9fafe56c85f0aa1f043
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page