Which bcrypt cost to use for 2018?

12,473

I'm not certain what's going on with Benchmark here. If you just time these, it works fine, and you can work out the right answer for you.

package main

import (
    "golang.org/x/crypto/bcrypt"
    "time"
)

func main() {
    cost := 10

    start := time.Now()
    bcrypt.GenerateFromPassword([]byte("password"), cost)
    end := time.Now()

    print(end.Sub(start) / time.Millisecond)
}

For a work factor of 10, on my MacBook Pro I get 78ms. A work factor of 11 is 154ms, and 12 is 334ms. So we're seeing roughly doubling, as expected.

The goal is not a work factor; it's a time. You want as long as you can live with. In my experience (mostly working on client apps), 80-100ms is a nice target because compared to a network request it's undetectable to the user, while being massive in terms of brute-force attacks (so the default of 10 is ideal for my common use).

I generally avoid running password stretching on servers if I can help it, but this scale can be a reasonable trade-off between server impact and security. Remember that attackers may use something dramatically faster than a MacBook Pro, and may use multiple machines in parallel; I pick 80-100ms because of user experience trade-offs. (I perform password stretching on the client when I can get away with it, and then apply a cheap hash like SHA-256 on the server.)

But if you don't do this very often, or can spend more time on it, then longer is of course better, and on my MacBook Pro a work factor of 14 is about 1.2s, which I would certainly accept for some purposes.

But there's a reason that 10 is still the default. It's not an unreasonable value.

Share:
12,473
CommonSenseCode
Author by

CommonSenseCode

Software Skills: JavaScript NodeJS Golang React Redis Android Ionic/Cordova Framework XML, HTML, CSS, Sass, Less jQuery, Bootstrap MongoDB SQLite, Postgres & MySQL Git, Github, Bitbucket & Gitlab Linux Agile Development Unit Testing

Updated on June 18, 2022

Comments

  • CommonSenseCode
    CommonSenseCode almost 2 years

    Update

    Actually it seems the benchmark was incorrectly setup I have followed the resource shared by user @Luke Joshua Park and now it works.

    package main
    
    import "testing"
    
    func benchmarkBcrypt(i int, b *testing.B){
        for n:= 0; n < b.N; n++ {
            HashPassword("my pass", i)
            }
    
    }
    
    func BenchmarkBcrypt9(b *testing.B){
        benchmarkBcrypt(9, b)
    }
    
    func BenchmarkBcrypt10(b *testing.B){
        benchmarkBcrypt(10, b)
    }
    
    func BenchmarkBcrypt11(b *testing.B){
        benchmarkBcrypt(11, b)
    }
    
    func BenchmarkBcrypt12(b *testing.B){
        benchmarkBcrypt(12, b)
    }
    
    func BenchmarkBcrypt13(b *testing.B){
        benchmarkBcrypt(13, b)
    }
    
    func BenchmarkBcrypt14(b *testing.B){
        benchmarkBcrypt(14, b)
    }
    

    Output:

    BenchmarkBcrypt9-4            30      39543095 ns/op
    BenchmarkBcrypt10-4           20      79184657 ns/op
    BenchmarkBcrypt11-4           10     158688315 ns/op
    BenchmarkBcrypt12-4            5     316070133 ns/op
    BenchmarkBcrypt13-4            2     631838101 ns/op
    BenchmarkBcrypt14-4            1    1275047344 ns/op
    PASS
    ok      go-playground   10.670s
    

    Old incorrect benchmark

    I have a small set on benchmark test in golang and am curios of to what is a recommended bcrypt cost to use as of May 2018.

    This is my benchrmark file:

    package main
    
    import "testing"
    
    func BenchmarkBcrypt10(b *testing.B){
        HashPassword("my pass", 10)
    }
    
    func BenchmarkBcrypt12(b *testing.B){
        HashPassword("my pass", 12)
    }
    
    func BenchmarkBcrypt13(b *testing.B){
        HashPassword("my pass", 13)
    }
    
    
    func BenchmarkBcrypt14(b *testing.B){
        HashPassword("my pass", 14)
    }
    
    func BenchmarkBcrypt15(b *testing.B){
        HashPassword("my pass", 15)
    }
    

    and this is HashPassword() func inside main.go:

    import (
        "golang.org/x/crypto/bcrypt"
    )
    
    func HashPassword(password string, cost int) (string, error) {
        bytes, err := bcrypt.GenerateFromPassword([]byte(password), cost)
        return string(bytes), err
    }
    

    The current output is:

    go test -bench=.
    BenchmarkBcrypt10-4     2000000000           0.04 ns/op
    BenchmarkBcrypt12-4     2000000000           0.16 ns/op
    BenchmarkBcrypt13-4     2000000000           0.32 ns/op
    BenchmarkBcrypt14-4            1    1281338532 ns/op
    BenchmarkBcrypt15-4            1    2558998327 ns/op
    PASS
    

    It seems that for a bcrypt with cost of 13 the time it takes is 0.32 nanoseconds, and for cost 14 the time is 1281338532ns or ~1.2 seconds Which I believe is too much. What do is the best bcrypt cost to use for the current year 2018.

  • CommonSenseCode
    CommonSenseCode almost 6 years
    so better to just use DefaultCost var from bcrypt package. Do you have an idea why the benchmarks don't work? when should one use benchmarks? I though it was a perfect use case for comparing bcrypt cost performances
  • Rob Napier
    Rob Napier almost 6 years
    I don't understand the Benchmark issue either. It's probably worth its own question. Probably worth exploring some other functions (to see if it's specific to the bcrypt function, or just something non-obvious about Benchmark generally).
  • Zippo
    Zippo almost 6 years
    Just a tip, you can use time.Since(start) instead of time.Now().Sub(start)
  • Luke Joshua Park
    Luke Joshua Park almost 6 years
    The benchmark issue is due to the fact that you aren't running the code b.N times. This is required and not optional when writing Go benchmarks.
  • CommonSenseCode
    CommonSenseCode almost 6 years
    check my update the benchmark was incorrectly set, I used @Luke Josua Park resource and got it working. Do you think the output now makes sense it is roughly doubling with every increase of bcryp
  • Rob Napier
    Rob Napier almost 6 years
    Yes, you should expect doubling with each increase of the work factor. It's 2^n.
  • My1
    My1 over 5 years
    @RobNapier while time is the target on your side, it would be helpful to know how much cost would be at the very least a reasonable minimum for any given era as not everyone has top of the line machines and the question is always whether this old machine that can only go so far with bcrypt but it would be still ugly because the machine is too old for example. basically a "how low can you go without impacting security too much"
  • Rob Napier
    Rob Napier over 5 years
    Agreed. I generally scale work factors based on running tests on the slowest hardware I support, and target certain user experiences there (usually a "slightly annoying, but not too painful" user experience, with the assumption people upgrade eventually). This is also a reason to store your work factor in your data (which I think bcrypt does) so you can increase it over time while still being able to deal with old hashes. (This approach doesn't always work, especially when you're dealing with network protocols, but it's worth pursuing when possible.)