Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Password Hashing Argon2id vs Bcrypt in 2026

By Kokil Thapa | Last reviewed: September 2026

Choosing between Password Hashing Argon2id vs Bcrypt in 2026 is not a theoretical debate for PHP teams. Every login form, client portal, and API token store depends on a slow hash that survives GPU cracking if the database leaks. On production Laravel applications I maintain, the wrong default or stale bcrypt cost can leave credentials exposed for years. This guide compares Argon2id and bcrypt on current PHP 8.5 and Laravel 13 stacks, with copy-paste config, migration steps, and a practical verdict. For deeper Laravel-specific rehashing, see the Laravel password rehashing and Argon2id setup guide.

What Is the Difference Between Argon2id and Bcrypt for Password Hashing?

Both algorithms turn a plaintext password into a one-way string. Neither can be reversed. An attacker who steals your user table must guess passwords and re-hash each guess until one matches.

Bcrypt, based on Blowfish, has been the PHP default for over a decade. It uses a cost factor that scales CPU time. Argon2 won the Password Hashing Competition in 2015. Argon2id blends Argon2i (side-channel resistance) and Argon2d (GPU resistance). That hybrid makes it the stronger choice on modern hardware.

Password Hashing: Core FlowPlaintextUser inputSlow HashArgon2id / bcryptStored HashDB columnBcryptCPU-bound cost factorMature, widely supported72-byte password limitNo memory hardnessArgon2idMemory + CPU costPHC winner, OWASP pickResists GPU farmsPHP 8.5 default driver
Password Hashing Argon2id vs Bcrypt in 2026 — both follow the same verify loop, but Argon2id adds memory hardness that bcrypt lacks.

The critical property is slowness. Login hashing should take roughly 200–500 ms per attempt on your production server. Fast hashes like SHA-256 let attackers test billions of guesses per second. Slow hashes cap that rate to hundreds or thousands.

PHP stores algorithm metadata inside the hash string itself. A bcrypt hash starts with $2y$. An Argon2id hash starts with $argon2id$. That prefix lets password_verify() pick the correct algorithm at login time.

How each algorithm resists attacks

  • Bcrypt: Adaptive work factor via the cost parameter (typically 10–12 in 2026).
  • Argon2id: Three tunable dimensions — time cost, memory cost, and parallelism.
  • Both: Per-password random salt embedded in the output string.
  • Neither: Suitable for session tokens or API keys — use random bytes and a fast MAC for those.

Which Password Hash Should You Use in PHP 8.5 and Laravel 13?

PHP 8.5 ships with Argon2id as the default PASSWORD_DEFAULT algorithm. Laravel 13 follows that direction in fresh installs. If your VPS runs Ubuntu 24 with PHP 8.5 and the sodium extension enabled, Argon2id is the right primary choice.

Bcrypt stays valid when Argon2 is unavailable. Some budget shared hosts still compile PHP without sodium support. In those cases, bcrypt at cost 12 is far better than leaving legacy MD5 hashes in place.

Laravel 13 hashing configuration

In config/hashing.php, set the driver explicitly rather than relying on silent defaults:

<?php

return [
    'driver' => env('HASH_DRIVER', 'argon2id'),

    'bcrypt' => [
        'rounds' => env('BCRYPT_ROUNDS', 12),
        'verify' => true,
    ],

    'argon' => [
        'memory' => 65536,   // 64 MiB in KiB
        'threads' => 1,
        'time' => 4,
        'verify' => true,
    ],
];

Run php artisan config:cache after changing values on production. I've seen stale config leave new installs on bcrypt while developers assumed Argon2id was active. Clear the config cache as part of every deploy pipeline.

Plain PHP without a framework

<?php

$hash = password_hash(
    $password,
    PASSWORD_ARGON2ID,
    ['memory_cost' => 65536, 'time_cost' => 4, 'threads' => 1]
);

if (password_verify($password, $hash)) {
    if (password_needs_rehash($hash, PASSWORD_ARGON2ID, $options)) {
        $hash = password_hash($password, PASSWORD_ARGON2ID, $options);
        /* persist upgraded hash */
    }
}

The official PHP manual documents every option flag for password_hash(). Cross-check your values there before pushing to production. The PHP password_hash documentation remains the authoritative reference.

