Here's a complete hashing utilities funcs I wrote based on RFC 2898 / PKCS #5 v2.0
.
Hash
can be used to hash passwords, straight forward something like Hash("hello")
while Verify
can be used to raw password against a hash, basically what it does is to hashes the raw string and compares it with the actual hash.
package common
import (
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"golang.org/x/crypto/pbkdf2"
"io"
"strconv"
"strings"
)
const (
SALT_BYTE_SIZE = 24
HASH_BYTE_SIZE = 24
PBKDF2_ITERATIONS = 1000
)
func Hash(password string) (string, error) {
salt := make([]byte, SALT_BYTE_SIZE)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
fmt.Print("Err generating random salt")
return "", errors.New("Err generating random salt")
}
//todo: enhance: randomize itrs as well
hbts := pbkdf2.Key([]byte(password), salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE, sha1.New)
//hbtstr := fmt.Sprintf("%x", hbts)
return fmt.Sprintf("%v:%v:%v",
PBKDF2_ITERATIONS,
base64.StdEncoding.EncodeToString(salt),
base64.StdEncoding.EncodeToString(hbts)), nil
}
func Verify(raw, hash string) (bool, error) {
hparts := strings.Split(hash, ":")
itr, err := strconv.Atoi(hparts[0])
if err != nil {
fmt.Printf("wrong hash %v", hash)
return false, errors.New("wrong hash, iteration is invalid")
}
salt, err := base64.StdEncoding.DecodeString(hparts[1])
if err != nil {
fmt.Print("wrong hash, salt error:", err)
return false, errors.New("wrong hash, salt error:" + err.Error())
}
hsh, err := base64.StdEncoding.DecodeString(hparts[2])
if err != nil {
fmt.Print("wrong hash, hash error:", err)
return false, errors.New("wrong hash, hash error:" + err.Error())
}
rhash := pbkdf2.Key([]byte(raw), salt, itr, len(hsh), sha1.New)
return equal(rhash, hsh), nil
}
//bytes comparisons
func equal(h1, h2 []byte) bool {
diff := uint32(len(h1)) ^ uint32(len(h2))
for i := 0; i < len(h1) && i < len(h2); i++ {
diff |= uint32(h1[i] ^ h2[i])
}
return diff == 0
}
Here's unit test that would help you figuring out how to call such funcs
package common
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestHash(t *testing.T) {
hash, err := Hash("hello")
assert.Nil(t, err)
assert.NotEmpty(t, hash)
}
func TestVerify(t *testing.T) {
hash, err := Hash("hello")
assert.Nil(t, err)
assert.NotEmpty(t, hash)
ok, err := Verify("hello", hash)
assert.Nil(t, err)
assert.True(t, ok)
}