// 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" "io" "golang.org/x/crypto/blowfish" ) var ( magic = []byte("OxychromaticBlowfishSwatDynamite") rounds = 64 words = blowfish.BlockSize hashSize = 4 * words be = binary.BigEndian le = binary.LittleEndian ) func swap(w io.Writer, data []byte) { var u [2]uint32 binary.Read(bytes.NewReader(data), le, &u) binary.Write(w, be, u) } // Hash computes bcrypt hash func Hash(pass, salt []byte) []byte { c, err := blowfish.NewSaltedCipher(pass, salt) if err != nil { panic(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 := make([]byte, words) copy(v, magic[n*words:(n+1)*words]) for i := 0; i < rounds; i++ { c.Encrypt(v, v) } // swap bytes and copy out swap(buf, v) } return buf.Bytes() } // Pbkdf returns derivated key func Pbkdf(pass, salt []byte, iter, keyLen int) []byte { // 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, be, uint32(n)) tmp := Hash(sha2pass, h.Sum(nil)) copy(out, tmp) for i := 1; i < iter; i++ { h.Reset() h.Write(tmp) tmp = Hash(sha2pass, h.Sum(nil)) 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] }