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.
1
+ // Package cryptipass provides a passphrase generation and entropy calculation
2
+ // system using wordlists and patterns. It is designed to ensure randomness
3
+ // while offering flexibility in the structure of generated passwords.
3
4
package cryptipass
4
5
5
6
import (
6
7
cr "crypto/rand"
8
+ _ "embed"
7
9
"fmt"
8
10
"log"
9
11
"math"
10
12
"math/rand/v2"
11
13
"strings"
12
14
)
13
15
14
- type Generator struct {
15
- Rng * rand.Rand
16
+ // Transition represents the probability distribution of rune sequences within
17
+ // words. It stores the runes, their counts, the total occurrences, and the
18
+ // entropy of the sequence.
19
+ type Transition struct {
20
+ Runes []rune
21
+ Counts []int
22
+ Total int
23
+ Entropy float64
16
24
}
17
25
18
- // NewInstance initializes the cryptographically secure random number generator (RNG).
19
- // It reads 32 bytes of entropy from crypto/rand and uses them to seed a ChaCha8-based RNG.
20
- // If the required number of bytes is not read, it logs a fatal error.
21
- func NewInstance () * Generator {
26
+ func distill (tokens []string ) map [string ]Transition {
27
+ transition_matrix := make (map [string ]map [rune ]int )
28
+ put := func (str string , r rune ) {
29
+ if transition_matrix [str ] == nil {
30
+ transition_matrix [str ] = make (map [rune ]int )
31
+ }
32
+ transition_matrix [str ][r ]++
33
+ }
34
+
35
+ for _ , w := range tokens {
36
+ R := []rune (w )
37
+ put ("lengths" , rune (len (R )))
38
+ put ("" , R [0 ])
39
+ put (string (R [0 ]), R [1 ])
40
+ for i := 0 ; i < len (R )- 2 ; i ++ {
41
+ put (string (R [i ])+ string (R [i + 1 ]), R [i + 2 ])
42
+ }
43
+ }
44
+ dist_trans_matrix := make (map [string ]Transition )
45
+ for k , rfreq := range transition_matrix {
46
+ C := 0
47
+ for _ , freq := range rfreq {
48
+ C += freq
49
+ }
50
+ H := 0.0
51
+ tr := Transition {}
52
+ tr .Counts = make ([]int , 0 )
53
+
54
+ tr .Runes = make ([]rune , 0 )
55
+ cum := 0
56
+ for ru , freq := range rfreq {
57
+ p := float64 (freq ) / float64 (C )
58
+ H -= math .Log2 (p ) * p
59
+ cum += freq
60
+ tr .Counts = append (tr .Counts , cum )
61
+ tr .Runes = append (tr .Runes , ru )
62
+ }
63
+ tr .Total = C
64
+ tr .Entropy = H
65
+ dist_trans_matrix [k ] = tr
66
+ }
67
+ return dist_trans_matrix
68
+ }
69
+
70
+ type generator struct {
71
+ Rng * rand.Rand
72
+ JumpTable * map [string ]Transition
73
+ }
74
+
75
+ // NewInstanceFromList generates a new passphrase generator using a custom
76
+ // list of tokens (words). It uses the ChaCha8 random number generator seeded
77
+ // by cryptographically secure random bytes.
78
+ func NewInstanceFromList (tokens []string ) * generator {
22
79
seed := [32 ]byte {}
23
80
n , err := cr .Reader .Read (seed [:])
24
81
if err != nil || n != 32 {
25
82
log .Fatal (n , err , seed )
26
83
}
27
84
rng := rand .New (rand .NewChaCha8 (seed ))
28
- g := new (Generator )
85
+ g := new (generator )
29
86
g .Rng = rng
87
+ jtbl := distill (tokens )
88
+ g .JumpTable = & jtbl
89
+
30
90
return g
31
91
}
32
92
33
- // NewPassphrase generates a passphrase consisting of the specified number of random words.
34
- // Each word is chosen by the GenMixWord function, and the total entropy of the passphrase
35
- // is calculated and returned along with the passphrase.
36
- //
37
- // - Parameters:
38
- //
39
- // words (uint64): The number of words to include in the passphrase.
40
- //
41
- // - Return values:
42
- //
43
- // string: The generated passphrase, with words joined by periods.
44
- //
45
- // float64: The total entropy (in bits) of the passphrase, indicating its strength.
46
- func (g * Generator ) GenPassphrase (words uint64 ) (string , float64 ) {
93
+ // NewInstance returns a new passphrase generator initialized with a default
94
+ // wordlist. It uses the ChaCha8 random number generator for randomization.
95
+ func NewInstance () * generator {
96
+ return NewInstanceFromList (eff_long_word_list )
97
+ }
98
+
99
+ // GenPassphrase generates a passphrase consisting of the specified number of
100
+ // words. It returns the passphrase and its total entropy value.
101
+ func (g * generator ) GenPassphrase (words uint64 ) (string , float64 ) {
47
102
wordvec := []string {}
48
103
total_entropy := 0.0
49
104
for range words {
@@ -54,31 +109,13 @@ func (g *Generator) GenPassphrase(words uint64) (string, float64) {
54
109
return strings .Join (wordvec , "." ), total_entropy
55
110
}
56
111
57
- // GenFromPattern generates a passphrase based on a specified pattern.
58
- // The pattern dictates the structure of the generated passphrase, with the following
59
- // format options:
60
- //
61
- // - `w`: Lowercase word generated via GenMixWord.
62
- //
63
- // - `W`: Capitalized word generated via GenMixWord (first letter uppercased).
64
- //
65
- // - `d`: Random digit (0-9).
66
- //
67
- // - `s`: Random symbol from a predefined set (@#!$%&=?^+-*").
68
- //
69
- // Any other characters in the pattern are appended as-is.
70
- //
71
- // - Parameters:
72
- //
73
- // pattern (string): A string pattern that specifies the structure of the passphrase.
74
- //
75
- // - Return values:
76
- //
77
- // string: The generated passphrase based on the given pattern.
78
- //
79
- // float64: The total entropy (in bits) of the generated passphrase.
80
-
81
- func (g * Generator ) GenFromPattern (pattern string ) (string , float64 ) {
112
+ // GenFromPattern generates a passphrase following the specified pattern.
113
+ // Supported pattern symbols are:
114
+ // - 'w', 'W': words (lowercase or capitalized)
115
+ // - 'd': digits
116
+ // - 's': symbols
117
+ // - 'c', 'C': characters (lowercase or uppercase)
118
+ func (g * generator ) GenFromPattern (pattern string ) (string , float64 ) {
82
119
passphrase := ""
83
120
entropy := 0.0
84
121
pushnext := false
@@ -122,7 +159,9 @@ func (g *Generator) GenFromPattern(pattern string) (string, float64) {
122
159
return passphrase , entropy
123
160
}
124
161
125
- func (g * Generator ) GenWord (c rune ) (string , float64 ) {
162
+ // GenWord generates a word based on the character pattern (e.g., 'w', 'W').
163
+ // It returns the generated word and its entropy.
164
+ func (g * generator ) GenWord (c rune ) (string , float64 ) {
126
165
head , h_head := g .PickNext ("" )
127
166
leng , h_leng := g .PickLength ()
128
167
if c == 'W' {
@@ -143,12 +182,14 @@ type CertifyResult struct {
143
182
StdDev float64
144
183
}
145
184
185
+ // Certify verifies the randomness and entropy of the generated passphrases.
186
+ // It runs multiple iterations to certify that the empirical entropy matches the nominal entropy.
146
187
func Certify (Gen func () (string , float64 )) CertifyResult {
147
188
nominal_H := 0.0
148
189
nominal_H2 := 0.0
149
190
cnt_nom_H := 0.0
150
191
for range 1000 {
151
- //pilot run to estimate budjet
192
+
152
193
_ , nh := Gen ()
153
194
nominal_H += nh
154
195
nominal_H2 += nh * nh
@@ -185,3 +226,39 @@ func Certify(Gen func() (string, float64)) CertifyResult {
185
226
}
186
227
187
228
}
229
+
230
+ // PickNext selects the next rune in the sequence based on the previous seed.
231
+ // It uses the stored transition matrix to ensure a smooth distribution of
232
+ // rune occurrences, retrying until a valid match is found.
233
+ func (g * generator ) PickNext (seed string ) (string , float64 ) {
234
+ L := min (len (seed ), 2 )
235
+ tok := strings .ToLower (seed [len (seed )- L :])
236
+ retry:
237
+ if tr , ok := (* g .JumpTable )[tok ]; ok {
238
+ N := g .Rng .IntN (tr .Total )
239
+ for i , v := range tr .Counts {
240
+ if N < v {
241
+ return string (tr .Runes [i ]), tr .Entropy
242
+ }
243
+ }
244
+ panic ("unexpected" )
245
+ }
246
+ tok = tok [1 :]
247
+ goto retry
248
+ }
249
+
250
+ // PickLength selects the length of the next generated word based on the
251
+ // transition matrix of word lengths. It ensures the generated word has an
252
+ // appropriate length and entropy.
253
+ func (g * generator ) PickLength () (int , float64 ) {
254
+ tr , ok := (* g .JumpTable )["lengths" ]
255
+ if ok {
256
+ N := g .Rng .IntN (tr .Total )
257
+ for i , v := range tr .Counts {
258
+ if N < v {
259
+ return int (tr .Runes [i ]), tr .Entropy
260
+ }
261
+ }
262
+ }
263
+ panic ("unexpected rand num" )
264
+ }
0 commit comments