Selaa lähdekoodia

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
Jeff Hodges 13 vuotta sitten
vanhempi
commit
2c5e207468
2 muutettua tiedostoa jossa 42 lisäystä ja 8 poistoa
  1. 18 7
      bcrypt/bcrypt.go
  2. 24 1
      bcrypt/bcrypt_test.go

+ 18 - 7
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
 }
 

+ 24 - 1
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)
 		}
 	}