// Package bcryptsha256 implements bcrypt with a SHA256 prehash in a format that is compatible with Python passlib's equivalent bcrypt-sha256 scheme. // // This is preferred over bcrypt because the prehash essentially renders bcrypt's password length // limitation irrelevant; although of course it is less compatible. package bcryptsha256 import "gopkg.in/hlandau/passlib.v1/abstract" import "gopkg.in/hlandau/passlib.v1/hash/bcrypt" import "encoding/base64" import "crypto/sha256" import "strings" import "fmt" type scheme struct { underlying abstract.Scheme cost int } // An implementation of Scheme implementing Python passlib's `$bcrypt-sha256$` // bcrypt variant. This is bcrypt with a SHA256 prehash, which removes bcrypt's // password length limitation. var Crypter abstract.Scheme // The recommended cost for bcrypt-sha256. This may change with subsequent releases. const RecommendedCost = bcrypt.RecommendedCost func init() { Crypter = New(bcrypt.RecommendedCost) } // Instantiates a new Scheme implementing bcrypt with the given cost. // // The recommended cost is RecommendedCost. func New(cost int) abstract.Scheme { return &scheme{ underlying: bcrypt.New(cost), cost: cost, } } func (s *scheme) Hash(password string) (string, error) { p := s.prehash(password) h, err := s.underlying.Hash(p) if err != nil { return "", err } return mangle(h), nil } func (s *scheme) Verify(password, hash string) error { p := s.prehash(password) return s.underlying.Verify(p, demangle(hash)) } func (s *scheme) prehash(password string) string { h := sha256.New() h.Write([]byte(password)) v := base64.StdEncoding.EncodeToString(h.Sum(nil)) return v } func (s *scheme) SupportsStub(stub string) bool { return strings.HasPrefix(stub, "$bcrypt-sha256$") && s.underlying.SupportsStub(demangle(stub)) } func (s *scheme) NeedsUpdate(stub string) bool { return s.underlying.NeedsUpdate(demangle(stub)) } func (s *scheme) String() string { return fmt.Sprintf("bcrypt-sha256(%d)", s.cost) } func demangle(stub string) string { if strings.HasPrefix(stub, "$bcrypt-sha256$2") { parts := strings.Split(stub[15:], "$") // 0: 2a,12 // 1: salt // 2: hash parts0 := strings.Split(parts[0], ",") return "$" + parts0[0] + "$" + fmt.Sprintf("%02s", parts0[1]) + "$" + parts[1] + parts[2] } else { return stub } } func mangle(hash string) string { parts := strings.Split(hash[1:], "$") // 0: 2a // 1: rounds // 2: salt + hash salt := parts[2][0:22] h := parts[2][22:] return "$bcrypt-sha256$" + parts[0] + "," + parts[1] + "$" + salt + "$" + h }