diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..a9d06f8 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,47 @@ +name: build-and-test +on: [push, pull_request] +jobs: + build: + strategy: + matrix: + version: ['1.22'] + os: [ubuntu-latest, windows-latest, macos-latest] + name: Build + runs-on: ${{ matrix.os }} + steps: + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: '${{ matrix.version }}' + id: go + + - name: checkout + uses: actions/checkout@v3 + + - name: build + run: go build + + - name: test + run: make test + + - name: Update coverage report + uses: ncruces/go-coverage-report@main + with: + report: true + chart: true + amend: true + if: | + matrix.os == 'ubuntu-latest' && + github.event_name == 'push' + continue-on-error: true + + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v3 + with: + go-version: 1.22 + - uses: actions/checkout@v3 + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 diff --git a/README.md b/README.md index 9e9e3ec..62dbd9d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +[![Go Report Card](https://goreportcard.com/badge/github.com/tlinden/valpass)](https://goreportcard.com/report/github.com/tlinden/valpass) +[![Actions](https://github.com/tlinden/valpass/actions/workflows/ci.yaml/badge.svg)](https://github.com/tlinden/valpass/actions) +[![Go Coverage](https://github.com/tlinden/valpass/wiki/coverage.svg)](https://raw.githack.com/wiki/tlinden/valpass/coverage.html) +![GitHub License](https://img.shields.io/github/license/tlinden/valpass) +[![GoDoc](https://godoc.org/github.com/tlinden/valpass?status.svg)](https://godoc.org/github.com/tlinden/valpass) + # valpass - a small golang module to verify passwords ## Background @@ -107,9 +113,85 @@ you can tune the quality thresholds as needed. ## Usage -Since the module is not yet complete and undocumented, -please look at [the example](https://github.com/TLINDEN/valpass/blob/main/example/test.go) -how to use it. +Usage is pretty simple: + +```go +import "github.com/tlinden/valpass" + +[..] + res, err := valpass.Validate("password"); if err != nil { + log.Fatal(err) + } + + if !res.Ok { + log.Fatal("Password is unsecure!") + } +[..] +``` + +You may also tune which tests you want to execute and with wich +parameters. To do this, just supply a second argument, which must be a +`valpas.Options` struct: + +```go +type Options struct { + Compress int // minimum compression rate in percent + CharDistribution float64 // minimum char distribution in percent + Entropy float64 // minimum entropy value in bits/char + Dictionary *Dictionary // if set, lookup given dictionary, the caller provides it + UTF8 bool // if true work on unicode utf-8 space, not just bytes +} +``` + +To turn off a test, just set the tunable to zero. + +Please take a look at [the +example](https://github.com/TLINDEN/valpass/blob/main/example/test.go) +or at [the unit tests](https://github.com/TLINDEN/valpass/blob/main/lib_test.go). + +## Performance + +Benchmark results of version 0.0.1: + +```default +% go test -bench=. -count 5 +goos: linux +goarch: amd64 +pkg: github.com/tlinden/valpass +cpu: Intel(R) Core(TM) i7-10610U CPU @ 1.80GHz +BenchmarkValidateEntropy-8 98703 12402 ns/op +BenchmarkValidateEntropy-8 92745 12258 ns/op +BenchmarkValidateEntropy-8 94020 12495 ns/op +BenchmarkValidateEntropy-8 96747 12349 ns/op +BenchmarkValidateEntropy-8 94790 12368 ns/op +BenchmarkValidateCharDist-8 95610 12184 ns/op +BenchmarkValidateCharDist-8 96631 12305 ns/op +BenchmarkValidateCharDist-8 97537 12215 ns/op +BenchmarkValidateCharDist-8 97544 13703 ns/op +BenchmarkValidateCharDist-8 95139 15392 ns/op +BenchmarkValidateCompress-8 2140 636274 ns/op +BenchmarkValidateCompress-8 5883 204162 ns/op +BenchmarkValidateCompress-8 5341 229536 ns/op +BenchmarkValidateCompress-8 4590 221610 ns/op +BenchmarkValidateCompress-8 5889 186709 ns/op +BenchmarkValidateDict-8 81 13730450 ns/op +BenchmarkValidateDict-8 78 16081013 ns/op +BenchmarkValidateDict-8 74 17545981 ns/op +BenchmarkValidateDict-8 92 12830625 ns/op +BenchmarkValidateDict-8 94 12564205 ns/op +BenchmarkValidateAll-8 5084 200770 ns/op +BenchmarkValidateAll-8 6054 193329 ns/op +BenchmarkValidateAll-8 5998 186064 ns/op +BenchmarkValidateAll-8 5996 191017 ns/op +BenchmarkValidateAll-8 6268 173846 ns/op +BenchmarkValidateAllwDict-8 374 3054042 ns/op +BenchmarkValidateAllwDict-8 390 3109049 ns/op +BenchmarkValidateAllwDict-8 404 3022698 ns/op +BenchmarkValidateAllwDict-8 393 3075163 ns/op +BenchmarkValidateAllwDict-8 381 3112361 ns/op +PASS +ok github.com/tlinden/valpass 54.017s +``` ## License diff --git a/lib_test.go b/lib_test.go index 15e92a7..4ff60d0 100644 --- a/lib_test.go +++ b/lib_test.go @@ -2,7 +2,10 @@ package valpass_test import ( "bufio" + "fmt" "os" + "os/exec" + "strings" "testing" "github.com/tlinden/valpass" @@ -250,6 +253,56 @@ func CheckPassword(t *testing.T, password string, } } +func BenchmarkValidateEntropy(b *testing.B) { + passwords := GetPasswords(b.N) + + for i := 0; i < b.N; i++ { + valpass.Validate(passwords[i], valpass.Options{Entropy: 10}) + } +} + +func BenchmarkValidateCharDist(b *testing.B) { + passwords := GetPasswords(b.N) + + for i := 0; i < b.N; i++ { + valpass.Validate(passwords[i], valpass.Options{CharDistribution: 10}) + } +} + +func BenchmarkValidateCompress(b *testing.B) { + passwords := GetPasswords(b.N) + + for i := 0; i < b.N; i++ { + valpass.Validate(passwords[i], valpass.Options{Compress: 10}) + } +} + +func BenchmarkValidateDict(b *testing.B) { + passwords := GetPasswords(b.N) + + for i := 0; i < b.N; i++ { + valpass.Validate(passwords[i], + valpass.Options{Dictionary: &valpass.Dictionary{Words: ReadDict("t/american-english")}}, + ) + } +} + +func BenchmarkValidateAll(b *testing.B) { + passwords := GetPasswords(b.N) + + for i := 0; i < b.N; i++ { + valpass.Validate(passwords[i]) + } +} + +func BenchmarkValidateAllwDict(b *testing.B) { + passwords := GetPasswords(b.N) + + for i := 0; i < b.N; i++ { + valpass.Validate(passwords[i], opts_dict) + } +} + func ReadDict(path string) []string { file, err := os.Open(path) if err != nil { @@ -266,3 +319,15 @@ func ReadDict(path string) []string { return lines } + +func GetPasswords(count int) []string { + + cmd := exec.Command("pwgen", "-1", "-s", "-y", "32", fmt.Sprintf("%d", count+1)) + + out, err := cmd.Output() + if err != nil { + panic(cmd.Err) + } + + return strings.Split(string(out), "\n") +}