A decade ago I designed an encryption algorithm just for fun and to learn more about cryptography. During development I wrote a little helper tool which I could use to verify some quality metrics of my algorithm: analyze.c.
This module is a re-implementation of this code with go as a reusable module.
- standalone module without external dependencies
- uses 5 different metrics to measure password quality
- you can configure which metric to use
- you can also configure the quality thresholds
- there's support for dictionary lookup, but you need to provide the dictionary yourself
- it's reasonably fast
- the code is small enough to just copy it into your code
A good password is easy to remember and hard to guess. Don't be fooled by those "use special characters" evangelists: diceware passwords as outlined in the well known xkcd comic are by far the best ones.
However, if it's your job to implement a registration user interface, then sooner or later you'll need to validate passwords.
This module can be used for this job.
By default it checks 3 metrics:
Entropy in this case measures the cryptographic strength of the password. In non-technical words: it checks how scrambled the password looks or how many different bits it uses.
We only look for printable US-ASCII characters.
Of course just measuring entropy is insufficient. For
instance a password 12345678
consists of 8 different
characters and might pass the entropy check. However, as
can be easily seen, the characters are sorted and
therefore this password would be a terrible one.
Thus, character diffusion measures how characters are distributed.
Keep in mind that these two metrics would flag
the Tr0ub4dor&3
password of the comic as pretty good,
while in reality it's not! You might remedy
this problem with a longer mandatory password
length. But the harsh reality is that people still
use such passwords.
We go one step further and also measure how much the password can be compressed. For instance, let's look at this run length encoding example:
The string aaabggthhhh
can be rle encoded to
2ab2gt4h
. The result is shorter than the original, it is compressed.
The ideal password cannot be compressed
or not much.
Of course we do not use RLE. We measure compression using the Flate algorithm.
You can supply a dictionary of words of your liking and check if the password under test matches one of the words. Submatches can also be done.
You can also enable or disable certain metrics and you can tune the quality thresholds as needed.
- checksum test using supplied checksum list, e.g. of leaked passwords
- fuzzy testing against dictionary to catch variations, using Levenshtein or something similar.
Usage is pretty simple:
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:
type Options struct {
Compress int // minimum compression rate in percent, default 10%
CharDistribution float64 // minimum character distribution in percent, default 10%
Entropy float64 // minimum entropy value in bits/char, default 3 bits/s
Dictionary *Dictionary // lookup given dictionary, the caller has to provide it
}
To turn off a test, just set the tunable to zero.
Please take a look at the example or at the unit tests.
Benchmark results of version 0.0.1:
% 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
This module is licensed under the BSD license.
go-password provides similar functionality and it's stable and battle tested. However ir only measures the character entropy.