mirror of
https://github.com/Luzifer/password.git
synced 2024-11-08 17:30:10 +00:00
Initial running version
This commit is contained in:
commit
8896698ac2
4 changed files with 344 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
password
|
121
lib/generator.go
Normal file
121
lib/generator.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package securepassword // import "github.com/Luzifer/password/lib"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SecurePassword struct {
|
||||||
|
characterTables map[string]string
|
||||||
|
insecurePattern []string
|
||||||
|
badCharacters []string
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SecurePassword) GeneratePassword(length int, special bool) string {
|
||||||
|
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 := ""
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
for {
|
||||||
|
password = fmt.Sprintf("%s%s",
|
||||||
|
password,
|
||||||
|
string(characterTable[rand.Intn(len(characterTable))]),
|
||||||
|
)
|
||||||
|
if len(password) == length {
|
||||||
|
if s.CheckPasswordSecurity(password, special) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
password = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return password
|
||||||
|
}
|
||||||
|
|
||||||
|
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 existance
|
||||||
|
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
|
||||||
|
}
|
136
lib/generator_test.go
Normal file
136
lib/generator_test.go
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
package securepassword
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestInsecurePasswords(t *testing.T) {
|
||||||
|
passwords := map[string]string{
|
||||||
|
"8452028337962356": "Password with only numeric characters was accepted.",
|
||||||
|
"adfgjadrgdagasdf": "Password with only lowercase characters was accepted.",
|
||||||
|
"ASEFSTDHQAEGFADF": "Password with only uppercase characters was accepted.",
|
||||||
|
"135fach74nc94bd6": "Password without uppercase characters was accepted.",
|
||||||
|
"235JGOA0YTVKS46S": "Password without lowercase characters was accepted.",
|
||||||
|
|
||||||
|
"cKTn5mQXfasdS6qy": "Password with pattern asd was accepted.",
|
||||||
|
"cKTn5mQXfdsaS6qy": "Password with pattern dsa was accepted.",
|
||||||
|
"cKTn5mQXf345S6qy": "Password with pattern 345 was accepted.",
|
||||||
|
"cKTn5mQXf987S6qy": "Password with pattern 987 was accepted.",
|
||||||
|
"cKTn5mQXfabcS6qy": "Password with pattern abc was accepted.",
|
||||||
|
"cKTn5mQXfcbaS6qy": "Password with pattern cba was accepted.",
|
||||||
|
"cKTn5mQXfABCS6qy": "Password with pattern ABC was accepted.",
|
||||||
|
"cKTn5mQXfONMS6qy": "Password with pattern ONM was accepted.",
|
||||||
|
|
||||||
|
"Gncj5zzK29Dvx92h": "Password with character repetition was accepted",
|
||||||
|
"Gncj5%%K29Dvx92h": "Password with character repetition was accepted",
|
||||||
|
"Gncj55%K29Dvx92h": "Password with character repetition was accepted",
|
||||||
|
}
|
||||||
|
|
||||||
|
for password, errorMessage := range passwords {
|
||||||
|
if NewSecurePassword().CheckPasswordSecurity(password, false) {
|
||||||
|
t.Error(errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecurePasswords(t *testing.T) {
|
||||||
|
passwords := []string{
|
||||||
|
"6e1GZ6V2empWAky5Z13a",
|
||||||
|
"DLHZA2zfWor1XUoJYvFR",
|
||||||
|
"sMf3uNf2E1pxPFMymah5",
|
||||||
|
"prb4tX1vtyVL7R316dKU",
|
||||||
|
"7bWc9C1ciL62h5u26Z9g",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, password := range passwords {
|
||||||
|
if !NewSecurePassword().CheckPasswordSecurity(password, false) {
|
||||||
|
t.Errorf("Password was rejected: %s", password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPasswordWithoutSpecialCharaterFail(t *testing.T) {
|
||||||
|
passwords := []string{
|
||||||
|
"6e1GZ6V2empWAky5Z13a",
|
||||||
|
"DLHZA2zfWor1XUoJYvFR",
|
||||||
|
"sMf3uNf2E1pxPFMymah5",
|
||||||
|
"prb4tX1vtyVL7R316dKU",
|
||||||
|
"7bWc9C1ciL62h5u26Z9g",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, password := range passwords {
|
||||||
|
if NewSecurePassword().CheckPasswordSecurity(password, true) {
|
||||||
|
t.Errorf("Password was accepted: %s", password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecurePasswordWithSpecialCharacter(t *testing.T) {
|
||||||
|
passwords := []string{
|
||||||
|
`a*5S(+zQ9=<J~bH}!F])`,
|
||||||
|
`9?)k1Ge?[F}5w{#$Ho(6`,
|
||||||
|
`LRrot)%qVH!>3%/1MiNr`,
|
||||||
|
`/K}.]C4-a/{,39r$"(D+`,
|
||||||
|
`9dk#:@xjPd_m$:F"}>Cj`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, password := range passwords {
|
||||||
|
if !NewSecurePassword().CheckPasswordSecurity(password, true) {
|
||||||
|
t.Errorf("Password was rejected: %s", password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGeneratePasswords8Char(b *testing.B) {
|
||||||
|
pwd := NewSecurePassword()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
pwd.GeneratePassword(8, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGeneratePasswords8CharSpecial(b *testing.B) {
|
||||||
|
pwd := NewSecurePassword()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
pwd.GeneratePassword(8, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGeneratePasswords16Char(b *testing.B) {
|
||||||
|
pwd := NewSecurePassword()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
pwd.GeneratePassword(16, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGeneratePasswords16CharSpecial(b *testing.B) {
|
||||||
|
pwd := NewSecurePassword()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
pwd.GeneratePassword(16, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGeneratePasswords32Char(b *testing.B) {
|
||||||
|
pwd := NewSecurePassword()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
pwd.GeneratePassword(32, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGeneratePasswords32CharSpecial(b *testing.B) {
|
||||||
|
pwd := NewSecurePassword()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
pwd.GeneratePassword(32, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGeneratePasswords128Char(b *testing.B) {
|
||||||
|
pwd := NewSecurePassword()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
pwd.GeneratePassword(128, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGeneratePasswords128CharSpecial(b *testing.B) {
|
||||||
|
pwd := NewSecurePassword()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
pwd.GeneratePassword(128, true)
|
||||||
|
}
|
||||||
|
}
|
86
main.go
Normal file
86
main.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Luzifer/password/lib"
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pwd *securepassword.SecurePassword
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pwd = securepassword.NewSecurePassword()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Usage = "generates secure random passwords"
|
||||||
|
app.Version = "1.0.0"
|
||||||
|
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "serve",
|
||||||
|
Usage: "start an API server to request passwords",
|
||||||
|
Action: startAPIServer,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "port",
|
||||||
|
Value: 3000,
|
||||||
|
Usage: "port to listen on",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "get",
|
||||||
|
Usage: "generate and return a secure random password",
|
||||||
|
Action: printPassword,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "length, l",
|
||||||
|
Value: 20,
|
||||||
|
Usage: "length of the generated password",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "special, s",
|
||||||
|
Usage: "use special characters in your password",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startAPIServer(c *cli.Context) {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/v1/getPassword", handleAPIGetPasswordv1).Methods("GET")
|
||||||
|
|
||||||
|
http.Handle("/", r)
|
||||||
|
http.ListenAndServe(fmt.Sprintf(":%d", c.Int("port")), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printPassword(c *cli.Context) {
|
||||||
|
fmt.Println(pwd.GeneratePassword(c.Int("length"), c.Bool("special")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAPIGetPasswordv1(res http.ResponseWriter, r *http.Request) {
|
||||||
|
length, err := strconv.Atoi(r.URL.Query().Get("length"))
|
||||||
|
if err != nil {
|
||||||
|
length = 20
|
||||||
|
}
|
||||||
|
special := r.URL.Query().Get("special") == "true"
|
||||||
|
|
||||||
|
if length > 128 {
|
||||||
|
http.Error(res, "Please do not use length with more than 128 characters!", http.StatusNotAcceptable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Header().Add("Content-Type", "text/plain")
|
||||||
|
res.Header().Add("Cache-Control", "no-cache")
|
||||||
|
res.Write([]byte(pwd.GeneratePassword(length, special)))
|
||||||
|
}
|
Loading…
Reference in a new issue