Skip to content

Commit b0fd05f

Browse files
committed
Implement password generator
Signed-off-by: Tamal Saha <[email protected]>
1 parent e940f1c commit b0fd05f

File tree

6 files changed

+239
-2
lines changed

6 files changed

+239
-2
lines changed

.github/workflows/ci.yml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
branches: [master]
8+
9+
jobs:
10+
build:
11+
name: Build
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Set up Go 1.x
15+
uses: actions/setup-go@v2
16+
with:
17+
go-version: ^1.15
18+
id: go
19+
20+
- uses: actions/checkout@v2
21+
22+
- name: Build
23+
run: go build -v ./...
24+
25+
- name: Test
26+
run: go test -v ./...

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@
1212
*.out
1313

1414
# Dependency directories (remove the comment below to include it)
15-
# vendor/
15+
vendor/
16+
17+
.idea/

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1+
[![PkgGoDev](https://pkg.go.dev/badge/gomodules.xyz/password-generator)](https://pkg.go.dev/gomodules.xyz/password-generator)
2+
13
# password-generator
2-
Generate Strong Password
4+
5+
Generates strong random password using algorithm described here:
6+
https://en.wikipedia.org/wiki/Random_password_generator#JavaScript

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module gomodules.xyz/password-generator
2+
3+
go 1.15

password.go

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package password
2+
3+
import (
4+
"crypto/rand"
5+
"math/big"
6+
)
7+
8+
type Charset int
9+
10+
const (
11+
Uppercase Charset = 1 << iota // 1 << 0 which is 00000001
12+
Lowercase // 1 << 1 which is 00000010
13+
Numbers // 1 << 2 which is 00000100
14+
Symbols // 1 << 3 which is 00001000
15+
)
16+
17+
var (
18+
uppercase = []byte(`ABCDEFGHIJKLMNOPQRSTUVWXYZ`)
19+
len_uppercase = len(uppercase)
20+
lowercase = []byte(`abcdefghijklmnopqrstuvwxyz`)
21+
len_lowercase = len(lowercase)
22+
numbers = []byte(`0123456789`)
23+
len_numbers = len(numbers)
24+
symbols = []byte(`!"#$%&'()*+,-./:;<=>?@^[\]^_{|}~` + "`")
25+
len_symbols = len(symbols)
26+
)
27+
28+
func Generate(n int) string {
29+
return GenerateForCharset(n, Uppercase|Lowercase|Numbers|Symbols)
30+
}
31+
32+
func GenerateForCharset(n int, chset Charset) string {
33+
buf := make([]byte, n)
34+
35+
count := 0
36+
if chset&Uppercase != 0 {
37+
count += len_uppercase
38+
}
39+
if chset&Lowercase != 0 {
40+
count += len_lowercase
41+
}
42+
if chset&Numbers != 0 {
43+
count += len_numbers
44+
}
45+
if chset&Symbols != 0 {
46+
count += len_symbols
47+
}
48+
max := big.NewInt(int64(count))
49+
50+
for i := 0; i < n; i++ {
51+
r, err := rand.Int(rand.Reader, max)
52+
if err != nil {
53+
panic(err)
54+
}
55+
idx := int(r.Int64())
56+
57+
if chset&Uppercase != 0 {
58+
if idx < len_uppercase {
59+
buf[i] = uppercase[idx]
60+
continue
61+
} else {
62+
idx -= len_uppercase
63+
}
64+
}
65+
if chset&Lowercase != 0 {
66+
if idx < len_lowercase {
67+
buf[i] = lowercase[idx]
68+
continue
69+
} else {
70+
idx -= len_lowercase
71+
}
72+
}
73+
if chset&Numbers != 0 {
74+
if idx < len_numbers {
75+
buf[i] = numbers[idx]
76+
continue
77+
} else {
78+
idx -= len_numbers
79+
}
80+
}
81+
if chset&Symbols != 0 {
82+
if idx < len_symbols {
83+
buf[i] = symbols[idx]
84+
continue
85+
} else {
86+
idx -= len_symbols
87+
}
88+
}
89+
}
90+
return string(buf)
91+
}

password_test.go

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package password
2+
3+
import "testing"
4+
5+
func TestGenerateForCharset2(t *testing.T) {
6+
type args struct {
7+
n int
8+
chset Charset
9+
}
10+
tests := []struct {
11+
name string
12+
args args
13+
}{
14+
{
15+
name: "Uppercase",
16+
args: args{
17+
n: 8,
18+
chset: Uppercase,
19+
},
20+
},
21+
{
22+
name: "Lowercase",
23+
args: args{
24+
n: 8,
25+
chset: Lowercase,
26+
},
27+
},
28+
{
29+
name: "Numbers",
30+
args: args{
31+
n: 8,
32+
chset: Numbers,
33+
},
34+
},
35+
{
36+
name: "Symbols",
37+
args: args{
38+
n: 8,
39+
chset: Symbols,
40+
},
41+
},
42+
{
43+
name: "Uppercase | Lowercase",
44+
args: args{
45+
n: 8,
46+
chset: Uppercase | Lowercase,
47+
},
48+
},
49+
{
50+
name: "Uppercase | Lowercase | Numbers",
51+
args: args{
52+
n: 8,
53+
chset: Uppercase | Lowercase | Numbers,
54+
},
55+
},
56+
{
57+
name: "Uppercase | Lowercase | Symbols",
58+
args: args{
59+
n: 8,
60+
chset: Uppercase | Lowercase | Symbols,
61+
},
62+
},
63+
}
64+
for _, tt := range tests {
65+
t.Run(tt.name, func(t *testing.T) {
66+
got := GenerateForCharset(tt.args.n, tt.args.chset)
67+
if len(got) != tt.args.n {
68+
t.Errorf("GenerateForCharset(%v, %v) returned password with length %v", tt.args.n, tt.args.chset, len(got))
69+
} else if !uses_charset(got, tt.args.chset) {
70+
t.Errorf("GenerateForCharset(%v, %v) uses unexpected charset", tt.args.n, tt.args.chset)
71+
}
72+
})
73+
}
74+
}
75+
76+
func uses_charset(str string, chset Charset) bool {
77+
data := []byte(str)
78+
for i := 0; i < len(data); i++ {
79+
if chset&Uppercase != 0 {
80+
if contains(uppercase, data[i]) {
81+
continue
82+
}
83+
}
84+
if chset&Lowercase != 0 {
85+
if contains(lowercase, data[i]) {
86+
continue
87+
}
88+
}
89+
if chset&Numbers != 0 {
90+
if contains(numbers, data[i]) {
91+
continue
92+
}
93+
}
94+
if chset&Symbols != 0 {
95+
if contains(symbols, data[i]) {
96+
continue
97+
}
98+
}
99+
return false
100+
}
101+
return true
102+
}
103+
104+
func contains(a []byte, ch byte) bool {
105+
for i := 0; i < len(a); i++ {
106+
if a[i] == ch {
107+
return true
108+
}
109+
}
110+
return false
111+
}

0 commit comments

Comments
 (0)