Argon2id Tuning KnobsMemory Cost65536 KiB = 64 MiBTime Cost4 iterations typicalThreads1 on small VPSTarget: 200–500 ms per hashMeasure on production hardwareToo low = weakCracks faster under loadToo high = DoS riskLogin spikes exhaust RAM
Tune Argon2id memory, time, and thread costs until hashing sits in the 200–500 ms window on your actual server.

How Do Argon2id and Bcrypt Compare on Security and Performance?

The OWASP Password Storage Cheat Sheet recommends Argon2id as the first preference when available. Bcrypt is listed as an acceptable alternative. That ranking reflects real attack economics, not vendor marketing.

CriterionArgon2idBcrypt
GPU / ASIC resistanceStrong — memory-hard designModerate — CPU-only scaling
Side-channel resistanceStrong (hybrid i+d variant)Good for most web apps
Max password lengthPractically unlimited72 bytes (pre-hash if longer)
PHP 8.5 supportNative via sodium / PASSWORD_ARGON2IDNative via PASSWORD_BCRYPT
Hosting compatibilityNeeds sodium extensionWorks almost everywhere
Tuning dimensionsMemory, time, threadsCost rounds only
2026 industry defaultPreferred for new systemsLegacy-safe, still valid

On a 2-vCPU VPS with 4 GB RAM — common for Nepal SMB sites at Rs 1,500–3,000/month (~USD 11–22) — Argon2id at 64 MiB and time cost 4 typically lands near 300 ms. Bcrypt at cost 12 runs closer to 250 ms on the same box. The difference is negligible at login but meaningful against offline cracking.

Memory hardness is the decisive edge. A bcrypt hash on a stolen dump can be attacked with thousands of GPU cores. Argon2id forces each guess to allocate tens of megabytes of RAM. That bottleneck shrinks parallel cracking throughput by orders of magnitude.

Do not confuse password hashing with general encryption. Hashing is one-way verification. For document storage on legal-tech portals, use proper encryption at rest plus access controls. I've built client portals where password hashing and file encryption serve different layers of the same security model.

Benchmark on your own server

  1. SSH into production or a staging mirror with identical PHP-FPM settings.
  2. Run a short script that hashes a test password 20 times and prints average milliseconds.
  3. Adjust Argon2id memory or bcrypt rounds until the average sits between 200 and 500 ms.
  4. Re-test under load — run the script while Apache or Nginx serves normal traffic.
  5. Document the final values in your deploy notes and .env.example.

Server tuning affects hash timing as much as algorithm choice. PHP-FPM worker counts, opcache settings, and concurrent login spikes all matter. The Ubuntu server setup guide for PHP apps covers baseline VPS hardening that keeps auth endpoints stable.

How Should You Migrate Existing Bcrypt Hashes to Argon2id?

Never bulk-rehash the entire user table offline. You do not have plaintext passwords. The correct pattern is lazy rehashing at successful login.

Laravel's built-in authentication already supports this flow when you call Hash::needsRehash() after verification. Symfony and plain PHP use password_needs_rehash() the same way. On a legal-tech portal with thousands of dormant accounts, only active users get upgraded — and that is fine.

Migration workflow

  1. Confirm sodium and Argon2id support: php -r "echo PASSWORD_ARGON2ID;"
  2. Update config/hashing.php driver to argon2id.
  3. Add rehash logic in your login controller or Laravel user provider.
  4. Deploy during low-traffic hours and monitor PHP-FPM memory usage.
  5. Keep bcrypt verification working — old hashes must still validate until rehashed.

WordPress sites follow a different path. Core still defaults to bcrypt-based phpass unless a plugin switches algorithms. The WordPress security hardening checklist covers plugin-level upgrades without breaking existing logins.

Lazy Rehash MigrationUser loginVerify hashbcrypt OKNeeds rehash?Check paramsArgon2idSave new Argon2id hash to databaseOld bcrypt row replaced in placeNever do offline mass rehashPlaintext passwords are not storedUpgrade only after successful verify
Migrate bcrypt to Argon2id at login time — verify first, rehash second, persist the new hash immediately.

Client portals like Mijar Law Associates store sensitive credentials alongside document access. A botched migration that invalidates passwords generates support tickets and erodes trust. Test the rehash path on staging with cloned production data before you deploy.

What Common Password Hashing Mistakes Still Appear in Production?

Even teams that know better slip on implementation details. These failures show up in audits, penetration tests, and incident post-mortems.

Using fast hashes or reversible encoding

