| // 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, DefaultCost) | |
| b.StartTimer() | |
| for i := 0; i < b.N; i++ { | |
| CompareHashAndPassword(hash, passwd) | |
| } | |
| } | |
| func BenchmarkDefaultCost(b *testing.B) { | |
| b.StopTimer() | |
| passwd := []byte("mylongpassword1234") | |
| b.StartTimer() | |
| for i := 0; i < b.N; i++ { | |
| GenerateFromPassword(passwd, DefaultCost) | |
| } | |
| } | |
| // 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) | |
| } | |
| } | |
| func TestPasswordTooLong(t *testing.T) { | |
| _, err := GenerateFromPassword(make([]byte, 73), 1) | |
| if err != ErrPasswordTooLong { | |
| t.Errorf("unexpected error: got %q, want %q", err, ErrPasswordTooLong) | |
| } | |
| } |