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