Understanding Bcrypt's Work Factor and Choosing the Right Value
My Thought on Bcrypt & Why Work Factor Matters If you ever work with passwords in coding either for work or for fun, you probably know the #1 rule: never store them as plain text! I’ve always seen bcrypt as the go-to for password hashing. Developers seem to love it. But let’s be honest, a lot of us just copy-paste some code from the Stack Overflow without really thinking about what’s going on under the hood. And in doing so, we often skip an important detail: the work factor. What so special about bcrypt? Bcrypt not just another hashing function, it has two key features that make it stand out: Automatic salt. Every time you hash a password, it generates and use a unique salt. This basically shuts the door on rainbow table attacks. Adaptive hashing (a.k.a. slowing things down on purpose). You can tweak a parameter called the work factor, which makes hashing take longer time and more computation power. This is actually a good thing! It means brute-force attacks become much harder and way more expensive. At first, I thought the work factor was just some random number you set once and forget. Nope. Turns out, it’s crucial for keeping passwords secure, and you need to adjust it as computers get faster. Why Work Factor Matters Hardware technology move fast, and what was "secure" ten years ago might be considered weak today. Here’s a rough timeline: 2010: Work factor 10 was considered solid. 2025: Security pros suggest bumping it up to 12 or 14. Future: We might need 15+ as hardware keeps improving. Basically, if your work factor is too low, bcrypt won’t be as effective. And if it’s too high, well… your users might start thinking your app is broken. What I want to share What the work factor actually does. How to pick the right number for your project. Some tricks to keep your passwords safe without making your project slow. So whether you’re just starting with bcrypt or fixing an old project, getting the work factor right is what takes your password security from “eh, good enough” to “actually solid”. So, What is Work Factor? Basically, work factor (also called cost or rounds) is a parameter that controls how much computation being done to hash a password. More computation means more secure, but also more processing time and resource. It grows exponentially, every time the work factor increased by 1, the hashing takes around twice as long. Under the hood Bcrypt repurposes the Blowfish cipher by using an expensive key setup mechanism, where the password is used during the key schedule. This work factor parameter controls the number of key setup iterations as 2^work_factor. This process is CPU intensive, unlike memory intensive algorithms like Argon2. So what does it mean in practice? Because a small increase can make hashing time jump exponentially, you can adjust this parameter based on your security requirement, or today hardware capability, making it harder to be brute forced regardless of advancement in hardware capability. A quick benchmark with Go in MacBook Air Apple M2 to show how hashing time doubles on each increase of the work factor: package main import ( "fmt" "time" "golang.org/x/crypto/bcrypt" ) func benchmarkBcrypt(workFactor int) { start := time.Now() password := []byte("mySecurePassword123") hash, err := bcrypt.GenerateFromPassword(password, workFactor) if err != nil { panic(err) } fmt.Printf("Work factor %d: %v (Hash: %s...)\n", workFactor, time.Since(start), string(hash)[:20]) } func main() { for workFactor := 8; workFactor

