1
0
Fork 0
forked from 0x2E/fusion
fusion/auth/password_test.go
Michael Lynch bfd4e8c66b Check passwords based on hashes rather than plaintext
fusion's current password mechanism is vulnerable to a timing attack:

https://en.wikipedia.org/wiki/Timing_attack

Because fusion checks passwords using simple character-by-character string comparison, a password attempt that begins with the correct characters will take longer to evaluate than one that starts with incorrect characters. For example, if the correct password is 'platypus123' then a password attempt of 'plates' will take longer to evaluate than 'spoons' because 'plates' and 'platypus' share a common prefix. An attacker who attempts the password 'plates' will know that they likely have the correct prefix.

To prevent the timing attack, this change hashes the user's password using PBKDF2 and compares hashes using subtle.ConstantTimeCompare, which is specifically designed to prevent timing attacks.
2025-01-12 11:31:01 -05:00

71 lines
1.6 KiB
Go

package auth_test
import (
"testing"
"github.com/0x2e/fusion/auth"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHashPassword(t *testing.T) {
for _, tt := range []struct {
explanation string
input string
wantErr error
}{
{
explanation: "valid password succeeds",
input: "mypassword",
wantErr: nil,
},
{
explanation: "empty password returns ErrPasswordTooShort",
input: "",
wantErr: auth.ErrPasswordTooShort,
},
} {
t.Run(tt.explanation, func(t *testing.T) {
got, err := auth.HashPassword(tt.input)
require.Equal(t, tt.wantErr, err)
if tt.wantErr == nil {
assert.NotEmpty(t, got.Bytes())
}
})
}
}
func TestHashedPasswordEquals(t *testing.T) {
for _, tt := range []struct {
explanation string
hashedPassword1 auth.HashedPassword
hashedPassword2 auth.HashedPassword
want bool
}{
{
explanation: "same passwords match",
hashedPassword1: mustHashPassword("password1"),
hashedPassword2: mustHashPassword("password1"),
want: true,
},
{
explanation: "different passwords don't match",
hashedPassword1: mustHashPassword("password1"),
hashedPassword2: mustHashPassword("password2"),
want: false,
},
} {
t.Run(tt.explanation, func(t *testing.T) {
assert.Equal(t, tt.want, tt.hashedPassword1.Equals(tt.hashedPassword2))
})
}
}
func mustHashPassword(password string) auth.HashedPassword {
hashedPassword, err := auth.HashPassword(password)
if err != nil {
panic(err)
}
return hashedPassword
}