apg-go/random.go
Winni Neessen ac97b94ec9
Refactor generator and add config options
Refactored the generator to include a new config option, changed function signatures to follow the new structure, and renamed the function 'RandomString' to 'RandomStringFromCharRange' for clarity. Also, added a new mode and algorithm feature to enhance password generation. Furthermore, added several tests for new features and configurations. Adapted the CLI to use the new configuration approach. This refactoring was necessary to improve the customizability and clarity of the password generation process. Fixed minor issues and added '.gitignore' for clean commits in the future.
2023-08-04 16:02:58 +02:00

108 lines
2.8 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
}