From 2c5e2074680afae185175121fc549913bf6f32c9 Mon Sep 17 00:00:00 2001 From: Jeff Hodges Date: Wed, 31 Oct 2012 12:35:46 -0400 Subject: [PATCH] go.crypto/bcrypt: add Cost function Extracts the cost used to generate a bcrypt hash from the hash itself. R=agl, agl, dchest CC=golang-dev https://golang.org/cl/6810051 --- bcrypt/bcrypt.go | 25 ++++++++++++++++++------- bcrypt/bcrypt_test.go | 25 ++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/bcrypt/bcrypt.go b/bcrypt/bcrypt.go index 35f0d8b..b71fc41 100644 --- a/bcrypt/bcrypt.go +++ b/bcrypt/bcrypt.go @@ -76,7 +76,7 @@ var magicCipherData = []byte{ type hashed struct { hash []byte salt []byte - cost uint32 // allowed range is MinCost to MaxCost + cost int // allowed range is MinCost to MaxCost major byte minor byte } @@ -94,8 +94,7 @@ func GenerateFromPassword(password []byte, cost int) ([]byte, error) { } // CompareHashAndPassword compares a bcrypt hashed password with its possible -// plaintext equivalent. Note: Using bytes.Equal for this job is -// insecure. Returns nil on success, or an error on failure. +// plaintext equivalent. Returns nil on success, or an error on failure. func CompareHashAndPassword(hashedPassword, password []byte) error { p, err := newFromHash(hashedPassword) if err != nil { @@ -115,6 +114,18 @@ func CompareHashAndPassword(hashedPassword, password []byte) error { return ErrMismatchedHashAndPassword } +// Cost returns the hashing cost used to create the given hashed +// password. When, in the future, the hashing cost of a password system needs +// to be increased in order to adjust for greater computational power, this +// function allows one to establish which passwords need to be updated. +func Cost(hashedPassword []byte) (int, error) { + p, err := newFromHash(hashedPassword) + if err != nil { + return 0, err + } + return p.cost, nil +} + func newFromPassword(password []byte, cost int) (*hashed, error) { if cost < MinCost { cost = DefaultCost @@ -127,7 +138,7 @@ func newFromPassword(password []byte, cost int) (*hashed, error) { if err != nil { return nil, err } - p.cost = uint32(cost) + p.cost = cost unencodedSalt := make([]byte, maxSaltSize) _, err = io.ReadFull(rand.Reader, unencodedSalt) @@ -172,11 +183,11 @@ func newFromHash(hashedSecret []byte) (*hashed, error) { return p, nil } -func bcrypt(password []byte, cost uint32, salt []byte) ([]byte, error) { +func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) { cipherData := make([]byte, len(magicCipherData)) copy(cipherData, magicCipherData) - c, err := expensiveBlowfishSetup(password, cost, salt) + c, err := expensiveBlowfishSetup(password, uint32(cost), salt) if err != nil { return nil, err } @@ -266,7 +277,7 @@ func (p *hashed) decodeCost(sbytes []byte) (int, error) { if err != nil { return -1, err } - p.cost = uint32(cost) + p.cost = cost return 3, nil } diff --git a/bcrypt/bcrypt_test.go b/bcrypt/bcrypt_test.go index 9ad5c1c..f949103 100644 --- a/bcrypt/bcrypt_test.go +++ b/bcrypt/bcrypt_test.go @@ -6,6 +6,7 @@ package bcrypt import ( "bytes" + "fmt" "testing" ) @@ -117,6 +118,28 @@ func TestUnpaddedBase64Encoding(t *testing.T) { } func TestCost(t *testing.T) { + suffix := "XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C" + for _, vers := range []string{"2a", "2"} { + for _, cost := range []int{4, 10} { + s := fmt.Sprintf("$%s$%02d$%s", vers, cost, suffix) + h := []byte(s) + actual, err := Cost(h) + if err != nil { + t.Errorf("Cost, error: %s", err) + continue + } + if actual != cost { + t.Errorf("Cost, expected: %d, actual: %d", cost, actual) + } + } + } + _, err := Cost([]byte("$a$a$" + suffix)) + if err == nil { + t.Errorf("Cost, malformed but no error returned") + } +} + +func TestCostValidationInHash(t *testing.T) { if testing.Short() { return } @@ -125,7 +148,7 @@ func TestCost(t *testing.T) { for c := 0; c < MinCost; c++ { p, _ := newFromPassword(pass, c) - if p.cost != uint32(DefaultCost) { + if p.cost != DefaultCost { t.Errorf("newFromPassword should default costs below %d to %d, but was %d", MinCost, DefaultCost, p.cost) } }