├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── Makefile ├── README.md ├── go.mod ├── main.go └── main_test.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | build: 8 | name: Build 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@master 13 | - name: Setup Go 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: 1.x 17 | - name: Build 18 | run: go build -v . 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@master 15 | - name: Setup Go 16 | uses: actions/setup-go@v2 17 | with: 18 | go-version: 1.x 19 | - name: Cross build 20 | run: make cross 21 | - name: Create Release 22 | id: create_release 23 | uses: actions/create-release@master 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | with: 27 | tag_name: ${{ github.ref }} 28 | release_name: Release ${{ github.ref }} 29 | - name: Upload 30 | run: make upload 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN := genpw 2 | VERSION := $$(make -s show-version) 3 | CURRENT_REVISION := $(shell git rev-parse --short HEAD) 4 | BUILD_LDFLAGS := "-s -w -X main.revision=$(CURRENT_REVISION)" 5 | GOBIN ?= $(shell go env GOPATH)/bin 6 | export GO111MODULE=on 7 | 8 | .PHONY: all 9 | all: clean build 10 | 11 | .PHONY: build 12 | build: 13 | go build -ldflags=$(BUILD_LDFLAGS) -o $(BIN) . 14 | 15 | .PHONY: install 16 | install: 17 | go install -ldflags=$(BUILD_LDFLAGS) . 18 | 19 | .PHONY: show-version 20 | show-version: $(GOBIN)/gobump 21 | gobump show -r . 22 | 23 | $(GOBIN)/gobump: 24 | go install github.com/x-motemen/gobump/cmd/gobump@latest 25 | 26 | .PHONY: cross 27 | cross: $(GOBIN)/goxz 28 | goxz -n $(BIN) -pv=v$(VERSION) -build-ldflags=$(BUILD_LDFLAGS) . 29 | 30 | $(GOBIN)/goxz: 31 | go install github.com/Songmu/goxz/cmd/goxz@latest 32 | 33 | .PHONY: test 34 | test: build 35 | go test -v ./... 36 | 37 | .PHONY: clean 38 | clean: 39 | rm -rf $(BIN) goxz 40 | go clean 41 | 42 | .PHONY: bump 43 | bump: $(GOBIN)/gobump 44 | ifneq ($(shell git status --porcelain),) 45 | $(error git workspace is dirty) 46 | endif 47 | ifneq ($(shell git rev-parse --abbrev-ref HEAD),master) 48 | $(error current branch is not master) 49 | endif 50 | @gobump up -w . 51 | git commit -am "bump up version to $(VERSION)" 52 | git tag "v$(VERSION)" 53 | git push origin master 54 | git push origin "refs/tags/v$(VERSION)" 55 | 56 | .PHONY: upload 57 | upload: $(GOBIN)/ghr 58 | ghr "v$(VERSION)" goxz 59 | 60 | $(GOBIN)/ghr: 61 | go install github.com/tcnksm/ghr@latest 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # genpw 2 | 3 | Password Generator 4 | 5 | ## Usage 6 | 7 | ``` 8 | Usage of genpw: 9 | -c int 10 | count of output (default 64) 11 | -n int 12 | number of characters (default 16) 13 | -nc int 14 | minimum count of numbers (default: any) (default -1) 15 | -sc int 16 | minimum count of symbols (default: any) (default -1) 17 | ``` 18 | 19 | ``` 20 | $ genpw 21 | BqU{#SQ'Yl'rV0?! Un[$@B[R,7Br?[O0 mj:&09k'U^aV0j%` K_QBEPE;xYu.(*1X 22 | T"rVWovu[:3`Lbs? HWVMy?Ge)#v2.O;B 2W!Y,V^!|M;h&9(i 'AKKQGS'9)jNX}J] 23 | FJ[*)G}q$?NFtoxV )7wT?34*'OfVf1{= :.NR/!n2X1o_Vmlm PlM"@!8ab[/AB"l6 24 | {pa<\10.#>s3gHG5 F85EPoIq]1~%iw4L ?H;oUu}I:;='1qU~ i[DU_^n?q~yo3Pi0 25 | y,al,,w(//;`mwwX JW0Kj[:_^3 $-68FJ%o:ti&Dr?K 26 | 4l>nW|iKcT(iNdDD 6l93\`&'p:?w"8cf B.;hUdDi@3MYJvs@ ]WL5[mu;-qS<$>4! 27 | !kl)gmL)~~f>)C_6 #%X1V^WOdfm/BPY& ^RAhYJQitR0eTri5 JJT|21uI":V'q>2= 28 | e}|$VeVkTF.=rjVV t-mS.g#2`2?&|w0X :{vuG.EWV\'"p~X? }y-{*F>'l7*.;Mm-|, 3xlEBI,~BW"}J./N ,nwMb<\5-Wl}B@YF 30 | 2>$?HE&lc!$#BtVX QYPFLTv/tWBtR(3m ^%~[nVXk@ikm9|4s Odx(t'xae4L-W2 sDj.!c852GGG-WA$ 32 | `F*.E,AD:v'|wW<9 |:(Y~6dFhUL':K JNY>MGw)$D#0>BM' 33 | w*i~L}@Q1Inw9:N[ _n/&;'!f$S1E8Y0O bJ~G1X"E"s8b@OhX ![v8"&RFVLqlv'gr 34 | BeP)Y_,a^bfKS49R ek9m]K3gAf*24(:P 8"pLnDCg6pyoYu|q GWi9%U*nX[lKV)~1 35 | .)4>{_uf]<~=K&j( [76FD7d^},|PI]\2 ]rtO)8&}E9!M.d>T 9:4nc3w0xFCJ^aT: 36 | YJ[/;X5~Xo#0&k{; X?$E?w|>'\k4%=@= uKT&\y*0ThM^)#x$ m?;U!T|^[6)L,]eg 37 | 38 | $ genpw -c 8 -sc 0 39 | 1eJXeEV74xlTe69m MdnnoBVOvS5LHA0N YRQ7oWTbOiS6dwPc 70yURuk9wviFnxkH 40 | wR4OG2Gop8uWY05j O9lcMih6EB1mQtRp SyhLo20BKLOcWeNC HFgubFL6QyxBRnFO 41 | 42 | $ genpw -c 8 -nc 0 43 | ``$^ 44 | \#uA@g'Cs~]$G!cY usFVQ}'Byi#na?_@[;:],./'") 24 | ) 25 | 26 | func count(bs []byte, bp []byte) int { 27 | n := 0 28 | for _, b := range bs { 29 | if bytes.Index(bp, []byte{b}) != -1 { 30 | n++ 31 | } 32 | } 33 | return n 34 | } 35 | 36 | func run(w io.Writer, o, n, nc, sc int) error { 37 | if o <= 0 || n < 1 { 38 | return flag.ErrHelp 39 | } 40 | nnc := 0 41 | if nc > 0 { 42 | nnc = nc 43 | } 44 | nsc := 0 45 | if sc > 0 { 46 | nsc = sc 47 | } 48 | if n < nnc+nsc { 49 | return errors.New("total length of numbers+symbols is greeter than maximum characters") 50 | } 51 | 52 | var buf bytes.Buffer 53 | if n > nnc+nsc { 54 | for r := 'a'; r <= 'z'; r++ { 55 | buf.WriteRune(r) 56 | } 57 | for r := 'A'; r <= 'Z'; r++ { 58 | buf.WriteRune(r) 59 | } 60 | } 61 | if nc != 0 && nsc != n { 62 | buf.Write(numbers) 63 | } 64 | if sc != 0 && nnc != n { 65 | buf.Write(symbols) 66 | } 67 | chars := buf.Bytes() 68 | 69 | no := 0 70 | nw := 0 71 | for { 72 | var pw bytes.Buffer 73 | for { 74 | pw.Reset() 75 | for i := 0; i < n; i++ { 76 | r, err := rand.Int(rand.Reader, big.NewInt(int64(len(chars)))) 77 | if err != nil { 78 | panic(err) 79 | } 80 | pw.WriteByte(chars[int(r.Int64())]) 81 | } 82 | 83 | if nc > 0 && count(pw.Bytes(), numbers) < nc { 84 | continue 85 | } 86 | if sc > 0 && count(pw.Bytes(), symbols) < sc { 87 | continue 88 | } 89 | break 90 | } 91 | fmt.Fprint(w, pw.String()) 92 | 93 | nw += n + 1 94 | if nw < 80-n { 95 | fmt.Fprint(w, " ") 96 | } else { 97 | fmt.Fprintln(w) 98 | nw = 0 99 | } 100 | no++ 101 | if no == o { 102 | break 103 | } 104 | } 105 | fmt.Fprintln(w) 106 | 107 | return nil 108 | } 109 | 110 | func main() { 111 | var c int 112 | var n int 113 | var nc int 114 | var sc int 115 | var showVersion bool 116 | flag.IntVar(&c, "c", 64, "count of output") 117 | flag.IntVar(&n, "n", 16, "number of characters") 118 | flag.IntVar(&nc, "nc", -1, "minimum count of numbers (default: any)") 119 | flag.IntVar(&sc, "sc", -1, "minimum count of symbols (default: any)") 120 | flag.BoolVar(&showVersion, "v", false, "show version") 121 | flag.Parse() 122 | 123 | if showVersion { 124 | fmt.Printf("%s %s (rev: %s/%s)\n", name, version, revision, runtime.Version()) 125 | return 126 | } 127 | 128 | err := run(os.Stdout, c, n, nc, sc) 129 | if err != nil { 130 | if err == flag.ErrHelp { 131 | flag.Usage() 132 | } else { 133 | fmt.Fprintln(os.Stderr, err) 134 | } 135 | os.Exit(2) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "testing" 7 | ) 8 | 9 | func TestFlags(t *testing.T) { 10 | var buf bytes.Buffer 11 | err := run(&buf, -1, 0, 0, 0) 12 | if err != flag.ErrHelp { 13 | t.Fatal(err) 14 | } 15 | 16 | err = run(&buf, -1, -1, 0, 0) 17 | if err != flag.ErrHelp { 18 | t.Fatal(err) 19 | } 20 | 21 | err = run(&buf, 1, 1, 0, 0) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | } 26 | 27 | func TestRun(t *testing.T) { 28 | var buf bytes.Buffer 29 | err := run(&buf, 1, 5, 0, 0) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | if buf.Len() != 7 { 34 | t.Fatalf("length should be 7 but %v", buf.Len()) 35 | } 36 | 37 | buf.Reset() 38 | err = run(&buf, 2, 5, 0, 0) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | if buf.Len() != 13 { 43 | t.Fatalf("length should be 13 but %v", buf.Len()) 44 | } 45 | 46 | buf.Reset() 47 | err = run(&buf, 5, 5, 0, 0) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | if buf.Len() != 31 { 52 | t.Fatalf("length should be 31 but %v", buf.Len()) 53 | } 54 | 55 | tests := []struct { 56 | c int 57 | n int 58 | nc int 59 | sc int 60 | }{ 61 | {1, 5, 1, 0}, 62 | {1, 5, 0, 1}, 63 | {1, 5, 1, 1}, 64 | {1, 5, 0, 0}, 65 | {1, 5, 3, 2}, 66 | } 67 | for _, test := range tests { 68 | buf.Reset() 69 | _ = run(&buf, test.c, test.n, test.nc, test.sc) 70 | for _, field := range bytes.Fields(buf.Bytes()) { 71 | got := count(field, numbers) 72 | if got < test.nc { 73 | t.Fatalf("numbers: want %v for %q but got %v", test.nc, string(field), got) 74 | } 75 | got = count(field, symbols) 76 | if got < test.sc { 77 | t.Fatalf("symbols: want %v for %q but got %v", test.sc, string(field), got) 78 | } 79 | } 80 | } 81 | } 82 | 83 | func TestCount(t *testing.T) { 84 | tests := []struct { 85 | pattern []byte 86 | input []byte 87 | want int 88 | }{ 89 | {[]byte("abcda"), []byte("ab"), 2}, 90 | {[]byte(""), []byte("ab"), 0}, 91 | {[]byte(""), []byte(""), 0}, 92 | {[]byte("abcde"), []byte("-"), 0}, 93 | {[]byte("a"), []byte("ab"), 1}, 94 | } 95 | 96 | for _, test := range tests { 97 | got := count(test.input, test.pattern) 98 | if got != test.want { 99 | t.Fatalf("want %v but got %v", test.want, got) 100 | } 101 | } 102 | } 103 | --------------------------------------------------------------------------------