mirror of https://gogs.blitter.com/RLabs/xs
97 lines
2.5 KiB
Go
97 lines
2.5 KiB
Go
|
// 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
|
||
|
}
|