I am having a problem with some Go code that I wrote for a password authentication library. The general idea is to provide 2 functions: Check () and New (), which are provided with a password and a 256-bit HMAC key. The Check () function also provides a 256-bit salt and 256-bit hash and returns a boolean value. The New () function returns a new random salt and the corresponding hash. Both functions rely on a helper function, hash (), which uses scrypt to extend the key and does the real job of generating an output hash.
This worked when I originally wrote it (as evidenced by the fact that I have test data generated earlier, a lost revision of the code).
The problem I'm currently facing is that the Check () function seems to work fine when it is provided with the data generated by the old version of the code, but now it seems that the data failure was generated by the native New () code (which use the main hash () function).
I know I had to have a git version running the code from the start! Now I have learned my lesson.
I grouped the functions and quickly demonstrated the problem into a single .go file, as shown below, and added some debugging results:
package main import ( "code.google.com/p/go.crypto/scrypt" "crypto/hmac" "crypto/rand" "crypto/sha256" "crypto/subtle" "errors" "fmt" "io" ) // Constants for scrypt. See code.google.com/p/go.crypto/scrypt const ( KEYLENGTH = 32 N = 16384 R = 8 P = 1 ) // hash takes an HMAC key, a password and a salt (as byte slices) // scrypt transforms the password and salt, and then HMAC transforms the result. // Returns the resulting 256 bit hash. func hash(hmk, pw, s []byte) (h []byte, err error) { sch, err := scrypt.Key(pw, s, N, R, P, KEYLENGTH) if err != nil { return nil, err } hmh := hmac.New(sha256.New, hmk) hmh.Write(sch) h = hmh.Sum(nil) hmh.Reset() // Probably not necessary return h, nil } // Check takes an HMAC key, a hash to check, a password and a salt (as byte slices) // Calls hash(). // Compares the resulting 256 bit hash against the check hash and returns a boolean. func Check(hmk, h, pw, s []byte) (chk bool, err error) { // Print the input hash fmt.Printf("Hash: %x\nHMAC: %x\nSalt: %x\nPass: %x\n", h, hmk, s, []byte(pw)) hchk, err := hash(hmk, pw, s) if err != nil { return false, err } // Print the hash to compare against fmt.Printf("Hchk: %x\n", hchk) if subtle.ConstantTimeCompare(h, hchk) != 1 { return false, errors.New("Error: Hash verification failed") } return true, nil } // New takes an HMAC key and a password (as byte slices) // Generates a new salt using "crypto/rand" // Calls hash(). // Returns the resulting 256 bit hash and salt. func New(hmk, pw []byte) (h, s []byte, err error) { s = make([]byte, KEYLENGTH) _, err = io.ReadFull(rand.Reader, s) if err != nil { return nil, nil, err } h, err = hash(pw, hmk, s) if err != nil { return nil, nil, err } fmt.Printf("Hash: %x\nSalt: %x\nPass: %x\n", h, s, []byte(pw)) return h, s, nil } func main() { // Known values that work pass := "pleaseletmein" hash := []byte{ 0x6f, 0x38, 0x7b, 0x9c, 0xe3, 0x9d, 0x9, 0xff, 0x6b, 0x1c, 0xc, 0xb5, 0x1, 0x67, 0x1d, 0x11, 0x8f, 0x72, 0x78, 0x85, 0xca, 0x6, 0x50, 0xd0, 0xe6, 0x8b, 0x12, 0x9c, 0x9d, 0xf4, 0xcb, 0x29, } salt := []byte{ 0x77, 0xd6, 0x57, 0x62, 0x38, 0x65, 0x7b, 0x20, 0x3b, 0x19, 0xca, 0x42, 0xc1, 0x8a, 0x4, 0x97, 0x48, 0x44, 0xe3, 0x7, 0x4a, 0xe8, 0xdf, 0xdf, 0xfa, 0x3f, 0xed, 0xe2, 0x14, 0x42, 0xfc, 0xd0, } hmac := []byte{ 0x70, 0x23, 0xbd, 0xcb, 0x3a, 0xfd, 0x73, 0x48, 0x46, 0x1c, 0x6, 0xcd, 0x81, 0xfd, 0x38, 0xeb, 0xfd, 0xa8, 0xfb, 0xba, 0x90, 0x4f, 0x8e, 0x3e, 0xa9, 0xb5, 0x43, 0xf6, 0x54, 0x5d, 0xa1, 0xf2, } // Check the known values. This Works. fmt.Println("Checking known values...") chk, err := Check(hmac, hash, []byte(pass), salt) if err != nil { fmt.Printf("%s\n", err) } fmt.Printf("%t\n", chk) fmt.Println() // Create new hash and salt from the known HMAC and Salt fmt.Println("Creating new hash and salt values...") h, s, err := New(hmac, []byte(pass)) if err != nil { fmt.Printf("%s\n", err) } // Check the new values. This Fails! fmt.Println("Checking new hash and salt values...") chk, err = Check(hmac, h, []byte(pass), s) if err != nil { fmt.Printf("%s\n", err) } fmt.Printf("%t\n", chk) }
I tried this on both a 64-bit and a 64-bit Windows8 computer, and it does not work on both.
Any help would be greatly appreciated! As I said, at some point I had this job, but I seem to have broken it somewhere along the way. I usually found that it doesn't work when writing unit tests ... I suppose they are needed!
Thanks,
Mike.