My Thought on Bcrypt & Why Work Factor Matters
If you ever work with passwords in coding either for work or for fun, you probably know the #1 rule: never store them as plain text! I’ve always seen bcrypt as the go-to for password hashing. Developers seem to love it. But let’s be honest, a lot of us just copy-paste some code from the Stack Overflow without really thinking about what’s going on under the hood. And in doing so, we often skip an important detail: the work factor.
What so special about bcrypt?
Bcrypt not just another hashing function, it has two key features that make it stand out:
- Automatic salt. Every time you hash a password, it generates and use a unique salt. This basically shuts the door on rainbow table attacks.
- Adaptive hashing (a.k.a. slowing things down on purpose). You can tweak a parameter called the work factor, which makes hashing take longer time and more computation power. This is actually a good thing! It means brute-force attacks become much harder and way more expensive.
At first, I thought the work factor was just some random number you set once and forget. Nope. Turns out, it’s crucial for keeping passwords secure, and you need to adjust it as computers get faster.
Why Work Factor Matters
Hardware technology move fast, and what was "secure" ten years ago might be considered weak today. Here’s a rough timeline:
- 2010: Work factor 10 was considered solid.
- 2025: Security pros suggest bumping it up to 12 or 14.
- Future: We might need 15+ as hardware keeps improving.
Basically, if your work factor is too low, bcrypt won’t be as effective. And if it’s too high, well… your users might start thinking your app is broken.
What I want to share
- What the work factor actually does.
- How to pick the right number for your project.
- Some tricks to keep your passwords safe without making your project slow.
So whether you’re just starting with bcrypt or fixing an old project, getting the work factor right is what takes your password security from “eh, good enough” to “actually solid”.
So, What is Work Factor?
Basically, work factor (also called cost or rounds) is a parameter that controls how much computation being done to hash a password. More computation means more secure, but also more processing time and resource.
It grows exponentially, every time the work factor increased by 1, the hashing takes around twice as long. Under the hood Bcrypt repurposes the Blowfish cipher by using an expensive key setup mechanism, where the password is used during the key schedule. This work factor parameter controls the number of key setup iterations as 2^work_factor. This process is CPU intensive, unlike memory intensive algorithms like Argon2.
So what does it mean in practice? Because a small increase can make hashing time jump exponentially, you can adjust this parameter based on your security requirement, or today hardware capability, making it harder to be brute forced regardless of advancement in hardware capability.
A quick benchmark with Go
in MacBook Air Apple M2
to show how hashing time doubles on each increase of the work factor:
package main
import (
"fmt"
"time"
"golang.org/x/crypto/bcrypt"
)
func benchmarkBcrypt(workFactor int) {
start := time.Now()
password := []byte("mySecurePassword123")
hash, err := bcrypt.GenerateFromPassword(password, workFactor)
if err != nil {
panic(err)
}
fmt.Printf("Work factor %d: %v (Hash: %s...)\n", workFactor, time.Since(start), string(hash)[:20])
}
func main() {
for workFactor := 8; workFactor <= 18; workFactor++ {
benchmarkBcrypt(workFactor)
}
}
Output:
Work factor 8: 17.70425ms (Hash: $2a$08$E.o7Tqw8T07rh...)
Work factor 9: 36.118125ms (Hash: $2a$09$JIS9lqNj47THu...)
Work factor 10: 72.501958ms (Hash: $2a$10$axDX0ijlIs3p....)
Work factor 11: 143.923834ms (Hash: $2a$11$m2KDuQl30gz6I...)
Work factor 12: 284.81625ms (Hash: $2a$12$n.fKAp6jHNJWw...)
Work factor 13: 573.204375ms (Hash: $2a$13$AQIMEUfJvtrQB...)
Work factor 14: 1.151081s (Hash: $2a$14$EewXxPOg0JPnl...)
Work factor 15: 2.300344583s (Hash: $2a$15$TAtTjpNYsTO0V...)
Work factor 16: 4.658600542s (Hash: $2a$16$luaozYqMW1kME...)
Work factor 17: 9.3284315s (Hash: $2a$17$U.OuLzA1HwqhQ...)
Work factor 18: 18.565636625s (Hash: $2a$18$h8rxVyo2fUK2W...)
NOTE: The computation time is nearly identical for both generating and comparing/verifying the hash.
If you’re using bcrypt, you need to check in on your work factor every few years. As computers get stronger, what’s "secure" today might be considered tomorrow.
What Work Factor Should I Use?
Bcrypt still relevant because it’s slow on purpose, but how slow is too slow?
As always the answer is "it depends". What kind of app are you building? How strong is the hardware? How much patience do your users have?
In general, it's recommended to determine it by the amount of time password verification will take on your server, then calculate the value of work factor based upon that. You want verification to take as long as you can stand.
In general, it's recommended to keep it under 1 second:
- Too Low (≤10): Easy pickings for modern GPUs and cloud brute-force attacks.
- Sweet Spot (12-14): Good mix of security and speed (~250ms–1s per hash).
- Too High (≥16): Feels like your app is crawling, and your server might cry.
Rough guide based on project type on this article written(2025):
Application Type | Minimum Value | Notes |
---|---|---|
Development/Testing | 10 | Fast, but not recommended for production! |
General Web Apps | 12 | Secure enough without slowing things down (300-500ms) |
Financial/Healthcare | 13-14 | Stronger protection, but adds ~1-2s per login |
Password Managers | 14+ | Hardcore security for stored passwords |
Things to consider:
- Will users notice? Most won’t care if login takes 1 second. (They’ll survive.)
- Can your servers handle it? Higher work factors mean higher CPU load.
Future-Proofing Your Password Hashing
One key advantage of having a work factor is that it can be increased over time as hardware becomes more powerful and cheaper. Common approach to upgrading the work factor is to gradually upgrade hashes until the next user authenticates, then re-hash their password with the new work factor. Depending on the requirement, it might be needed to force logout the users e.g: invalidate their session cookie or rolling your JWT secrets, to trigger the authentication flow.
Keep it adjustable. It's better to not hardcode your work factor, instead, store it in your configuration system.
func Authenticate(ctx context.Context, email, password string) (bool, error) {
user := db.GetUserByEmail(email)
err := bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(password))
if err != nil {
return false, err
}
currentCost, _ := bcrypt.Cost(user.PasswordHash)
if currentCost < config.MinWorkFactor {
newHash, _ := bcrypt.GenerateFromPassword(
[]byte(password), config.MinWorkFactor)
db.UpdateUserPassword(user.ID, newHash)
}
return true, nil
}
Benefits:
✔ No user interruption
✔ Self-healing system over time
✔ Distributed computational load
Key Takeaways for Implementers
- Each +1 doubles hashing time and brute-force resistance. 12 is the new minimum in 2025.
- Migration is inevitable. Hardware advances require periodic work factor bumps. Rolling rehashes beat mass password resets.
- Context Matters. Financial apps need 14+ while IoT may cap at 8-10. Monitor and adjust based on your threat model.
Reference and tools that might be useful for you: