// Package bhash implements bcrypt_pbkdf key derivation function compatible to // OpenBSD implementation // // http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libutil/bcrypt_pbkdf.c package bhash import ( "bytes" "crypto/sha512" "encoding/binary" "golang.org/x/crypto/blowfish" ) const ( magic = "OxychromaticBlowfishSwatDynamite" rounds = 64 words = blowfish.BlockSize hashSize = 4 * words ) // Hash computes bcrypt hash func Hash(pass, salt []byte) ([]byte, error) { c, err := blowfish.NewSaltedCipher(pass, salt) if err != nil { return nil, err } // key expansion for i := 0; i < rounds; i++ { blowfish.ExpandKey(salt, c) blowfish.ExpandKey(pass, c) } // encryption buf := new(bytes.Buffer) for n := 0; n < len(magic)/words; n++ { v := []byte(magic[n*words : (n+1)*words]) for i := 0; i < rounds; i++ { c.Encrypt(v, v) } // swap bytes and copy out var u [2]uint32 binary.Read(bytes.NewReader(v), binary.LittleEndian, &u) binary.Write(buf, binary.BigEndian, u) } return buf.Bytes(), nil } // Pbkdf returns derivated key func Pbkdf(pass, salt []byte, iter, keyLen int) ([]byte, error) { // collapse password h := sha512.New() h.Write(pass) sha2pass := h.Sum(nil) numBlocks := (keyLen + hashSize - 1) / hashSize out := make([]byte, hashSize) key := make([]byte, hashSize*numBlocks) for n := 1; n <= numBlocks; n++ { // first round, salt is salt h.Reset() h.Write(salt) binary.Write(h, binary.BigEndian, uint32(n)) tmp, err := Hash(sha2pass, h.Sum(nil)) if err != nil { return nil, err } copy(out, tmp) for i := 1; i < iter; i++ { h.Reset() h.Write(tmp) tmp, err = Hash(sha2pass, h.Sum(nil)) if err != nil { return nil, err } for x := range tmp { out[x] ^= tmp[x] } } // pbkdf2 deviation: output the key material non-linearly for x := range out { dst := x*numBlocks + (n - 1) key[dst] = out[x] } } return key[:keyLen], nil }