// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package bcrypt import ( "bytes" "fmt" "testing" ) func TestBcryptingIsEasy(t *testing.T) { pass := []byte("mypassword") hp, err := GenerateFromPassword(pass, 0) if err != nil { t.Fatalf("GenerateFromPassword error: %s", err) } if CompareHashAndPassword(hp, pass) != nil { t.Errorf("%v should hash %s correctly", hp, pass) } notPass := "notthepass" err = CompareHashAndPassword(hp, []byte(notPass)) if err != ErrMismatchedHashAndPassword { t.Errorf("%v and %s should be mismatched", hp, notPass) } } func TestBcryptingIsCorrect(t *testing.T) { pass := []byte("allmine") salt := []byte("XajjQvNhvvRt5GSeFk1xFe") expectedHash := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga") hash, err := bcrypt(pass, 10, salt) if err != nil { t.Fatalf("bcrypt blew up: %v", err) } if !bytes.HasSuffix(expectedHash, hash) { t.Errorf("%v should be the suffix of %v", hash, expectedHash) } h, err := newFromHash(expectedHash) if err != nil { t.Errorf("Unable to parse %s: %v", string(expectedHash), err) } // This is not the safe way to compare these hashes. We do this only for // testing clarity. Use bcrypt.CompareHashAndPassword() if err == nil && !bytes.Equal(expectedHash, h.Hash()) { t.Errorf("Parsed hash %v should equal %v", h.Hash(), expectedHash) } } func TestVeryShortPasswords(t *testing.T) { key := []byte("k") salt := []byte("XajjQvNhvvRt5GSeFk1xFe") _, err := bcrypt(key, 10, salt) if err != nil { t.Errorf("One byte key resulted in error: %s", err) } } func TestTooLongPasswordsWork(t *testing.T) { salt := []byte("XajjQvNhvvRt5GSeFk1xFe") // One byte over the usual 56 byte limit that blowfish has tooLongPass := []byte("012345678901234567890123456789012345678901234567890123456") tooLongExpected := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C") hash, err := bcrypt(tooLongPass, 10, salt) if err != nil { t.Fatalf("bcrypt blew up on long password: %v", err) } if !bytes.HasSuffix(tooLongExpected, hash) { t.Errorf("%v should be the suffix of %v", hash, tooLongExpected) } } type InvalidHashTest struct { err error hash []byte } var invalidTests = []InvalidHashTest{ {ErrHashTooShort, []byte("$2a$10$fooo")}, {ErrHashTooShort, []byte("$2a")}, {HashVersionTooNewError('3'), []byte("$3a$10$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")}, {InvalidHashPrefixError('%'), []byte("%2a$10$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")}, {InvalidCostError(32), []byte("$2a$32$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")}, } func TestInvalidHashErrors(t *testing.T) { check := func(name string, expected, err error) { if err == nil { t.Errorf("%s: Should have returned an error", name) } if err != nil && err != expected { t.Errorf("%s gave err %v but should have given %v", name, err, expected) } } for _, iht := range invalidTests { _, err := newFromHash(iht.hash) check("newFromHash", iht.err, err) err = CompareHashAndPassword(iht.hash, []byte("anything")) check("CompareHashAndPassword", iht.err, err) } } func TestUnpaddedBase64Encoding(t *testing.T) { original := []byte{101, 201, 101, 75, 19, 227, 199, 20, 239, 236, 133, 32, 30, 109, 243, 30} encodedOriginal := []byte("XajjQvNhvvRt5GSeFk1xFe") encoded := base64Encode(original) if !bytes.Equal(encodedOriginal, encoded) { t.Errorf("Encoded %v should have equaled %v", encoded, encodedOriginal) } decoded, err := base64Decode(encodedOriginal) if err != nil { t.Fatalf("base64Decode blew up: %s", err) } if !bytes.Equal(decoded, original) { t.Errorf("Decoded %v should have equaled %v", decoded, original) } } 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 } pass := []byte("mypassword") for c := 0; c < MinCost; c++ { p, _ := newFromPassword(pass, c) if p.cost != DefaultCost { t.Errorf("newFromPassword should default costs below %d to %d, but was %d", MinCost, DefaultCost, p.cost) } } p, _ := newFromPassword(pass, 14) if p.cost != 14 { t.Errorf("newFromPassword should default cost to 14, but was %d", p.cost) } hp, _ := newFromHash(p.Hash()) if p.cost != hp.cost { t.Errorf("newFromHash should maintain the cost at %d, but was %d", p.cost, hp.cost) } _, err := newFromPassword(pass, 32) if err == nil { t.Fatalf("newFromPassword: should return a cost error") } if err != InvalidCostError(32) { t.Errorf("newFromPassword: should return cost error, got %#v", err) } } func TestCostReturnsWithLeadingZeroes(t *testing.T) { hp, _ := newFromPassword([]byte("abcdefgh"), 7) cost := hp.Hash()[4:7] expected := []byte("07$") if !bytes.Equal(expected, cost) { t.Errorf("single digit costs in hash should have leading zeros: was %v instead of %v", cost, expected) } } func TestMinorNotRequired(t *testing.T) { noMinorHash := []byte("$2$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga") h, err := newFromHash(noMinorHash) if err != nil { t.Fatalf("No minor hash blew up: %s", err) } if h.minor != 0 { t.Errorf("Should leave minor version at 0, but was %d", h.minor) } if !bytes.Equal(noMinorHash, h.Hash()) { t.Errorf("Should generate hash %v, but created %v", noMinorHash, h.Hash()) } } func BenchmarkEqual(b *testing.B) { b.StopTimer() passwd := []byte("somepasswordyoulike") hash, _ := GenerateFromPassword(passwd, 10) b.StartTimer() for i := 0; i < b.N; i++ { CompareHashAndPassword(hash, passwd) } } func BenchmarkGeneration(b *testing.B) { b.StopTimer() passwd := []byte("mylongpassword1234") b.StartTimer() for i := 0; i < b.N; i++ { GenerateFromPassword(passwd, 10) } } // See Issue https://github.com/golang/go/issues/20425. func TestNoSideEffectsFromCompare(t *testing.T) { source := []byte("passw0rd123456") password := source[:len(source)-6] token := source[len(source)-6:] want := make([]byte, len(source)) copy(want, source) wantHash := []byte("$2a$10$LK9XRuhNxHHCvjX3tdkRKei1QiCDUKrJRhZv7WWZPuQGRUM92rOUa") _ = CompareHashAndPassword(wantHash, password) got := bytes.Join([][]byte{password, token}, []byte("")) if !bytes.Equal(got, want) { t.Errorf("got=%q want=%q", got, want) } }