1
0
Fork 0
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:
Knut Ahlers 2015-05-02 15:03:27 +02:00
commit 8896698ac2
4 changed files with 344 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
password

121
lib/generator.go Normal file
View 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
View 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
View 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)))
}