Skip to content

Commit

Permalink
Randomize tests, fix broken key (golang#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
zugzwang authored and twiss committed Apr 16, 2020
1 parent a103d74 commit 1fa7f40
Show file tree
Hide file tree
Showing 28 changed files with 930 additions and 784 deletions.
8 changes: 7 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
language: go
script: go test -short ./...

script:
- go test -short ./...
- go test ./... -run RandomizeFast -count=512
- go test ./... -run RandomizeSlow -count=32

go_import_path: golang.org/x/crypto

go:
- 1.10.x
- 1.11.x
Expand Down
145 changes: 67 additions & 78 deletions bcrypt/bcrypt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ package bcrypt

import (
"bytes"
"crypto/rand"
"fmt"
mathrand "math/rand"
"testing"
)

func TestBcryptingIsEasy(t *testing.T) {
pass := []byte("mypassword")
const (
maxPasswordLength = 80
)

func TestRandomBcryptMismatchRandomizeSlow(t *testing.T) {
pass := make([]byte, mathrand.Intn(maxPasswordLength))
rand.Read(pass)
hp, err := GenerateFromPassword(pass, 0)
if err != nil {
t.Fatalf("GenerateFromPassword error: %s", err)
Expand All @@ -21,74 +28,57 @@ func TestBcryptingIsEasy(t *testing.T) {
t.Errorf("%v should hash %s correctly", hp, pass)
}

notPass := "notthepass"
err = CompareHashAndPassword(hp, []byte(notPass))
notPass := make([]byte, mathrand.Intn(maxPasswordLength))
for rand.Read(notPass); bytes.Equal(notPass, pass); {
rand.Read(notPass)
}

err = CompareHashAndPassword(hp, 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")
func TestExternalBcryptingCorrectness(t *testing.T) {
for _, sample := range externalBcryptHashes {
pass := []byte(sample.pass)
salt := []byte(sample.salt)
expectedHash := []byte(sample.hash)
cost := sample.cost

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)
}
hash, err := bcrypt(pass, cost, salt)
if err != nil {
t.Fatalf("bcrypt blew up: %v", err)
}
if !bytes.HasSuffix(expectedHash, hash) {
fmt.Println(sample.pass)
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)
}
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)
// 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)
for _, salt := range randomSalts {
key := make([]byte, mathrand.Intn(5))
rand.Read(key)
_, err := bcrypt(key, 10, []byte(salt))
if err != nil {
t.Errorf("One byte key resulted in error: %s", err)
}
}
}

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 {
Expand Down Expand Up @@ -149,11 +139,8 @@ func TestCost(t *testing.T) {
}

func TestCostValidationInHash(t *testing.T) {
if testing.Short() {
return
}

pass := []byte("mypassword")
pass := make([]byte, maxPasswordLength)
rand.Read(pass)

for c := 0; c < MinCost; c++ {
p, _ := newFromPassword(pass, c)
Expand Down Expand Up @@ -182,7 +169,9 @@ func TestCostValidationInHash(t *testing.T) {
}

func TestCostReturnsWithLeadingZeroes(t *testing.T) {
hp, _ := newFromPassword([]byte("abcdefgh"), 7)
pass := make([]byte, maxPasswordLength)
rand.Read(pass)
hp, _ := newFromPassword(pass, 7)
cost := hp.Hash()[4:7]
expected := []byte("07$")

Expand All @@ -206,6 +195,23 @@ func TestMinorNotRequired(t *testing.T) {
}
}

// 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 BenchmarkEqual(b *testing.B) {
b.StopTimer()
passwd := []byte("somepasswordyoulike")
Expand All @@ -224,20 +230,3 @@ func BenchmarkDefaultCost(b *testing.B) {
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)
}
}
111 changes: 111 additions & 0 deletions bcrypt/bcrypt_test_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package bcrypt

// Passphrases [email protected] generated with dev/urandom entropy
// Bcrypt hashes computed with python's bcrypt https://pypi.org/project/bcrypt/

type InvalidHashTest struct {
err error
hash []byte
}
var externalBcryptHashes = []struct {
pass string
salt string
hash string
cost int
}{
{
"",
"JGZJSHED/woRIKSoTp5bZe",
"$2b$12$JGZJSHED/woRIKSoTp5bZea/99GHy6jGK1ToltiTObaiRQMLxH3we",
12,
},
{
"allmine",
"XajjQvNhvvRt5GSeFk1xFe",
"$2a$10$XajjQuNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga",
10,
},
{
"pass",
"GNk.4LiPcEcQxTb/FiWhfu",
"$2b$12$GNk.4LiPcEcQxTb/FiWhfu52a11RA6Jh5r4mLpezmg6.DlYS3MKzy",
12,
},
{
"letmein",
"biCUWeQbpfJiIT0hZJqOWO",
"$2b$12$biCUWeQbpfJiIT0hZJqOWOQAPN93iU3MPDHkvsnKx3tqV2yWRtiNK",
12,
},
{
"010203040506070809",
"60xRZwFvBNfExmNnV.twIO",
"$2b$12$60xRZwFvBNfExmNnV.twIOgz89kFEpp83ruKh5bufkUWQvVikbfL2",
12,
},
{
"1.e4 e5 2. Nf3 Nc6 3. Bb4 Bb5",
"9cgE2qZ1LbIKMPerEq/gIe",
"$2b$12$9cgE2qZ1LbIKMPerEq/gIeTCKUHaB6v9QJmjmEY1A01lkT3hL3eb6",
12,
},
{
"!@#$%^&*()",
"51NJndAjnyZOvS7YSH6rWe",
"$2b$12$51NJndAjnyZOvS7YSH6rWesdaN02VMVMQnxv2b48Oe.pBxe1mFg6K",
12,
},
{
"LI\"}41SWG(SD@^:~td",
"hakLP0gLwtpiA0LB.jgEP.",
"$2b$12$hakLP0gLwtpiA0LB.jgEP.NCyuc8GkA.k943vBdX6qMJie5flQaJO",
12,
},
{
"VTaT^O<b%[8\\M7CJ&krtVTaT^O<b%[8\\M7CJ&krt",
"o3Q7Grn/7RHqockRlJWave",
"$2b$12$o3Q7Grn/7RHqockRlJWaveTMz1KcClmMaDR.KAnV3gPUlwcNsSfKq",
12,
},
{
"\"j%MgQ\"c{dRr07FDO{qo1j%MgQ\"c{dRr07FDO{qo1",
"uG5.qLAVM6g9oFp6ucDAZe",
"$2b$12$uG5.qLAVM6g9oFp6ucDAZe7QfjAz8qSFB8pFEximoK856UbnXCD.i",
12,
},
{
"HI`#ZWSY,wCXj>jIz(=-8AM[+\"L$${l(:]LBih&?)KHe*rLN$,z_g<]WWP1#Udh#\\gN+M9n*4",
"qJAEBcCXXO5bF.O1iZhy9u",
"$2b$12$qJAEBcCXXO5bF.O1iZhy9uEl35W84j9d1H6OAVfP19uR8hhS4QQzy",
12,
},
// 57 byte password of old TestTooLongPasswordWord test. Notice that salt is repeated.
{
"012345678901234567890123456789012345678901234567890123456",
"XajjQvNhvvRt5GSeFk1xFe",
"$2a$10$XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C",
10,
},
}

// Generated with python's bcrypt
var randomSalts = []string {
"Te0tzvXK54kCPxTib.Yrqe",
"Sk24alQjTsdXwSlaUdUGNe",
"CSzKaVGc70Z74Nbsu0lJje",
"xXMqLl4/t21aJHlTcBN4h.",
"GU.WqBHNelnEkg5ZfVDUR.",
"qh0/aSSVJBx4cvMOtBsucO",
"Oy5dSRPysuM6X/mVxuKmJO",
"wuFoMgC2HEPHh87aifJOl.",
"AiPCQjKBaVGaul9/XMp6Xe",
"UdNZfjHo56pN9s7yawvWEu",
}

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")},
}
Loading

0 comments on commit 1fa7f40

Please sign in to comment.