MD5, SHA-1, and SHA-256 were never designed for passwords. Salting a SHA-256 hash does not fix the speed problem. Base64 is encoding, not protection. If you inherit a legacy CodeIgniter or custom PHP app with unsalted MD5, plan an incremental upgrade rather than a big-bang rewrite.

Ignoring the bcrypt 72-byte limit

Passwords longer than 72 bytes are truncated silently by bcrypt. Pre-hash with SHA-256, then bcrypt the digest, if you must support passphrases. Argon2id avoids this footgun entirely. Test edge cases with the secure password generator tool to confirm your app handles long random strings.

Setting Argon2id memory too high on small VPS plans

A 256 MiB memory cost on a 1 GB RAM droplet can exhaust PHP-FPM workers during a login burst. Start at 64 MiB. Measure. Scale up only if headroom exists. Linux system administration support often starts with exactly this kind of memory profiling.

Skipping rate limiting and MFA

Strong hashing protects offline dumps. It does not stop online guessing against a live login form. Pair slow hashes with rate limiting, CAPTCHA after failures, and MFA for admin roles. The 2026 cybersecurity trends for developers article covers layered auth defence beyond hashing alone.

Hard-coding pepper without a rotation plan

A pepper — a server-side secret mixed before hashing — adds defence in depth. Losing the pepper invalidates every password. Store peppers in environment variables, not Git. Document rotation as a manual rehash project because you cannot recover plaintext.

2026 Algorithm DecisionNew PHP 8.5 project?YesUse Argon2idDefault in Laravel 13NoLegacy app?Lazy rehash pathNo sodium ext?Bcrypt cost 12Never MD5 / SHA-256 alone
Decision tree for Password Hashing Argon2id vs Bcrypt in 2026 — new builds favour Argon2id; legacy systems migrate lazily.

API-only backends need the same discipline. JWTs are not password stores. Hash credentials in your user service and issue short-lived tokens after verification. The REST API design best practices guide treats auth as a first-class architectural concern.

How Does Password Hashing Fit Into Broader Application Security?

Hashing is one layer in a stack that includes TLS, CSRF protection, session fixation guards, and secure cookie flags. On Court Marriage In Nepal and similar lead-capture portals, most users create passwords once and log in rarely. That usage pattern makes lazy rehashing ideal — returning visitors upgrade silently over months.

Enterprise apps with SSO may still store local passwords for fallback accounts. Keep those hashes on the same modern standard as primary credentials. Symfony 8.1 applications can configure the password_hasher service per user type — bcrypt for legacy imports, Argon2id for new records.

Compliance conversations in Nepal often reference international baselines rather than a single local password statute. Following the OWASP Password Storage Cheat Sheet satisfies most client security questionnaires I've seen on RFPs for enterprise application development work.

Testing belongs in CI. Add a PHPUnit or Pest test that asserts new registrations produce $argon2id$ prefixes. Add another that feeds a known bcrypt fixture through login and confirms the stored hash upgrades. Testing and optimization services often start with exactly these auth regression gaps.

Founders choosing a stack for a 2026 greenfield product should default to Laravel 13 or Symfony 8.1 on PHP 8.5 with Argon2id. The case for learning Laravel in 2026 includes built-in hashing conventions that save weeks of security plumbing.

If you maintain WooCommerce or Magento 2.4.x storefronts, customer passwords follow platform defaults unless you override them. Custom Laravel carts — like those built for Quick And Easy Nepalese Grocery — give you full control. Use it.

Key Takeaways

  • Prefer Argon2id on PHP 8.5 and Laravel 13 when sodium is available; bcrypt at cost 12 remains a safe fallback.
  • Tune hash duration to 200–500 ms on production hardware — measure, do not guess memory or cost values.
  • Migrate existing bcrypt hashes with lazy rehash at login via password_needs_rehash(), never offline bulk conversion.
  • Pair slow hashing with rate limiting, MFA for privileged roles, and TLS — hashing alone does not stop online attacks.
  • Test registration and login paths in CI to catch algorithm regressions before deploy.
  • Avoid MD5, SHA-256, and Base64 for password storage — they fail the offline cracking test immediately.

People Also Ask

Is Argon2id better than bcrypt in 2026?

Yes, for most new PHP applications. Argon2id's memory-hard design resists GPU-based offline cracking better than bcrypt. Bcrypt remains acceptable when Argon2 is unavailable or when you need maximum hosting compatibility without code changes.

