go-passwd/md5_crypt.go
2024-09-07 18:53:27 -05:00

111 lines
2.7 KiB
Go

package passwd
import "crypto/md5"
type MD5Crypt struct {
Passwd
}
// Make an MD5Crypt password instance.
func NewMD5CryptPasswd() PasswdInterface {
m := new(MD5Crypt)
m.Magic = MD5_CRYPT_MAGIC
// Max of 8 characters in the salt per the spec.
m.SaltLength = 8
// Set the interface to allow parents to call overriden functions.
m.i = m
return m
}
// Hash a password with salt using MD5 crypt standard.
func (a *MD5Crypt) Hash(password []byte, salt []byte) (hash []byte) {
magic := []byte(a.Magic)
// Salt should be a maximum of 8 characters.
if len(salt) > 8 {
salt = salt[0:8]
}
// Encode pass, salt, pass hash to feed into the next hash.
h := md5.New()
h.Write(password)
h.Write(salt)
h.Write(password)
result := h.Sum(nil)
// Encode pass, magic, salt, and some extra stuff to help limit brute force attacks.
h.Reset()
h.Write(password)
h.Write(magic)
h.Write(salt)
// Append characters from the prior encode until it equals the length of the password.
HashBlockRecycle(h, result, len(password))
// For compatibility: Every 1 bit of the password length, append null.
// Every 0 bit of the password length, append the first character of the
// password. Yes, this is a weird thing. But think, weird thing equals
// harder for brute forcers.
result = []byte{'\000'}
var cnt int
for cnt = len(password); cnt > 0; cnt >>= 1 {
if cnt&1 != 0 {
h.Write(result[:1])
} else {
h.Write(password[:1])
}
}
// Compute the hash to feed into the 1000 iterations.
result = h.Sum(nil)
// For 1000 iterations, make a new hash feeding the prior hash,
// password, and salt at different points. This is designed to
// limit brute force attempts, although todays tech is fast.
for cnt = 0; cnt < 1000; cnt++ {
h.Reset()
// Add pass or prior result depending on bit of current iteration.
if cnt&1 != 0 {
h.Write(password)
} else {
h.Write(result)
}
// Add salt for numbers not divisible by 3.
if cnt%3 != 0 {
h.Write(salt)
}
// Add password for numbers not divisible by 7.
if cnt%7 != 0 {
h.Write(password)
}
// 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 {
h.Write(password)
}
// Compute hash for next round.
result = h.Sum(nil)
}
// Create hash with result.
b64 := MD5Base64Encode(result)
hash = append(magic, salt...)
hash = append(hash, '$')
hash = append(hash, b64...)
return
}
// Override the passwd hash with salt function to hash with MD5 crypt.
func (a *MD5Crypt) HashPasswordWithSalt(password []byte, salt []byte) (hash []byte, err error) {
hash = a.Hash(password, salt)
return
}