mirror of
https://github.com/wneessen/apg-go.git
synced 2024-11-08 23:42:53 +01:00
Winni Neessen
499a82d884
A FixedLength field was added to the Config struct and a corresponding command line flag was added in `apg.go`. The field allows for the generation of passwords of a fixed length, overriding the MinLength and MaxLength values if present. Revised the `random.go` script to accommodate this change. The option for fixed length enhances the flexibility and customization of the password generation tool.
131 lines
3.3 KiB
Go
131 lines
3.3 KiB
Go
package apg
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
// 7 bits to represent a letter index
|
|
letterIdxBits = 7
|
|
// All 1-bits, as many as letterIdxBits
|
|
letterIdxMask = 1<<letterIdxBits - 1
|
|
// # of letter indices fitting in 63 bits)
|
|
letterIdxMax = 63 / letterIdxBits
|
|
)
|
|
|
|
var (
|
|
// ErrInvalidLength is returned if the provided maximum number is equal or less than zero
|
|
ErrInvalidLength = errors.New("provided length value cannot be zero or less")
|
|
// ErrLengthMismatch is returned if the number of generated bytes does not match the expected length
|
|
ErrLengthMismatch = errors.New("number of generated random bytes does not match the expected length")
|
|
// ErrInvalidCharRange is returned if the given range of characters is not valid
|
|
ErrInvalidCharRange = errors.New("provided character range is not valid or empty")
|
|
)
|
|
|
|
// RandomBytes returns a byte slice of random bytes with length n that got generated by
|
|
// the crypto/rand generator
|
|
func (g *Generator) RandomBytes(n int64) ([]byte, error) {
|
|
if n < 1 {
|
|
return nil, ErrInvalidLength
|
|
}
|
|
b := make([]byte, n)
|
|
l, err := rand.Read(b)
|
|
if int64(l) != n {
|
|
return nil, ErrLengthMismatch
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
// RandomStringFromCharRange returns a random string of length l based of the range of characters given.
|
|
// The method makes use of the crypto/random package and therfore is
|
|
// cryptographically secure
|
|
func (g *Generator) RandomStringFromCharRange(l int, cr string) (string, error) {
|
|
if l < 1 {
|
|
return "", ErrInvalidLength
|
|
}
|
|
if len(cr) < 1 {
|
|
return "", ErrInvalidCharRange
|
|
}
|
|
rs := strings.Builder{}
|
|
rs.Grow(l)
|
|
crl := len(cr)
|
|
|
|
rp := make([]byte, 8)
|
|
_, err := rand.Read(rp)
|
|
if err != nil {
|
|
return rs.String(), err
|
|
}
|
|
for i, c, r := l-1, binary.BigEndian.Uint64(rp), letterIdxMax; i >= 0; {
|
|
if r == 0 {
|
|
_, err = rand.Read(rp)
|
|
if err != nil {
|
|
return rs.String(), err
|
|
}
|
|
c, r = binary.BigEndian.Uint64(rp), letterIdxMax
|
|
}
|
|
if idx := int(c & letterIdxMask); idx < crl {
|
|
rs.WriteByte(cr[idx])
|
|
i--
|
|
}
|
|
c >>= letterIdxBits
|
|
r--
|
|
}
|
|
|
|
return rs.String(), nil
|
|
}
|
|
|
|
// RandNum generates a random, non-negative number with given maximum value
|
|
func (g *Generator) RandNum(m int64) (int64, error) {
|
|
if m < 1 {
|
|
return 0, ErrInvalidLength
|
|
}
|
|
mbi := big.NewInt(m)
|
|
rn, err := rand.Int(rand.Reader, mbi)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("random number generation failed: %w", err)
|
|
}
|
|
return rn.Int64(), nil
|
|
}
|
|
|
|
// CoinFlip performs a simple coinflip based on the rand library and returns 1 or 0
|
|
func (g *Generator) CoinFlip() int64 {
|
|
cf, _ := g.RandNum(2)
|
|
return cf
|
|
}
|
|
|
|
// CoinFlipBool performs a simple coinflip based on the rand library and returns true or false
|
|
func (g *Generator) CoinFlipBool() bool {
|
|
return g.CoinFlip() == 1
|
|
}
|
|
|
|
// GetPasswordLength returns the password length based on the given config
|
|
// parameters
|
|
func (g *Generator) GetPasswordLength() (int64, error) {
|
|
if g.config.FixedLength > 0 {
|
|
return g.config.FixedLength, nil
|
|
}
|
|
mil := g.config.MinLength
|
|
mal := g.config.MaxLength
|
|
if mil > mal {
|
|
mal = mil
|
|
}
|
|
diff := mal - mil + 1
|
|
ra, err := g.RandNum(diff)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to calculate password length: %w", err)
|
|
}
|
|
l := mil + ra
|
|
if l <= 0 {
|
|
return 1, nil
|
|
}
|
|
return l, nil
|
|
}
|