109 lines
3.2 KiB
Go
109 lines
3.2 KiB
Go
package passgen
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"strings"
|
|
)
|
|
|
|
// GeneratePasswords generates random passwords based on the configuration provided by the user.
|
|
func GeneratePasswords(
|
|
count uint, // Number of passwords to generate.
|
|
length uint, // Length of each generated password.
|
|
alphabet string, // Alphabet to pull password characters from.
|
|
) (
|
|
passwords []string, // Generated passwords.
|
|
err error, // Possible error encountered during password generation.
|
|
) {
|
|
// Validate the supplied count parameter.
|
|
if count < PasswordCountMin || count > PasswordCountMax {
|
|
return nil, fmt.Errorf("count must be at least %d and at most %d", PasswordCountMin, PasswordCountMax)
|
|
}
|
|
|
|
// Validate the supplied length parameter.
|
|
if length < PasswordLengthMin || length > PasswordLengthMax {
|
|
return nil, fmt.Errorf("length must be at least %d and at most %d", PasswordLengthMin, PasswordLengthMax)
|
|
}
|
|
|
|
// Deduplicate the provided alphabet.
|
|
chars := map[rune]struct{}{}
|
|
for _, char := range alphabet {
|
|
chars[char] = struct{}{}
|
|
}
|
|
var charSet []rune
|
|
for char := range chars {
|
|
charSet = append(charSet, char)
|
|
}
|
|
|
|
// Validate the provided alphabet.
|
|
if len(charSet) < AlphabetLengthMin {
|
|
return nil, fmt.Errorf("alphabet must contain at least %d unique characters", AlphabetLengthMin)
|
|
}
|
|
|
|
// Determine how many bytes are needed to represent a password of the specified length in the
|
|
// provided alphabet.
|
|
bitsPerChar := uint(math.Ceil(math.Log2(float64(len(charSet)))))
|
|
bitsPerPassword := bitsPerChar * length
|
|
bytesPerPassword := bitsPerPassword / 8
|
|
if bitsPerPassword%8 > 0 {
|
|
bytesPerPassword++
|
|
}
|
|
|
|
var (
|
|
i uint // Password counter.
|
|
b strings.Builder // String builder for efficiently constructing passwords.
|
|
passwordBuffer []byte // Byte buffer for random data used as password source.
|
|
)
|
|
|
|
for i = 0; i < count; i++ {
|
|
// Read enough random data to sufficiently produce a password.
|
|
passwordBuffer = make([]byte, bytesPerPassword)
|
|
_, err = io.ReadFull(randSource, passwordBuffer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var (
|
|
j uint // Password character counter.
|
|
charIdx uint // Character index within the provided alphabet.
|
|
bitIdx uint // Source buffer bit counter.
|
|
byteIdx uint // Source buffer byte counter.
|
|
charBitIdx uint // Password character bit counter.
|
|
)
|
|
|
|
for j = 0; j < length; j++ {
|
|
for charBitIdx = 0; charBitIdx < bitsPerChar; charBitIdx++ {
|
|
// Left shift the character index to read the next bit.
|
|
charIdx <<= 1
|
|
|
|
// Set the bit from the password source in the character index.
|
|
charIdx |= uint((passwordBuffer[byteIdx] & (0x80 >> (bitIdx % 8))) >> (7 - (bitIdx % 8)))
|
|
|
|
// Increment the bit counter and, if necessary, the byte counter.
|
|
bitIdx++
|
|
if bitIdx%8 == 0 {
|
|
byteIdx++
|
|
}
|
|
}
|
|
|
|
// Ensure the character index is within the bounds of the alphabet.
|
|
charIdx = charIdx % uint(len(charSet))
|
|
|
|
// Retrieve the character from the alphabet and write it to the password.
|
|
b.WriteRune(charSet[charIdx])
|
|
|
|
// Reset character index value.
|
|
charIdx = 0
|
|
}
|
|
|
|
// Append the password to the return list.
|
|
passwords = append(passwords, b.String())
|
|
|
|
// Reset the string builder for the next password.
|
|
b.Reset()
|
|
}
|
|
|
|
return
|
|
}
|