go-passwd/sha256_crypt.go
2024-09-07 18:43:05 -05:00

142 lines
3.4 KiB
Go

package passwd
import (
"crypto/sha256"
"fmt"
)
type SHA256Crypt struct {
Passwd
}
// Make an MD5Crypt password instance.
func NewSHA256CryptPasswd() PasswdInterface {
m := new(SHA256Crypt)
m.Magic = SHA256_CRYPT_MAGIC
// Set the interface to allow parents to call overriden functions.
m.i = m
return m
}
// Hash a password with salt using SHA256 crypt standard.
func (a *SHA256Crypt) Hash(password []byte, salt []byte, iterations uint64) (hash []byte) {
// Salt should be a maximum of 16 characters.
if len(salt) > 16 {
salt = salt[0:16]
}
passwordLen := len(password)
saltLen := len(salt)
customIterations := true
if iterations == 0 {
customIterations = false
iterations = 5000
}
// Encode pass, salt, pass hash to feed into the next hash.
h := sha256.New()
h.Write(password)
h.Write(salt)
h.Write(password)
result := h.Sum(nil)
// Encod the password and salt, and recycle bytes from prior hash.
h.Reset()
h.Write(password)
h.Write(salt)
// Append characters from the prior encode until it equals the length of the password.
HashBlockRecycle(h, result, passwordLen)
// Alternate the prior encode with the password for the binary length of the password.
var cnt uint64
for cnt = uint64(passwordLen); cnt > 0; cnt >>= 1 {
if cnt&1 != 0 {
h.Write(result)
} else {
h.Write(password)
}
}
// Calculate sum for iterations.
result = h.Sum(nil)
// Calculate a hash of password added for each character of the password for recycling in iterations.
h.Reset()
for cnt = 0; cnt < uint64(passwordLen); cnt++ {
h.Write(password)
}
p_bytes := h.Sum(nil)
// For maximum salt size plus the integer representation of the first byte of the prior hash,
// write the entire salt to the hash for recycling in iterations.
h.Reset()
for cnt = 0; cnt < 16+uint64(result[0]); cnt++ {
h.Write(salt)
}
s_bytes := h.Sum(nil)
// For the defined number of interations, hash using bytes from
// the above password and salt hashes and prior hash iteration.
for cnt = 0; cnt < iterations; cnt++ {
h.Reset()
// Add pass or prior result depending on bit of current iteration.
if cnt&1 != 0 {
HashBlockRecycle(h, p_bytes, passwordLen)
} else {
h.Write(result)
}
// Add salt for numbers not divisible by 3.
if cnt%3 != 0 {
HashBlockRecycle(h, s_bytes, saltLen)
}
// Add password for numbers not divisible by 7.
if cnt%7 != 0 {
HashBlockRecycle(h, p_bytes, passwordLen)
}
// Add the reverse of the above pass or prior result.
// This ensures we at a minimum have both the password,
// and the prior result in the hash calculation for the round.
if cnt&1 != 0 {
h.Write(result)
} else {
HashBlockRecycle(h, p_bytes, passwordLen)
}
// Compute hash for next round.
result = h.Sum(nil)
}
output := fmt.Sprintf("%s%s$", a.Magic, salt)
if customIterations {
output = fmt.Sprintf("%srounds=%d$%s$", a.Magic, iterations, salt)
}
// Create hash with result.
b64 := Base64RotateEncode(result, false)
hash = []byte(output)
hash = append(hash, b64...)
return
}
// Override the passwd hash with salt function to hash with SHA256 crypt.
func (a *SHA256Crypt) HashPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error) {
// Parse iterations from parameter.
var iterations uint64
if a.Params != "" {
_, err = fmt.Sscanf(a.Params, "rounds=%d", &iterations)
if err != nil {
return
}
}
// Compute hash.
hash = a.Hash(password, salt, iterations)
return
}