Skip to content

Commit 65e387b

Browse files
committed
Merge branch 'release/0.1.0'
2 parents 4a45a0f + 3d9c534 commit 65e387b

5 files changed

Lines changed: 119 additions & 30 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## [0.1.0]
4+
5+
- Add timeout option (#16)
6+
7+
38
## [0.0.9]
49

510
- Add regex support (#13)
@@ -27,7 +32,7 @@
2732

2833
## [0.0.5]
2934

30-
- Allow for inclusion as a module (see #2)
35+
- Allow for inclusion as a module (#2)
3136

3237

3338
## [0.0.4]

README.md

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
[![Go Report Card](https://goreportcard.com/badge/github.com/axllent/wireguard-vanity-keygen)](https://goreportcard.com/report/github.com/axllent/wireguard-vanity-keygen)
44

5-
A command-line vanity (public) key generator for [WireGuard](https://www.wireguard.com/). It only matches the prefix of generated public keys, and not whether the search matches anywhere in the public key. The concept is based on [wireguard-vanity-address](https://github.com/warner/wireguard-vanity-address), however I wanted something a little more streamlined.
5+
A command-line vanity (public) key generator for [WireGuard](https://www.wireguard.com/). By default, it only matches the prefix of generated public keys,
6+
and not whether the search matches anywhere in the public key. The concept is based on
7+
[wireguard-vanity-address](https://github.com/warner/wireguard-vanity-address), however I wanted something a little more streamlined.
68

79

810
## Features
@@ -51,10 +53,17 @@ private gJtn0woDChGvyN2eSdc7mTpAFA/nA6jykJeK5bYYfFA= public Pc7+UEJSHiWsQ9zkO2
5153
private IMyPmYm/v0SPmB62hC8l6kfxT3/Lfp7dMioo+SM6T2c= public Pc7/uVfD/ZftxWBHwYbaudEywUS61biBcpj5Tw830Q4=
5254
```
5355

56+
57+
## Installing
58+
59+
Download the [latest binary release](https://github.com/axllent/wireguard-vanity-keygen/releases/latest) for your system,
60+
or build from source `go install github.com/axllent/wireguard-vanity-keygen@latest`.
61+
62+
5463
## Timings
5564

5665
To give you a rough idea of how long it will take to generate keys, the following table lists
57-
estimated timings for each match on a system that reported "`Calculating speed: 230,000 calculations per second using 19 CPU cores`" when it started:
66+
estimated timings to find a matching key on a system that reported "`Calculating speed: 230,000 calculations per second using 19 CPU cores`" when it started:
5867

5968
| Length | Case-insensitive | Case-sensitive |
6069
| :------ | :--------------- | :------------- |
@@ -66,14 +75,16 @@ estimated timings for each match on a system that reported "`Calculating speed:
6675
| 8 chars | 7 months | 38 years |
6776
| 9 chars | 22 years | 175 years |
6877

69-
Note that the above timings are for finding a result for any search term.
70-
Passing multiple search terms will not substantially increase the time,
71-
but increasing the limit to two (`--limit 2`) will double the estimated time, three will triple the time, etc.
78+
Note that the above timings are for finding a matching key for a single search term.
79+
Passing multiple search terms will not substantially increase the time to find any single term, but the time to find
80+
all search terms is the sum of all the estimated times.
81+
Also, increasing the limit to two (`--limit 2`) will double the estimated time, three will triple the time, etc.
7282

7383
If any search term contains numbers, the timings would fall somewhere between the case-insensitive and case-sensitive columns.
7484

7585
Of course, your mileage will differ, depending on the number, and speed, of your CPU cores.
7686

87+
7788
## Regular Expressions
7889

7990
Since each additional letter in a search term increases the search time exponentially, searching using a regular expression may
@@ -87,18 +98,14 @@ reduce the time considerably. Here are some examples:
8798

8899
A good guide on Go's regular expression syntax is at https://pkg.go.dev/regexp/syntax.
89100

90-
To include a `+` in your regular expression, preface it with a backslash, like `\+`.
101+
To include a literal `+` in your regular expression, preface it with a backslash: `^ex\+`.
91102

92103
NOTE: If your search term contains shell metacharacters, such as `|`, or `^`, you will need to quote it.
93104
On Windows, you must use double quotes. For example: `"^(a|b)"`.
94105

95106
NOTE: Complex regular expressions, such as those using escape sequences, flags, or character classes, may never match a key.
96-
To avoid that, consider testing your regex using a tool such as [this one](https://go.dev/play/p/6LJy51Wd08O).
97-
98-
## Installing
99-
100-
Download the [latest binary release](https://github.com/axllent/wireguard-vanity-keygen/releases/latest) for your system,
101-
or build from source `go install github.com/axllent/wireguard-vanity-keygen@latest`.
107+
To avoid that, consider testing your regex using a tool such as [this one](https://go.dev/play/p/6LJy51Wd08O) on The Go Playground,
108+
or the same tool on [goplay.tools](https://goplay.tools/snippet/6LJy51Wd08O).
102109

103110

104111
## FAQ
@@ -109,16 +116,20 @@ Valid characters include `A-Z`, `a-z`, `0-9`, `/` and `+`. There are no other ch
109116

110117
You can also use regex expressions to search.
111118

119+
112120
### Why does `test` & `tes1` show different probabilities despite having 4 characters each?
113121

114122
With case-insensitive searches (default), a-z have the chance of matching both uppercase and lowercase. A search for "cat" can match `Cat`, `cAT` etc.
115123

116124

117125
### How accurate are the estimated times?
118126

119-
They are not (and cannot be) accurate. Keys are completely randomly generated, and the estimate is based on a law of averages. For instance, you could find a match for a one in a billion chance on the very first hit, or it could take you 5 billion attempts. It will however give you an indication based on your CPU speed, word count, case sensitivity, and use of numbers or characters.
127+
They are not (and cannot be) accurate. Keys are completely randomly generated, and the estimate is based on a law of averages.
128+
For instance, you could find a match for a one in a billion chance on the very first hit, or it could take you 5 billion attempts.
129+
It will however give you an indication based on your CPU speed, word count, case sensitivity, and use of numbers or characters.
120130

121131

122132
### Why do I need this?
123133

124-
You don't. I wrote it because I run a WireGuard server, which does not provide any reference as to who the key belongs to (`wg` on the server). Using vanity keys, I can at least identify connections. I also wanted to learn more about multi-core processing in Golang.
134+
You don't. I wrote it because I run a WireGuard server, which does not provide any reference as to who the key belongs to (`wg` on the server).
135+
Using vanity keys, I can at least identify connections. I also wanted to learn more about multi-core processing in Golang.

keygen/utils.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import (
99
"time"
1010
)
1111

12-
// regexChars contains the list of regex metacharacters, excluding +,
13-
// which is valid in a key
12+
// regexChars contains the list of the metacharacters used in regular expressions,
13+
// but not including the + character, which is valid in a key
1414
const regexChars = `^$.|?*-[]{}()\`
1515

1616
// regexWillNeverMatch is a shared error message that the regex will never match
@@ -113,21 +113,23 @@ func invalidRegexMsg(s string, errmsg string) string {
113113

114114
// IsValidRegex checks the regex has any chance of matching a key
115115
func IsValidRegex(s string) string {
116-
// A consise guide on golang's regex syntax is at
116+
// A concise guide on golang's regex syntax is at
117117
// https://pkg.go.dev/regexp/syntax
118118

119119
stripped := removeMetacharacters(s)
120120
if !IsValidSearch(stripped) {
121121
return InvalidSearchMsg(s)
122122
}
123123

124-
// Expressions with '^' character
124+
// Regexes containing the '^' character:
125+
125126
re := regexp.MustCompile(`.\^`)
126127
if re.MatchString(s) {
127128
return invalidRegexMsg(s, "The '^' character must appear at the beginning of the search term")
128129
}
129130

130-
// Expressions with '$' character
131+
// Regexes containing the '$' character:
132+
131133
re = regexp.MustCompile(`\$.`)
132134
if re.MatchString(s) {
133135
return invalidRegexMsg(s, "The '$' character must appear at the end of the search term")
@@ -141,23 +143,23 @@ func IsValidRegex(s string) string {
141143
return invalidRegexMsg(s, "The '=' character can only appear at the end of a key")
142144
}
143145
// The command:
144-
// wireguard-vanity-keygen -l 1000 . | grep private | cut -c 105- | sort -u | tr -d "=" | tr -d "\n"
146+
// wireguard-vanity-keygen -l 1000 a | grep private | cut -c 105- | sort -u | tr -d "=" | tr -d "\n"
145147
// outputs:
146148
// 048AEIMQUYcgkosw
147149
re = regexp.MustCompile(`[^048AEIMQUYcgkosw]=\$`)
148150
if re.MatchString(s) {
149151
return invalidRegexMsg(s, regexWillNeverMatch)
150152
}
151153

152-
// Expressions with backslashes:
154+
// Regexes containing backslashes:
153155

154156
// A regex of just a backslash and a single character will never match
155157
re = regexp.MustCompile(`^\\.$`)
156158
if re.MatchString(s) {
157159
return invalidRegexMsg(s, regexWillNeverMatch)
158160
}
159161

160-
// Control characters and many octal values will meter match, disallow them all
162+
// Control characters and most octal values will never match, disallow them all
161163
re = regexp.MustCompile(`\\[aftnrxswWpP0-7]`)
162164
if re.MatchString(s) {
163165
return invalidRegexMsg(s, regexWillNeverMatch)
@@ -169,7 +171,7 @@ func IsValidRegex(s string) string {
169171
return invalidRegexMsg(s, regexWillNeverMatch)
170172
}
171173

172-
// Expressions with character classes: [[:alnum:]], etc.
174+
// Regexes containing character classes: [[:alnum:]], etc.
173175

174176
// [[:blank:]], [[:cntrl:]], [[:punct:]] and [[:space:]] will never match
175177
re = regexp.MustCompile(`\[\[:(blank|cntrl|punct|space):\]\]`)
@@ -186,7 +188,7 @@ func IsValidRegex(s string) string {
186188
return ""
187189
}
188190

189-
// removeMetacharacters removes regex metacharacters from the string
191+
// removeMetacharacters removes regex metacharacters (except +) from the string
190192
func removeMetacharacters(s string) string {
191193
// This logic isn't needed anymore, as we don't attempt to calculate the probability of regular expressions
192194
// // remove (?i) from beginning of string
@@ -203,7 +205,7 @@ func removeMetacharacters(s string) string {
203205
// s = re.ReplaceAllLiteralString(s, "x")
204206

205207
// strip out remaining regexp metacharacters
206-
for _, rune1 := range []rune(regexChars) {
208+
for _, rune1 := range regexChars {
207209
s = strings.ReplaceAll(s, string(rune1), "")
208210
}
209211
return s

keygen/worker.go

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package keygen
22

33
import (
4+
"fmt"
45
"regexp"
56
"strings"
67
"sync"
@@ -13,6 +14,7 @@ type Options struct {
1314
Threads int
1415
CaseSensitive bool
1516
Cores int
17+
Timeout string
1618
}
1719

1820
// Cruncher struct
@@ -23,6 +25,8 @@ type Cruncher struct {
2325
RegexpMap map[*regexp.Regexp]int
2426
thread chan int
2527
Abort bool // set to true to abort processing
28+
timeout time.Duration
29+
timedout bool
2630
}
2731

2832
// Pair struct
@@ -32,12 +36,13 @@ type Pair struct {
3236
}
3337

3438
// New returns a Cruncher
35-
func New(options Options) *Cruncher {
39+
func New(options Options, timeout time.Duration) *Cruncher {
3640
return &Cruncher{
3741
Options: options,
3842
WordMap: make(map[string]int),
3943
RegexpMap: make(map[*regexp.Regexp]int),
4044
thread: make(chan int, options.Cores),
45+
timeout: timeout,
4146
}
4247
}
4348

@@ -173,14 +178,50 @@ func (c *Cruncher) CollectToSlice() []Pair {
173178

174179
// Find will invoke a callback function for each match to support some interactivity or at least feedback
175180
func (c *Cruncher) Find(cb func(match Pair)) {
181+
182+
if c.timeout == time.Duration(0) {
183+
// If you change anything in this for block, make the corresponding change in
184+
// the timeout-aware for block below this one.
185+
for {
186+
c.thread <- 1 // will block if there is MAX ints in threads
187+
go func() {
188+
if c.crunch(cb) {
189+
c.Abort = true
190+
}
191+
}()
192+
if c.Abort {
193+
return
194+
}
195+
}
196+
}
197+
198+
t := time.NewTimer(c.timeout)
199+
200+
// This is same code as immediately above, with only the timeout logic added.
201+
// Since the code is simple enough, let's duplicate it, so we don't slow down
202+
// the calculations with unnecessary logic, if the user didn't specify a timeout.
176203
for {
177204
c.thread <- 1 // will block if there is MAX ints in threads
178-
go func() {
205+
go func(t *time.Timer) {
179206
if c.crunch(cb) {
180207
c.Abort = true
208+
return
181209
}
182-
}()
210+
select {
211+
case <-t.C:
212+
c.timedout = true
213+
c.Abort = true
214+
default:
215+
}
216+
}(t)
183217
if c.Abort {
218+
if c.timedout {
219+
fmt.Printf("Timed out after %v\n", c.timeout)
220+
} else {
221+
if !t.Stop() {
222+
<-t.C
223+
}
224+
}
184225
return
185226
}
186227
}

main.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func main() {
4646
flag.BoolVarP(&options.CaseSensitive, "case-sensitive", "c", false, "case sensitive match (default false)")
4747
flag.IntVarP(&options.Threads, "threads", "t", options.Cores, "threads")
4848
flag.IntVarP(&options.LimitResults, "limit", "l", 1, "limit results to n (exists after)")
49+
flag.StringVarP(&options.Timeout, "timeout", "T", "", "quit after n minutes (allowed suffixes: s/m/h) (default \"\")")
4950

5051
flag.Parse(os.Args[1:])
5152
args := flag.Args()
@@ -61,7 +62,13 @@ func main() {
6162
options.Cores = options.Threads
6263
}
6364

64-
c := keygen.New(options)
65+
timeout, err := parseTimeout(options.Timeout)
66+
if err != nil {
67+
fmt.Fprintf(os.Stderr, "Invalid timeout value: %s\n", err)
68+
os.Exit(2)
69+
}
70+
71+
c := keygen.New(options, timeout)
6572

6673
fmt.Printf("Calculating speed: ")
6774

@@ -125,6 +132,10 @@ func main() {
125132
c.RegexpMap[re] = options.LimitResults
126133
}
127134

135+
if timeout > time.Duration(0) {
136+
fmt.Printf("\nQuitting after %v, or sooner if all matching keys are found...\n", timeout)
137+
}
138+
128139
fmt.Printf("\nPress Ctrl-c to cancel\n\n")
129140
if !summary {
130141
c.Find(func(match keygen.Pair) {
@@ -136,3 +147,22 @@ func main() {
136147
}
137148
}
138149
}
150+
151+
// parseTimeout parses the timeout string to a time.Duration. If the input is
152+
// solely digits, minutes is assumed
153+
func parseTimeout(t string) (time.Duration, error) {
154+
if t == "" {
155+
return time.Duration(0), nil
156+
}
157+
158+
re := regexp.MustCompile(`^[\d\.]+$`)
159+
if re.MatchString(t) {
160+
t += "m"
161+
}
162+
complex, err := time.ParseDuration(t)
163+
if err != nil {
164+
return time.Duration(0), err
165+
}
166+
167+
return complex, nil
168+
}

0 commit comments

Comments
 (0)