What is the default password algorithm in PHP 8.5?

PHP 8.5 uses Argon2id as PASSWORD_DEFAULT. Calling password_hash($pw, PASSWORD_DEFAULT) produces an Argon2id hash when the sodium extension is compiled in. Verify with php -i | grep -i argon on your server.

Can Laravel verify old bcrypt passwords after switching to Argon2id?

Yes. Laravel's hasher detects the algorithm from the hash prefix. Bcrypt hashes continue to verify correctly. After login, Hash::needsRehash() returns true and you store a fresh Argon2id hash.

How long should password hashing take?

Target 200–500 milliseconds per hash on production hardware. Faster hashes weaken offline attack resistance. Slower hashes risk denial-of-service during traffic spikes and frustrate legitimate users at login.

Choose Argon2id, Keep Bcrypt as Fallback, and Ship Secure Logins

Password Hashing Argon2id vs Bcrypt in 2026 is settled for greenfield work: Argon2id wins on modern PHP stacks. Bcrypt is not broken — it is the pragmatic choice on constrained hosting and the bridge that keeps legacy logins working during migration. Pick Argon2id for new Laravel 13 and Symfony 8.1 projects, tune parameters on real hardware, and rehash bcrypt rows lazily at login.

Need auth hardened on an existing portal or a new custom software project? I help teams audit credential storage, configure hashing, and deploy without breaking user sessions. Contact us to review your login stack, or browse the Notary Nepal portfolio for an example of secure client-facing auth in production.

Frequently Asked Questions

Yes, for most new PHP applications on PHP 8.5 with sodium available. Argon2id's memory-hard design resists GPU offline cracking better than bcrypt. Bcrypt remains acceptable when Argon2 is unavailable or maximum hosting compatibility is required.

PHP 8.5 uses Argon2id as PASSWORD_DEFAULT. Calling password_hash with PASSWORD_DEFAULT produces an Argon2id hash when the sodium extension is compiled in.

Target 200–500 milliseconds per hash on your actual production hardware. Faster is weaker against offline cracking; slower risks login delays and PHP-FPM strain during traffic spikes.

Both turn plaintext passwords into one-way strings with per-password salts embedded in the output. Bcrypt, based on Blowfish, scales difficulty through a cost factor and stores hashes starting with $2y$. Argon2id, winner of the 2015 Password Hashing Competition, blends side-channel-resistant Argon2i with GPU-resistant Argon2d and stores hashes starting with $argon2id$. PHP's password_verify() reads the prefix and picks the correct algorithm at login. The critical shared property is slowness — neither should be used for session tokens or API keys, where random bytes and a fast MAC belong instead.

Choose Argon2id when your server runs PHP 8.5 with the sodium extension enabled — typical on Ubuntu 24 VPS setups. Laravel 13 follows PHP's direction in fresh installs. Set the driver explicitly in config/hashing.php rather than assuming defaults, then run php artisan config:cache on production. I've seen stale config leave new installs on bcrypt while developers assumed Argon2id was active. Bcrypt at cost 12 remains the safe fallback on budget shared hosts that compile PHP without sodium support. Both beat legacy MD5 and SHA-256 for stored credentials.

In config/hashing.php, set driver to env('HASH_DRIVER', 'argon2id') and configure the argon block with memory 65536 (64 MiB in KiB), threads 1, and time 4. Keep bcrypt rounds at 12 as fallback settings. Run php artisan config:cache after every change on production and include config cache clearing in your deploy pipeline. For plain PHP without Laravel, call password_hash with PASSWORD_ARGON2ID and the same memory_cost, time_cost, and threads options. Cross-check option flags against the official PHP password_hash documentation before pushing live.

Yes. Laravel's hasher detects the algorithm from the hash string prefix — bcrypt hashes starting with $2y$ continue to verify correctly after you change the driver to argon2id. After a successful login, call Hash::needsRehash(); when it returns true, store a fresh Argon2id hash immediately. Never bulk-rehash the user table offline because you do not have plaintext passwords. This lazy rehash-at-login pattern works the same in Symfony 8.1 and plain PHP via password_needs_rehash(). On client portals with thousands of dormant accounts, only active users upgrade over time, and that is acceptable.

