187 lines
4.4 KiB
Go
187 lines
4.4 KiB
Go
package passwd
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
BCRYPT_MAX_HASH_SIZE = 23
|
|
)
|
|
|
|
// BCrypt implementation of the PasswdInterface.
|
|
type BCrypt struct {
|
|
Passwd
|
|
}
|
|
|
|
// Creates a new BCrypt password instance.
|
|
func NewBCryptPasswd() PasswdInterface {
|
|
m := new(BCrypt)
|
|
m.Magic = BCRYPT_MAGIC
|
|
// Default parameters: cost 14, minor 'a'.
|
|
m.Params = "a$14"
|
|
m.SaltLength = 16
|
|
m.i = m
|
|
return m
|
|
}
|
|
|
|
// IV for the 64 Blowfish encryption calls in bcrypt().
|
|
// It is the string "OrpheanBeholderScryDoubt" in big-endian bytes.
|
|
var magicCipherData = []byte{
|
|
0x4f, 0x72, 0x70, 0x68,
|
|
0x65, 0x61, 0x6e, 0x42,
|
|
0x65, 0x68, 0x6f, 0x6c,
|
|
0x64, 0x65, 0x72, 0x53,
|
|
0x63, 0x72, 0x79, 0x44,
|
|
0x6f, 0x75, 0x62, 0x74,
|
|
}
|
|
|
|
// Sets up the Blowfish cipher state with the expensive key expansion and salt mixing required by BCrypt.
|
|
func (a *BCrypt) setupBlowfishCipher(key []byte, cost uint32, salt []byte, bug bool, safety bool) (*BfCipher, error) {
|
|
csalt, err := BCryptBase64Decode(salt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Base64 decoding 22 chars produces 18 bytes (due to padding/alignment).
|
|
// We must truncate to 16 bytes to match libxcrypt behavior.
|
|
if len(csalt) > 16 {
|
|
csalt = csalt[:16]
|
|
}
|
|
|
|
// Bug compatibility with C bcrypt implementations. They use the trailing
|
|
// NULL in the key string during expansion.
|
|
// We copy the key to prevent changing the underlying array.
|
|
ckey := append(key[:len(key):len(key)], 0)
|
|
|
|
c := NewBfCipher()
|
|
c.SetKey(ckey, bug, safety)
|
|
c.ExpandKeyWithSalt(csalt)
|
|
|
|
var i, rounds uint64
|
|
rounds = 1 << cost
|
|
for i = 0; i < rounds; i++ {
|
|
c.ExpandKey(ckey)
|
|
c.ExpandKey(csalt)
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
// Generates a hash for the given password and salt using BCrypt.
|
|
func (a *BCrypt) Hash(password []byte, salt []byte) ([]byte, error) {
|
|
// Defaults.
|
|
var cost uint32 = 14
|
|
var minor byte = 'a'
|
|
|
|
// Parse parameters: "minor$cost".
|
|
if a.Params != "" {
|
|
idx := strings.Index(a.Params, "$")
|
|
if idx != -1 {
|
|
// Found separator.
|
|
if idx > 0 {
|
|
minor = a.Params[0]
|
|
}
|
|
// Parse cost.
|
|
if len(a.Params) > idx+1 {
|
|
cStr := a.Params[idx+1:]
|
|
if c, cErr := strconv.ParseUint(cStr, 10, 32); cErr == nil {
|
|
cost = uint32(c)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure salt is truncated to 22 bytes.
|
|
if len(salt) > 22 {
|
|
salt = salt[:22]
|
|
}
|
|
|
|
cipherData := make([]byte, len(magicCipherData))
|
|
copy(cipherData, magicCipherData)
|
|
|
|
// Determine Blowfish flags based on minor version.
|
|
var bug, safety bool
|
|
switch minor {
|
|
case 'a':
|
|
safety = true
|
|
case 'x':
|
|
bug = true
|
|
case 'b', 'y':
|
|
// No bug, no safety hack needed (standard).
|
|
default:
|
|
// Unknown minor version defaults to standard (safe).
|
|
}
|
|
|
|
c, err := a.setupBlowfishCipher(password, cost, salt, bug, safety)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for i := 0; i < 24; i += 8 {
|
|
for j := 0; j < 64; j++ {
|
|
c.Encrypt(cipherData[i:i+8], cipherData[i:i+8])
|
|
}
|
|
}
|
|
|
|
// Bug compatibility with C bcrypt implementations. We only encode 23 of
|
|
// the 24 bytes encrypted.
|
|
hashed := BCryptBase64Encode(cipherData[:BCRYPT_MAX_HASH_SIZE])
|
|
|
|
// Format output.
|
|
// $2<minor>$<cost>$<salt><hash>.
|
|
magic := "$2"
|
|
if minor != 0 {
|
|
magic += string(minor)
|
|
}
|
|
|
|
output := fmt.Sprintf("%s$%02d$%s%s", magic, cost, salt, hashed)
|
|
return []byte(output), nil
|
|
}
|
|
|
|
// Hashes the password using BCrypt with the provided salt.
|
|
func (a *BCrypt) HashPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error) {
|
|
return a.Hash(password, salt)
|
|
}
|
|
|
|
// Hash an password using default parameters with BCrypt.
|
|
func HashBCryptPassword(password []byte) (hash []byte, err error) {
|
|
passwd := NewBCryptPasswd()
|
|
hash, err = passwd.HashPassword(password)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// Hash an password with salt using default parameters with BCrypt.
|
|
func HashBCryptPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error) {
|
|
passwd := NewBCryptPasswd()
|
|
hash, err = passwd.HashPasswordWithSalt(password, salt)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// Hash an password string using default parameters with BCrypt.
|
|
func SHashBCryptPassword(password string) (hash string, err error) {
|
|
passwd := NewBCryptPasswd()
|
|
hash, err = passwd.SHashPassword(password)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// Hash an password string with salt using default parameters with BCrypt.
|
|
func SHashBCryptPasswordWithSalt(password string, salt string) (hash string, err error) {
|
|
passwd := NewBCryptPasswd()
|
|
hash, err = passwd.SHashPasswordWithSalt(password, salt)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|