xs/vendor/github.com/jameskeane/bcrypt/bcrypt.go

191 lines
4.1 KiB
Go

package bcrypt
import (
"bytes"
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"errors"
"strconv"
"strings"
)
var (
InvalidRounds = errors.New("bcrypt: Invalid rounds parameter")
InvalidSalt = errors.New("bcrypt: Invalid salt supplied")
)
const (
MaxRounds = 31
MinRounds = 4
DefaultRounds = 12
SaltLen = 16
BlowfishRounds = 16
)
var enc = base64.NewEncoding("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
// Helper function to build the bcrypt hash string
// payload takes :
// * []byte -> which it base64 encodes it (trims padding "=") and writes it to the buffer
// * string -> which it writes straight to the buffer
func build_bcrypt_str(minor byte, rounds uint, payload ...interface{}) []byte {
rs := bytes.NewBuffer(make([]byte, 0, 61))
rs.WriteString("$2")
if minor >= 'a' {
rs.WriteByte(minor)
}
rs.WriteByte('$')
if rounds < 10 {
rs.WriteByte('0')
}
rs.WriteString(strconv.FormatUint(uint64(rounds), 10))
rs.WriteByte('$')
for _, p := range payload {
if pb, ok := p.([]byte); ok {
rs.WriteString(strings.TrimRight(enc.EncodeToString(pb), "="))
} else if ps, ok := p.(string); ok {
rs.WriteString(ps)
}
}
return rs.Bytes()
}
// Salt generation
func Salt(rounds ...int) (string, error) {
rb, err := SaltBytes(rounds...)
return string(rb), err
}
func SaltBytes(rounds ...int) (salt []byte, err error) {
r := DefaultRounds
if len(rounds) > 0 {
r = rounds[0]
if r < MinRounds || r > MaxRounds {
return nil, InvalidRounds
}
}
rnd := make([]byte, SaltLen)
read, err := rand.Read(rnd)
if read != SaltLen || err != nil {
return nil, err
}
return build_bcrypt_str('a', uint(r), rnd), nil
}
func consume(r *bytes.Buffer, b byte) bool {
got, err := r.ReadByte()
if err != nil {
return false
}
if got != b {
r.UnreadByte()
return false
}
return true
}
func Hash(password string, salt ...string) (ps string, err error) {
var s []byte
var pb []byte
if len(salt) == 0 {
s, err = SaltBytes()
if err != nil {
return
}
} else if len(salt) > 0 {
s = []byte(salt[0])
}
pb, err = HashBytes([]byte(password), s)
return string(pb), err
}
func HashBytes(password []byte, salt ...[]byte) (hash []byte, err error) {
var s []byte
if len(salt) == 0 {
s, err = SaltBytes()
if err != nil {
return
}
} else if len(salt) > 0 {
s = salt[0]
}
// TODO: use a regex? I hear go has bad regex performance a simple FSM seems faster
// "^\\$2([a-z]?)\\$([0-3][0-9])\\$([\\./A-Za-z0-9]{22}+)"
// Ok, extract the required information
minor := byte(0)
sr := bytes.NewBuffer(s)
if !consume(sr, '$') || !consume(sr, '2') {
return nil, InvalidSalt
}
if !consume(sr, '$') {
minor, _ = sr.ReadByte()
if minor != 'a' || !consume(sr, '$') {
return nil, InvalidSalt
}
}
rounds_bytes := make([]byte, 2)
read, err := sr.Read(rounds_bytes)
if err != nil || read != 2 {
return nil, InvalidSalt
}
if !consume(sr, '$') {
return nil, InvalidSalt
}
var rounds64 uint64
rounds64, err = strconv.ParseUint(string(rounds_bytes), 10, 0)
if err != nil {
return nil, InvalidSalt
}
rounds := uint(rounds64)
// TODO: can't we use base64.NewDecoder(enc, sr) ?
salt_bytes := make([]byte, 22)
read, err = sr.Read(salt_bytes)
if err != nil || read != 22 {
return nil, InvalidSalt
}
var saltb []byte
// encoding/base64 expects 4 byte blocks padded, since bcrypt uses only 22 bytes we need to go up
saltb, err = enc.DecodeString(string(salt_bytes) + "==")
if err != nil {
return nil, err
}
// cipher expects null terminated input (go initializes everything with zero values so this works)
password_term := make([]byte, len(password)+1)
copy(password_term, password)
hashed := crypt_raw(password_term, saltb[:SaltLen], rounds)
return build_bcrypt_str(minor, rounds, string(salt_bytes), hashed[:len(bf_crypt_ciphertext)*4-1]), nil
}
func Match(password, hash string) bool {
return MatchBytes([]byte(password), []byte(hash))
}
func MatchBytes(password []byte, hash []byte) bool {
h, err := HashBytes(password, hash)
if err != nil {
return false
}
return subtle.ConstantTimeCompare(h, hash) == 1
}