Bcrypt scales CPU time through cost rounds but lacks memory hardness. An attacker with a stolen dump can attack bcrypt hashes using thousands of GPU cores in parallel. Argon2id forces each guess to allocate tens of megabytes of RAM — at 64 MiB memory cost, that bottleneck shrinks parallel cracking throughput by orders of magnitude. On a typical 2-vCPU VPS with 4 GB RAM, Argon2id at 64 MiB and time cost 4 lands near 300 ms per hash versus bcrypt cost 12 at roughly 250 ms. The login-time difference is negligible, but the offline attack economics favour Argon2id decisively.

Use bcrypt when Argon2id is unavailable — common on budget shared hosts that compile PHP without the sodium extension — or when you need maximum hosting compatibility without code changes. Bcrypt at cost 12 is far better than leaving legacy MD5 hashes in place. It also serves as the bridge during migration: old bcrypt hashes must keep verifying until each user logs in and gets lazily rehashed. OWASP lists bcrypt as an acceptable alternative to Argon2id. Bcrypt is not broken in 2026; it is the pragmatic fallback on constrained hosting and for legacy credential stores.

Start at 64 MiB memory (65536 KiB), time cost 4, and threads 1 — the values recommended in Laravel 13's config/hashing.php example. On a 2-vCPU VPS with 4 GB RAM, common for Nepal SMB sites at Rs 1,500–3,000/month (~USD 11–22), that typically lands near 300 ms per hash. Do not set memory to 256 MiB on a 1 GB RAM droplet; it can exhaust PHP-FPM workers during a login burst. SSH into production or a staging mirror, hash a test password 20 times, and adjust until the average sits between 200 and 500 ms. Re-test under normal traffic before documenting final values.

Follow lazy rehashing at successful login, never offline bulk conversion. Confirm sodium support with php -r "echo PASSWORD_ARGON2ID;", update config/hashing.php driver to argon2id, add rehash logic using Hash::needsRehash() or password_needs_rehash() in your login flow, and deploy during low-traffic hours while monitoring PHP-FPM memory. Old bcrypt hashes must still validate until each user re-authenticates. Test the rehash path on staging with cloned production data before deploying — a botched migration that invalidates passwords generates support tickets and erodes trust on client portals storing sensitive credentials alongside document access.

Bcrypt silently truncates passwords longer than 72 bytes. Users with long passphrases may believe their full string protects them while only the first 72 bytes are hashed. Argon2id avoids this footgun entirely with practically unlimited password length. If you must support long passphrases on bcrypt, pre-hash with SHA-256 then bcrypt the digest. Test edge cases with long random strings to confirm your application handles them correctly. This matters less for typical user-chosen passwords but becomes relevant for password-manager-generated credentials on legal-tech portals and client portals where strong passphrases are encouraged.

Teams still store passwords with MD5, SHA-1, or SHA-256 — fast hashes that let attackers test billions of guesses per second even with salting. Base64 is encoding, not protection. Other recurring failures: setting Argon2id memory too high on small VPS plans, ignoring bcrypt's 72-byte truncation, hard-coding a pepper in Git without a rotation plan, and treating strong hashing as sufficient without rate limiting, CAPTCHA after failures, and MFA for admin roles. Strong hashing protects offline dumps; it does not stop online guessing against a live login form. Pair slow hashes with layered auth defence.

Yes. PHP 8.5 provides Argon2id natively via the sodium extension and PASSWORD_ARGON2ID constant. Verify availability on your server with php -i | grep -i argon before switching your application default. Fresh Laravel 13 installs on Ubuntu 24 with PHP 8.5 typically have sodium enabled. Budget shared hosts sometimes compile PHP without it — in those environments, bcrypt at cost 12 is the correct fallback rather than forcing Argon2id or leaving legacy fast hashes untouched. Always confirm extension support in staging that mirrors production PHP-FPM settings before changing your hashing driver.

Yes. Bcrypt is not broken — OWASP lists it as an acceptable alternative to Argon2id, and cost 12 remains a valid production setting that typically runs near 250 ms on a modest VPS. It lacks Argon2id's memory hardness, so offline GPU attacks against a stolen dump are more economical, but bcrypt still caps guessing to hundreds or thousands of attempts per second versus billions for SHA-256. Use it as fallback on hosts without sodium, as the verification path for legacy hashes during lazy migration, and on platforms like WordPress core that still default to bcrypt-based phpass unless a plugin switches algorithms. Upgrade when infrastructure allows.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: