This comparison of MD5, Bcrypt, and Argon started as internal documentation for my team, but seemed worth sharing with the wider developer community. It's primarily for backend developers and security engineers who want a practical understanding of these hashing algorithms. While not exhaustive, this quantified comparison should give you a good sense of how these approaches differ. The information is readily verifiable through standard documentation.
| MD5 | Bcrypt/Argon | |
|---|---|---|
| First Published | 1992 | Bcrypt- 1999, Argon- 2015 |
| Original Purpose | Data integrity verification via checksums | Specifically designed for password hashing |
| Speed | Extremely fast (~70 billion hashes/second on GPU, ~330MB/second, ~400-600 nanoseconds per hash) | Intentionally slow with configurable work factor |
| Security Against Brute Force | Vulnerable (e.g., LinkedIn 2012 breach: 60% passwords cracked in 24h, 90% within 3 days) | Highly resistant (what takes 40s in MD5 would take 12 years in Bcrypt) |
| Rainbow Table Protection | None - vulnerable to precomputed tables with trillions of combinations | Built-in salt protection makes rainbow tables ineffective |
| Hash Uniqueness | Same password produces same hash, collisions possible | Same password produces different hashes due to salting |
| Collision Resistance | Poor - multiple inputs can produce the same hash | Strong collision resistance |
| Future Proofing | MD5None - becomes more vulnerable as computers get faster | Adjustable work factor keeps pace with Moore's Law |
| Future Proofing | MD5Single layer of defense | Multiple layers of defense. |
| Industry adoption | MD5Considered very unsafe, all major companies have moved away from it. | Recommended by OWASP. They specifically argue against MD5/SHA1. |
| Bcrypt | Argon | |
|---|---|---|
| First Published | Bcrypt paper, 1999 | Argon Paper, 2015 |
| Industry Adoption | Industry standard, well-tested | Current state-of-the-art, [Password-Hashing-Competiton 2015 Winner](https://argon2%20is%20a%20password%20hashing%20algorithm%20that%20won%20the%202015%20password%20hashing%20competition/) (industry's last major competition), recommended for new implementations (some call it over-kill for web dev.) |
| NPM Details | NPM Package : ~2M weekly downloads, last update 1year ago, 111kB | NPM Package: ~372k weekly downloads, last update 5 months ago, 866 kB |
| Features | Supports salting, work factors (multiple iterations- 2^12 is industry standard), etc. | Everything bcrypt + memory hard and configurable memory and CPU usage giving granular control. More robust against specialized hardware attacks (like ASICs/FPGAs) |
| Security | Comparatively faster, less computationally intensive, good security | Comparatively slower, more computationally intensive, great security. |
| Memory use | ~4KB of memory per hash operation | 32-128MB by default (configurable) |
| Speed | ~300-400 milliseconds per hash (work factor 12) | ~500-700 milliseconds per hash (recommended parameters) |
| Cost | (~83-111 hours of CPU time, ~$8-12 in AWS compute costs) per 1 million hashes | (~138-194 hours of CPU time, $14-20 in AWS compute costs) per 1 million hashes |
| Limitations | Silently truncates passwords to 72 bytes. | No password length limitations, handles long passwords natively |
| Recommendations | Recommended, only if newer methods are unavailable. | Recommended first choice by OWASP |
// Simplified implementation walkthrough for Argon/Bcrypt
const hashPassword() => {
const workFactor = 12;
const iterations = 2 ** workFactor;
const rawPassword = 12345678;
const salt = library.getSalt(); // returns 128 bit unique string; (can be configured in argon)
let hashedPassword = salt + '$' + rawPassword;
while (iterations) {
hashedPassword = library.hash(salt, hashedPassword);
hashedPassword = salt + '$' + hashedPassoword;
iterations--;
}
return hashedPassword;
}
const comparePassword(rawPassword) => {
const hashedPassword = db.getHashedPassword();
const workFactor == 12;
const iterations = 2 ** workFactor;
const salt = hashedPassword.split('$')[0;
const comparePassword = salt + '$' + rawPassword;
while (iterations) {
comparePassword = library.hash(salt, comparePassword);
comparePassword = salt + '$' comparePassword;
iterations--;
}
return comparePassword === hashedPassword;
}}
Password Storage Flow
graph TD
%% Entities are rectangles, actions are rounded rectangles/circles
Client[[Client]]
Server[[Server]]
DB[(Database)]
%% Actions are rounded rectangles
SendHash(Send MD5 Password)
HashGen(Pass to Hashing Library)
StoreHash(Store Final Hash)
Client --> SendHash
SendHash --> Server
Server --> HashGen
subgraph Library["Bcrypt/Argon2 Library (Automated)"]
style Library fill:#333,stroke:#666,stroke-width:2px,color:#fff
HashGen --> InternalProcess(("Hash Generation (Automated salt handling)"))
end
InternalProcess --> StoreHash
StoreHash --> DB
note["Note: Salt generation and storage are handled automatically by the library"]
style note fill:#D3D3D3,stroke:#000000,stroke-width:2px,color:#000000
Authentication Flow
graph TD
%% Entities are rectangles
Client[[Client]]
Server[[Server]]
DB[(Database)]
%% Actions are rounded rectangles
SendHash(Send MD5 Password)
FetchHash(Fetch Stored Hash)
ReturnHash(Return Stored Hash)
Compare(Pass to Comparison)
Grant(Grant Access)
Deny(Deny Access)
Client --> SendHash
SendHash --> Server
Server --> FetchHash
FetchHash --> DB
DB --> ReturnHash
ReturnHash --> Compare
subgraph Library["Bcrypt/Argon2 Library (Automated)"]
style Library fill:#333,stroke:#666,stroke-width:2px,color:#fff
Compare --> Verification{{"Password Verification (Automated salt handling)"}}
end
Verification --> |Match| Grant
Verification --> |No Match| Deny
note["Note: Salt extraction and comparison are handled automatically by the library"]
style note fill:#D3D3D3,stroke:#000000,stroke-width:2px,color:#000000