Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Zozi96/hash-forge/llms.txt

Use this file to discover all available pages before exploring further.

Hash Forge provides a full async API for every core operation. Under the hood, each async method calls asyncio.get_running_loop().run_in_executor(None, ...) to offload the CPU-bound hash computation to a thread-pool executor. This means the event loop is never blocked while a hash is being computed — your server stays responsive to other requests even during expensive Argon2 or bcrypt operations.
All async methods are inherited from AsyncHashMixin. Every HashManager instance includes them automatically — no additional setup or subclassing is required.

Available Async Methods

MethodSignatureDescription
hash_async(string: str) -> strHash with the preferred hasher, non-blocking
verify_async(string: str, hashed_string: str) -> boolVerify against a stored hash, non-blocking
needs_rehash_async(hashed_string: str) -> boolCheck if rehash is needed, non-blocking
hash_many_async(strings: list[str]) -> list[str]Hash a list of strings concurrently
verify_many_async(pairs: list[tuple[str, str]]) -> list[bool]Verify a list of pairs concurrently
All async methods run the underlying synchronous operation in a thread-pool executor via asyncio.get_running_loop().run_in_executor. This keeps the asyncio event loop free for I/O tasks while the CPU-bound hash work runs on a worker thread.

FastAPI Integration

The example below shows register and login endpoints that use hash_async and verify_async so neither endpoint blocks the event loop.
import asyncio
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
from hash_forge import HashManager

app = FastAPI(title="Hash Forge Auth API", version="1.0.0")

# Initialize HashManager with Argon2 (recommended for web apps)
hash_manager = HashManager.from_algorithms("argon2")

# In-memory user database (use a real DB in production)
users_db: dict[str, dict] = {}


class UserRegister(BaseModel):
    username: str = Field(..., min_length=3, max_length=50)
    password: str = Field(..., min_length=8)
    email: str


class UserLogin(BaseModel):
    username: str
    password: str


class UserResponse(BaseModel):
    username: str
    email: str
    message: str


@app.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def register(user: UserRegister):
    """Register a new user with async password hashing."""
    if user.username in users_db:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Username already exists",
        )

    # Hash password asynchronously — does not block the event loop
    password_hash = await hash_manager.hash_async(user.password)

    users_db[user.username] = {
        "email": user.email,
        "password_hash": password_hash,
    }

    return UserResponse(
        username=user.username,
        email=user.email,
        message="User registered successfully",
    )


@app.post("/login", response_model=UserResponse)
async def login(credentials: UserLogin):
    """Login user with async password verification."""
    user = users_db.get(credentials.username)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid username or password",
        )

    # Verify password asynchronously — does not block the event loop
    is_valid = await hash_manager.verify_async(
        credentials.password,
        user["password_hash"],
    )

    if not is_valid:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid username or password",
        )

    # Transparently upgrade the hash if parameters changed
    needs_rehash = await hash_manager.needs_rehash_async(user["password_hash"])
    if needs_rehash:
        new_hash = await hash_manager.hash_async(credentials.password)
        users_db[credentials.username]["password_hash"] = new_hash

    return UserResponse(
        username=credentials.username,
        email=user["email"],
        message="Login successful",
    )

Batch Hashing

hash_many_async hashes a list of strings concurrently. Internally it builds a list of hash_async coroutines and runs them together with asyncio.gather, so all hashes are computed in parallel on the thread pool.
import asyncio
from hash_forge import HashManager

async def batch_example():
    manager = HashManager.from_algorithms("pbkdf2_sha256")

    passwords = ["user1_pass", "user2_pass", "user3_pass", "user4_pass"]

    # All four hashes computed concurrently
    hashes = await manager.hash_many_async(passwords)

    for password, hash_value in zip(passwords, hashes):
        print(f"{password} -> {hash_value[:50]}...")

    # Verify multiple pairs concurrently
    pairs = [
        ("user1_pass", hashes[0]),
        ("user2_pass", hashes[1]),
        ("wrong_password", hashes[2]),  # will be False
    ]
    results = await manager.verify_many_async(pairs)
    print(results)  # [True, True, False]


asyncio.run(batch_example())
The batch-register endpoint below shows this pattern applied inside a FastAPI route:
@app.post("/batch-register")
async def batch_register(users: list[UserRegister]):
    """Register multiple users with concurrent password hashing."""
    for user in users:
        if user.username in users_db:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Username {user.username} already exists",
            )

    # Hash all passwords concurrently — much faster than sequential
    passwords = [user.password for user in users]
    password_hashes = await hash_manager.hash_many_async(passwords)

    registered = []
    for user, password_hash in zip(users, password_hashes):
        users_db[user.username] = {
            "email": user.email,
            "password_hash": password_hash,
        }
        registered.append(user.username)

    return {
        "message": f"Successfully registered {len(registered)} users",
        "users": registered,
    }
For bulk operations outside of a web request — such as a one-off data migration — you can combine hash_many_async with asyncio.gather across multiple batches to maximise throughput. Async batch processing is typically 3–5x faster than sequential hashing for 10 passwords, and 8–10x faster at 100 passwords.

Basic Async Example

If you’re not using a web framework, run async code directly with asyncio.run:
import asyncio
from hash_forge import HashManager

async def main():
    manager = HashManager.from_algorithms("argon2")

    # Non-blocking hash
    hashed = await manager.hash_async("my_password")
    print(f"Hashed: {hashed}")

    # Non-blocking verify
    is_valid = await manager.verify_async("my_password", hashed)
    print(f"Valid: {is_valid}")   # True

    # Non-blocking rehash check
    needs_rehash = await manager.needs_rehash_async(hashed)
    print(f"Needs rehash: {needs_rehash}")  # False


asyncio.run(main())

Build docs developers (and LLMs) love