1
0
Fork 0
mirror of https://github.com/Luzifer/password.git synced 2024-12-20 12:51:17 +00:00
password/lib/generator.go
Knut Ahlers 83660a3cdf
Update dependencies, replace usage of fmt.Errorf
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2022-06-27 19:45:12 +02:00

155 lines
4.6 KiB
Go

// Package securepassword implements a password generator and check.
package securepassword
import (
"fmt"
"regexp"
"strings"
"github.com/pkg/errors"
)
const minPasswordLength = 4
// SecurePassword provides methods for generating secure passwords and
// checking the security requirements of passwords
type SecurePassword struct {
characterTables map[string]string
insecurePattern []string
badCharacters []string
}
// ErrLengthTooLow represents an error thrown if the password will
// never be able match the security considerations in this package
var ErrLengthTooLow = errors.New("passwords with a length lower than 4 will never meet the security requirements")
// NewSecurePassword initializes a new SecurePassword generator
func NewSecurePassword() *SecurePassword {
return &SecurePassword{
characterTables: map[string]string{
"numeric": "0123456789",
"simple": "abcdefghijklmnopqrstuvwxyz",
"special": "!#$%&()*+,-_./:;=?@[]^{}~|",
},
insecurePattern: []string{
"abcdefghijklmnopqrstuvwxyz", // Alphabet
"zyxwvutsrqponmlkjihgfedcba", // Alphabet reversed
"01234567890", // Numeric increasing
"09876543210", // Numeric decreasing
"qwertzuiopasdfghjklyxcvbnm", // German keyboard layout
"mnbvcxylkjhgfdsapoiuztrewq", // German keyboard layout reversed
"qwertyuiopasdfghjklzxcvbnm", // US keyboard layout
"mnbvcxzlkjhgfdsapoiuytrewq", // US keyboard layour reversed
"789_456_123_147_258_369_159_753", // Numpad patterns
},
badCharacters: []string{"I", "l", "0", "O", "B", "8"}, // Characters that could lead to confusion due to font
}
}
// GeneratePassword generates a new password with a given length and
// optional special characters in it. The password is automatically
// checked against CheckPasswordSecurity in order to only deliver secure
// passwords.
func (s *SecurePassword) GeneratePassword(length int, special bool) (string, error) {
// Sanity check
if length < minPasswordLength {
return "", ErrLengthTooLow
}
characterTable := strings.Join([]string{
s.characterTables["simple"],
strings.ToUpper(s.characterTables["simple"]),
s.characterTables["numeric"],
}, "")
if special {
characterTable = strings.Join([]string{characterTable, s.characterTables["special"]}, "")
}
password := ""
for {
cidx, err := randIntn(len(characterTable))
if err != nil {
return "", errors.Wrap(err, "generating random number")
}
char := string(characterTable[cidx])
if strings.Contains(strings.Join(s.badCharacters, ""), char) {
continue
}
password = fmt.Sprintf("%s%s",
password,
char,
)
if len(password) == length {
if s.CheckPasswordSecurity(password, special) {
break
}
password = ""
}
}
return password, nil
}
// CheckPasswordSecurity executes three checks to ensure the passwords
// meet the security considerations in this package:
//
// 1. The password may not contain pattern found on the keyboard or in alphabet
// 2. The password must have 3 or 4 different character groups in it
// 3. The password may not have repeating characters
func (s *SecurePassword) CheckPasswordSecurity(password string, needsSpecialCharacters bool) bool {
return !s.hasInsecurePattern(password) &&
s.matchesBasicSecurity(password, needsSpecialCharacters) &&
!s.hasCharacterRepetition(password)
}
func (s *SecurePassword) hasInsecurePattern(password string) bool {
for i := 0; i < len(password)-3; i++ {
slice := password[i : i+3] // Extract an 3 char slice to check
for _, pattern := range s.insecurePattern {
if strings.Contains(pattern, slice) {
return true
}
if strings.Contains(strings.ToUpper(pattern), slice) {
return true
}
}
}
return false
}
func (s *SecurePassword) matchesBasicSecurity(password string, needsSpecialCharacters bool) bool {
bytePassword := []byte(password)
// Passwords does require numeric characters
if !regexp.MustCompile(`[0-9]`).Match(bytePassword) {
return false
}
// Passwords does require lowercase alphabetical characters
if !regexp.MustCompile(`[a-z]`).Match(bytePassword) {
return false
}
// Passwords does require uppercase alphabetical characters
if !regexp.MustCompile(`[A-Z]`).Match(bytePassword) {
return false
}
// If request was to require special characters check for their existence
if needsSpecialCharacters && !regexp.MustCompile(`[^a-zA-Z0-9]`).Match(bytePassword) {
return false
}
return true
}
func (s *SecurePassword) hasCharacterRepetition(password string) bool {
for i := 1; i < len(password); i++ {
if password[i-1] == password[i] {
return true
}
}
return false
}