Storing passwords securely is one of the most critical security tasks in modern applications. Many developers still rely on time-tested algorithms like bcrypt, but technology doesn't stand still. In this article, we'll explore Argon2 — a modern and secure password hashing algorithm that serves as an excellent alternative to bcrypt. We'll also look at how to implement it in Go (Golang).
Why Argon2?
A Quick Recap on bcrypt
bcrypt is a cryptographic algorithm specifically designed for password hashing. It resists brute-force attacks thanks to the use of "salt" and a tunable cost factor that increases computational complexity.
However, over time new threats have emerged — especially those involving specialized hardware such as GPUs and ASICs for password cracking. This is where bcrypt starts to fall short compared to more modern solutions.
What Is Argon2?
Argon2 is the winner of the Password Hashing Competition (PHC), a competition organized by the cryptographic community to find a new standard for secure password hashing. It was developed by a team of cryptographers from the University of Luxembourg: Alex Biryukov, Daniel Dinu, and Dmitry Khovratovich.
Argon2 was chosen for its resistance to various types of attacks, including:
- Timing attacks
- GPU/ASIC-based attacks
- Memory-hard attacks
Argon2 offers three different modes:
- Argon2d — provides maximum protection against hardware attacks but is vulnerable to timing attacks.
- Argon2i — resistant to timing attacks but weaker against hardware-based attacks.
- Argon2id — a hybrid mode combining the best features of both.
For most practical purposes, Argon2id is recommended.
Why Argon2 Is Better Than bcrypt
Feature | bcrypt | Argon2 |
---|---|---|
Protection against GPU attacks | Weak | Strong |
Configurable memory usage | No | Yes |
Parallelism support | No | Yes |
Resistance to timing attacks | Yes | Yes |
In short, Argon2 is a more flexible, modern, and secure solution.
Using Argon2 in Go
The package golang.org/x/crypto/argon2
provides a solid implementation of the Argon2 algorithm.
In this example, we'll use Argon2id (IDKey
), which combines the strengths of Argon2i and Argon2d: it's resistant to side-channel attacks and protected against time-memory trade-off attacks.
Installation
go get golang.org/x/crypto/argon2
Example Code
package main
import (
"crypto/rand"
"crypto/subtle"
"fmt"
"golang.org/x/crypto/argon2"
)
// Recommended RFC parameters for most applications
const (
saltLength = 16 // length of salt in bytes
keyLength = 32 // length of derived key (e.g., for AES-256)
time = 1 // number of iterations
memory = 64 << 10 // memory cost in KiB (~64MB)
threads = 4 // number of parallel threads
)
func generateSalt(length int) ([]byte, error) {
salt := make([]byte, length)
_, err := rand.Read(salt)
if err != nil {
return nil, err
}
return salt, nil
}
func hashPassword(password string) ([]byte, []byte, error) {
salt, err := generateSalt(saltLength)
if err != nil {
return nil, nil, err
}
hashed := argon2.IDKey([]byte(password), salt, time, memory, threads, keyLength)
return hashed, salt, nil
}
func verifyPassword(password string, salt []byte, expectedHash []byte) bool {
newHash := argon2.IDKey([]byte(password), salt, time, memory, threads, keyLength)
return subtleCompare(newHash, expectedHash)
}
// Prevent timing attacks
func subtleCompare(a, b []byte) bool {
return subtle.ConstantTimeCompare(a, b) == 1
}
func main() {
password := "mySecureP@ssw0rd"
// Step 1: Hash the password
hashed, salt, err := hashPassword(password)
if err != nil {
panic(err)
}
fmt.Printf("Hashed password: %x\n", hashed)
// Step 2: Verify correct password
if verifyPassword(password, salt, hashed) {
fmt.Println("✅ Password is valid")
} else {
fmt.Println("❌ Password is invalid")
}
// Step 3: Verify incorrect password
if verifyPassword("wrongPass", salt, hashed) {
fmt.Println("❌ Incorrect password was accepted!")
} else {
fmt.Println("✅ Verification rejected incorrect password")
}
}
🔐 Tip: Never use fixed
salt
values. Always generate a new random salt before each password hashing.
Recommended Parameters
Parameter | Value | Description |
---|---|---|
time |
1 | Number of passes through memory |
memory |
64 * 1024 | Amount of memory used in KiB (~64 MB) |
threads |
4 | Number of threads to use |
keyLength |
32 | Length of the resulting key in bytes |
saltLength |
16 | Random salt to prevent collisions |
These values are suitable for most web applications. Adjust them based on your system's capabilities or specific requirements (e.g., mobile devices).
Conclusion
While bcrypt remains a solid choice, Argon2 offers superior protection against modern threats, particularly GPU and ASIC-based attacks. With its flexibility and efficient resource usage, it is becoming the de-facto standard for password hashing in new projects.
If you're developing in Go, integrating Argon2 is straightforward using existing libraries. Just remember to choose appropriate parameters for your application load and always store the salt and metadata correctly.
📎 Useful Links
📌 What do you think?
Have you already switched from bcrypt to Argon2 in your projects — or still sticking with the classic?
What password hashing strategy do you use in Go — and how do you manage security vs. performance?
👇 Share your thoughts and experience in the comments — I’d love to learn from you!
👍 If you enjoyed this article, don’t forget to like and share it — help others upgrade their password security the right way!
📣 Follow me and read my content on other platforms:
Check out this and other articles on my pages:
🔗 LinkedIn
📚 Medium
📘 Facebook
✈️ Telegram
🐦 X
Oh, almost forgot — my personal website.
🔔 Follow me not to miss new articles and guides on hot topics!
Top comments (0)