Skip to content

Commit 5c16201

Browse files
author
Aizen
committed
- added code documentation
- improved entropy of distribution - added a few basic tests
1 parent 377eed8 commit 5c16201

File tree

6 files changed

+19410
-27056
lines changed

6 files changed

+19410
-27056
lines changed

cmd/genpw/cli.go

+11-6
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ type Passphrase struct {
1717

1818
func main() {
1919
cert := flag.Bool("c", false, "run entropy certification algorithm (for developers)")
20-
words := flag.Uint64("w", 4, "number of words per password")
21-
passwords := flag.Uint64("p", 4, "number of passwords to generate")
20+
depth := flag.Uint64("cd", 1, "certification depth, larger number -> more accurate results (for developers)")
21+
words := flag.Uint64("w", 3, "number of words per password")
22+
passwords := flag.Uint64("p", 6, "number of passwords to generate")
2223
flag.Parse()
2324
if *cert {
24-
certify()
25+
certify(*depth)
2526
}
2627

2728
pws := []Passphrase{}
@@ -45,17 +46,19 @@ func main() {
4546
}
4647
}
4748

48-
func certify() {
49+
func certify(udepth uint64) {
4950
cnt := make(map[string]int)
5051
iN := 0
5152
Q := 128
5253
nominal_H := 0.0
54+
nominal_H2 := 0.0
5355
cnt_nom_H := 0.0
5456
for {
5557
Q += Q / 14
5658
for range Q {
5759
w, nh := cp.GenMixWord()
5860
nominal_H += nh
61+
nominal_H2 += nh * nh
5962
cnt_nom_H++
6063
cnt[w]++
6164
iN++
@@ -76,9 +79,11 @@ func certify() {
7679
fmt.Print("|")
7780
}
7881
nomH := nominal_H / cnt_nom_H
82+
nomH2 := nominal_H2 / cnt_nom_H
83+
stddev := math.Sqrt(max(nomH2-nomH*nomH, 0.0001))
7984
gap := nomH - H
80-
fmt.Printf("%v | E[H]-E=%.2f E[H] = %.2f ", strings.Repeat(" ", 60-CH), gap, nomH)
81-
if gap < 0.01 {
85+
fmt.Printf("%v | E[H]-E=%.2f E[H] = %.2f ∂E[H] = %.2f ", strings.Repeat(" ", 60-CH), gap, nomH, stddev)
86+
if gap < stddev/(1+float64(udepth)) {
8287
fmt.Print("\n")
8388
fmt.Println(H)
8489
break

cryptipass.go

+33-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// Package cryptipass v1.3.0 provides functionality for generating secure passphrases
2+
// composed of random words, with a focus on cryptographic security and entropy calculation.
13
package cryptipass
24

35
import (
@@ -9,17 +11,30 @@ import (
911

1012
var rng *rand.Rand
1113

14+
// init initializes the cryptographically secure random number generator (RNG).
15+
// It reads 32 bytes of entropy from crypto/rand and uses them to seed a ChaCha8-based RNG.
16+
// If the required number of bytes is not read, it logs a fatal error.
1217
func init() {
1318
seed := [32]byte{}
1419
n, err := cr.Reader.Read(seed[:])
1520
if err != nil || n != 32 {
1621
log.Fatal(n, err, seed)
1722
}
1823
rng = rand.New(rand.NewChaCha8(seed))
19-
// Faster but non-cryptographic
24+
// Faster but non-cryptographic alternative:
2025
// rng = rand.New(rand.NewPCG(rng.Uint64(), rng.Uint64()))
2126
}
2227

28+
// NewPassphrase generates a passphrase consisting of the specified number of random words.
29+
// Each word is chosen by the GenMixWord function, and the total entropy of the passphrase
30+
// is calculated and returned along with the passphrase.
31+
//
32+
// - Parameters:
33+
// words (uint64): The number of words to include in the passphrase.
34+
//
35+
// - Return values:
36+
// string: The generated passphrase, with words joined by periods.
37+
// float64: The total entropy (in bits) of the passphrase, indicating its strength.
2338
func NewPassphrase(words uint64) (string, float64) {
2439
wordvec := []string{}
2540
total_entropy := 0.0
@@ -31,12 +46,29 @@ func NewPassphrase(words uint64) (string, float64) {
3146
return strings.Join(wordvec, "."), total_entropy
3247
}
3348

49+
// GenMixWord generates a single word of random length and returns it along with its entropy.
50+
// The word length is determined by the PickLength function, and the word itself is generated
51+
// by the GenWord function.
52+
//
53+
// - Return values:
54+
// string: The generated word of random length.
55+
// float64: The entropy contributed by both the word length and the word itself.
3456
func GenMixWord() (string, float64) {
3557
l, entropy_l := PickLength()
3658
w, entropy_w := GenWord(l)
3759
return w, entropy_l + entropy_w
3860
}
3961

62+
// GenWord generates a word of exactly n characters and returns it along with the entropy
63+
// associated with the process. The characters are selected by calling PickNext iteratively
64+
// until the word reaches the desired length.
65+
//
66+
// - Parameters:
67+
// n (int): The number of characters to include in the generated word.
68+
//
69+
// - Return values:
70+
// string: The generated word consisting of n characters.
71+
// float64: The total entropy contributed by the character selection process.
4072
func GenWord(n int) (string, float64) {
4173
s := ""
4274
total_entropy := 0.0

cryptipass_test.go

+72-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cryptipass_test
22

33
import (
4+
"strings"
45
"testing"
56

67
"github.com/francescoalemanno/cryptipass"
@@ -11,7 +12,77 @@ func TestBasic(t *testing.T) {
1112
if len(pw) < 15 {
1213
t.Fatalf(`Wrong length "%s"`, pw)
1314
}
14-
if H < 60 {
15+
if H < 40 {
16+
// this event is so unlikely (p = 9.86588*10^-10) it must not happen.
1517
t.Fatalf(`Wrong entropy "%s"`, pw)
1618
}
1719
}
20+
21+
// TestNewPassphrase tests the NewPassphrase function for generating a passphrase and validating its length.
22+
func TestNewPassphrase(t *testing.T) {
23+
words := uint64(5)
24+
passphrase, entropy := cryptipass.NewPassphrase(words)
25+
wordList := strings.Split(passphrase, ".")
26+
27+
if len(wordList) != int(words) {
28+
t.Errorf("Expected %d words, got %d", words, len(wordList))
29+
}
30+
31+
if entropy <= 0 {
32+
t.Errorf("Expected entropy to be greater than 0, got %f", entropy)
33+
}
34+
}
35+
36+
// TestGenMixWord tests that GenMixWord generates a word and returns a positive entropy value.
37+
func TestGenMixWord(t *testing.T) {
38+
word, entropy := cryptipass.GenMixWord()
39+
40+
if len(word) == 0 {
41+
t.Error("Expected a word, got an empty string")
42+
}
43+
44+
if entropy < 0 {
45+
t.Errorf("Expected entropy to be greater than 0, got %f", entropy)
46+
}
47+
}
48+
49+
// TestGenWord tests that GenWord generates a word of a specific length and returns a positive entropy value.
50+
func TestGenWord(t *testing.T) {
51+
length := 6
52+
word, entropy := cryptipass.GenWord(length)
53+
54+
if len(word) != length {
55+
t.Errorf("Expected word length %d, got %d", length, len(word))
56+
}
57+
58+
if entropy < 0 {
59+
t.Errorf("Expected entropy to be greater than 0, got %f", entropy)
60+
}
61+
}
62+
63+
// TestPickNext tests that PickNext generates a valid character appended to the seed and returns entropy.
64+
func TestPickNext(t *testing.T) {
65+
seed := "te"
66+
updatedSeed, entropy := cryptipass.PickNext(seed)
67+
68+
if len(updatedSeed) <= len(seed) {
69+
t.Errorf("Expected updated seed to be longer, got %s", updatedSeed)
70+
}
71+
72+
if entropy < 0 {
73+
t.Errorf("Expected entropy to be >= 0, got %f", entropy)
74+
}
75+
}
76+
77+
// TestPickLength tests that PickLength generates a valid word length and returns entropy.
78+
func TestPickLength(t *testing.T) {
79+
length, entropy := cryptipass.PickLength()
80+
81+
if length < 3 || length > 9 {
82+
t.Errorf("Expected length to be between 3 and 9, got %d", length)
83+
}
84+
85+
if entropy < 0 {
86+
t.Errorf("Expected entropy to be greater than 0, got %f", entropy)
87+
}
88+
}

0 commit comments

Comments
 (0)