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.

As cryptographic best practices evolve, you will eventually need to upgrade stored password hashes to a stronger algorithm or higher cost parameters. Running a bulk migration script is impractical — it requires the plaintext password, which is never stored. Hash Forge’s approach is on-login migration: every time a user authenticates, the stored hash is verified and, if it does not meet current standards, silently re-hashed with the preferred algorithm before the response is returned. Users experience no disruption, and old hashes are phased out naturally over time.

Migration Methods

rotate(string, old_hash) -> str | None

rotate() verifies the plaintext string against old_hash. If verification succeeds it immediately produces a new hash with the preferred hasher and returns it. If verification fails it returns None — no exception is raised, making it safe to use directly as the return value of a login check.
from hash_forge import HashManager

manager = HashManager.from_algorithms("argon2", "pbkdf2_sha256")

old_hash = get_stored_hash(user_id)      # a pbkdf2_sha256 hash
new_hash = manager.rotate(user_password, old_hash)

if new_hash is not None:
    save_hash(user_id, new_hash)         # now stored as argon2
    print("Password migrated to argon2")
else:
    print("Wrong password — login failed")
rotate() returns None when verification fails. Check the return value rather than treating a non-None result as proof of identity — the method’s responsibility is migration, not authentication state.

verify_and_update(string, hashed_string) -> tuple[bool, str | None]

verify_and_update() returns a two-element tuple:
  • boolTrue if the password matched the stored hash, False otherwise
  • str | None — a new hash if needs_rehash() returns True, otherwise None
If verification fails, the second element is always None. Use this method when you need the authentication result and the migration signal as separate values.
is_valid, new_hash = manager.verify_and_update(user_password, stored_hash)

if not is_valid:
    return False

if new_hash is not None:
    save_to_db(new_hash)   # upgrade stored hash

return True

Step-by-Step Migration Guide

1
Set up a multi-algorithm manager
2
Create a HashManager with the target algorithm first (it becomes the preferred hasher) and all legacy algorithms you need to support for verification.
3
from hash_forge import HashManager

manager = HashManager.from_algorithms("argon2", "pbkdf2_sha256", "bcrypt")
# preferred: argon2
# can verify: pbkdf2_sha256, bcrypt
4
Call verify_and_update on every login
5
Replace your existing password-check logic with a verify_and_update call.
6
is_valid, new_hash = manager.verify_and_update(password, stored_hash)

if not is_valid:
    return login_failed()
7
Persist the upgraded hash when provided
8
If new_hash is not None, the stored hash was either produced by a legacy algorithm or with outdated parameters. Save the new hash to the database before returning.
9
if new_hash is not None:
    save_to_db(new_hash)   # transparently upgraded to argon2
10
Old hashes are replaced as users log in
11
No bulk migration script is needed. Each user’s hash is upgraded the next time they authenticate. Users who have not logged in since the migration still have their old hash in the database and can log in normally — they are upgraded on their next login.

Complete Login Function Example

from hash_forge import HashManager

manager = HashManager.from_algorithms("argon2", "pbkdf2_sha256", "bcrypt")


def login(password: str, stored_hash: str) -> bool:
    is_valid, new_hash = manager.verify_and_update(password, stored_hash)

    if not is_valid:
        return False

    if new_hash is not None:
        save_to_db(new_hash)  # Upgraded to Argon2

    return True

Policy-Based Migration

PasswordHashPolicy.recommended() bundles the same multi-algorithm setup used above into a single reusable profile. Use it with HashManager.from_policy() for a more declarative approach.
from hash_forge import HashManager, PasswordHashPolicy

manager = HashManager.from_policy(PasswordHashPolicy.recommended())
# preferred: argon2
# can verify: pbkdf2_sha256, bcrypt_sha256, scrypt


def login(password: str, stored_hash: str) -> bool:
    is_valid, new_hash = manager.verify_and_update(password, stored_hash)
    if not is_valid:
        return False
    if new_hash is not None:
        save_to_db(new_hash)
    return True
If your system holds hashes from many different historical algorithms — including RIPEMD-160 or PBKDF2-SHA1 — use PasswordHashPolicy.legacy_compat() instead of recommended(). It includes a wider set of legacy algorithms and explicitly sets allow_legacy_verify=True.

Auditing Hashes Before Migration

Before rolling out a migration, use inspect() to understand the distribution of algorithms and parameters currently stored in your database.
from hash_forge import HashManager, PasswordHashPolicy

manager = HashManager.from_policy(PasswordHashPolicy.legacy_compat())

stored_hashes = fetch_all_hashes_from_db()

for h in stored_hashes:
    info = manager.inspect(h)
    if info:
        print(info)
        # e.g. {'algorithm': 'pbkdf2_sha256', 'category': 'password',
        #        'deprecated': False, 'iterations': 150000}
        # e.g. {'algorithm': 'bcrypt', 'category': 'password',
        #        'deprecated': False, 'rounds': 12}
inspect() returns None for any hash that no registered hasher recognises, which helps identify corrupt or completely unknown records before you start the migration.

Explicit Rehash Check

When you want to check whether a hash needs upgrading without verifying the password (for example, in a background audit job), use needs_rehash() directly:
from hash_forge import HashManager, PasswordHashPolicy

manager = HashManager.from_policy(PasswordHashPolicy.recommended())

legacy_pbkdf2_hash = get_stored_hash(user_id)

if manager.needs_rehash(legacy_pbkdf2_hash):
    print("This hash should be migrated on next login")
needs_rehash() returns True when:
  • The algorithm used to produce the hash is not the preferred algorithm, or
  • The hash was produced with parameters that no longer match the current hasher configuration (e.g. a lower iteration count)

Build docs developers (and LLMs) love