Skip to content

Commit 4a45a0f

Browse files
committed
Merge branch 'release/0.0.9'
2 parents 9e84919 + 6930451 commit 4a45a0f

9 files changed

Lines changed: 254 additions & 27 deletions

File tree

.github/workflows/release-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- uses: actions/checkout@v4
2323

2424
# build the binaries
25-
- uses: wangyoucao577/go-release-action@v1.49
25+
- uses: wangyoucao577/go-release-action@v1.50
2626
with:
2727
github_token: ${{ secrets.GITHUB_TOKEN }}
2828
goos: ${{ matrix.goos }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/dist/
22
/wireguard-vanity-keygen
3+
/wireguard-vanity-keygen.exe
34
/cmd/wg-vanity-keygen/wg-vanity-keygen

CHANGELOG.md

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

3+
## [0.0.9]
4+
5+
- Add regex support (#13)
6+
- Update Go modules
7+
8+
39
## [0.0.8]
410

511
- Update Go modules

README.md

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ A command-line vanity (public) key generator for [WireGuard](https://www.wiregua
1010
- Generates compliant [curve25519](https://cr.yp.to/ecdh.html) private and public keys
1111
- Configurable multi-core processing (defaults to all cores)
1212
- Optional case sensitive searching
13+
- Optional regex searching
1314
- Search multiple prefixes at once
1415
- Exit after results limit reached (defaults to 1)
1516
- Displays probability and estimated runtime based on quick benchmark
@@ -30,24 +31,70 @@ Options:
3031
## Example
3132

3233
```
33-
$ wireguard-vanity-keygen -l 4 test pc1/
34+
$ wireguard-vanity-keygen -l 3 test pc1/ "^pc7[+/]"
3435
Calculating speed: 49,950 calculations per second using 4 CPU cores
3536
Case-insensitive search, exiting after 4 results
3637
Probability for "test": 1 in 2,085,136 (approx 41 seconds per match)
3738
Probability for "pc1/": 1 in 5,914,624 (approx 1 minute per match)
39+
Cannot calculate probability for the regular expression "^pc7[/+]"
3840
3941
Press Ctrl-c to cancel
4042
4143
private OFVUjUoTNQp94fNPB9GCLzxiJPTbN03rcDPrVd12uFc= public tEstMXL/3ZzAd2TnVlr1BNs/+eOnKzSHpGUnjspk3kc=
4244
private gInIEDmENYbyuaWR1W/KLfximExwbcCg45W2WOmEc0I= public TestKmA/XVagDW/JsHBXk5mhYJ6E1N1lAWeIeCttgRs=
4345
private yDQLNiQlfnMGhUBsbLQjoBbuNezyHug31Qa1Ht6cgkw= public PC1/3oUId241TLYImJLUObR8NNxz4HXzG4z+EazfWxY=
46+
private QIbJgxy83+F/1kdogcF+T04trs+1N9gAr1t5th2tLXM= public Pc7+h172sx0TfIMikjgszM/B8i8/ghi7qJVOwWQtx0w=
4447
private +CUqn4jcKoL8pw53pD4IzfMKW/IMceDWKcM2W5Dxtn4= public teStmGXZwiJl9HmfnTSmk83girtiIH8oZEa6PFJ8F1Y=
45-
private 2G0X+IvBLw3NRfRnHb8diIXp96NQ9wSu4gdqPidy3nw= public tESt3DBU40Q/Zkp0d1aeb6HOgEOsEM3BxzNqLckKhhc=
4648
private EMaUfQvAEABpQV/21ALJP5YtyGerRXAn8u67j2AQzVs= public pC1/t2x5V99Y1SBqNgPZDPsa6r+L5y3BJ4XUCJMar3g=
4749
private wNuHOKCfoH1emfvijXNBoc/7KjrEXUeof7tSdGWvRFo= public PC1/jXQosaBad2HePOm/w1KjCZ82eT3qNbfzNDZiwTs=
48-
private 8IdcNsman/ZRGvqWzw1e5cRfhhdtAAmk02X9TkQxhHI= public pC1/N8coOcXmcwO09QXxLrF5/BoHQfvp/qsysGPXiw0=
50+
private gJtn0woDChGvyN2eSdc7mTpAFA/nA6jykJeK5bYYfFA= public Pc7+UEJSHiWsQ9zkO2q+guqDK4sc3VMDMgJu+h/bOFI=
51+
private IMyPmYm/v0SPmB62hC8l6kfxT3/Lfp7dMioo+SM6T2c= public Pc7/uVfD/ZftxWBHwYbaudEywUS61biBcpj5Tw830Q4=
4952
```
5053

54+
## Timings
55+
56+
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:
58+
59+
| Length | Case-insensitive | Case-sensitive |
60+
| :------ | :--------------- | :------------- |
61+
| 3 chars | 0 seconds | 1 second |
62+
| 4 chars | 9 seconds | 1 minute |
63+
| 5 chars | 5 minutes | 1.25 hours |
64+
| 6 chars | 4 hours | 3.5 days |
65+
| 7 chars | 6 days | 7 months |
66+
| 8 chars | 7 months | 38 years |
67+
| 9 chars | 22 years | 175 years |
68+
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.
72+
73+
If any search term contains numbers, the timings would fall somewhere between the case-insensitive and case-sensitive columns.
74+
75+
Of course, your mileage will differ, depending on the number, and speed, of your CPU cores.
76+
77+
## Regular Expressions
78+
79+
Since each additional letter in a search term increases the search time exponentially, searching using a regular expression may
80+
reduce the time considerably. Here are some examples:
81+
82+
1. `.*word.*` - find word anywhere in the key (`word.*` and `.*word` will also work)
83+
2. `^.{0,10}word` - find word anywhere in the first 10 letters of the key
84+
3. `word1.*word2` - find two words, anywhere in the key
85+
4. `^[s5][o0][ll]ar` - find 'solar', or the visually similar 's01ar`, at the beginning of the key
86+
5. `^(best|next)[/+]` - find 'best', or the 'next' best, at the beginning of the key, with `/` or `+` as a delimiter
87+
88+
A good guide on Go's regular expression syntax is at https://pkg.go.dev/regexp/syntax.
89+
90+
To include a `+` in your regular expression, preface it with a backslash, like `\+`.
91+
92+
NOTE: If your search term contains shell metacharacters, such as `|`, or `^`, you will need to quote it.
93+
On Windows, you must use double quotes. For example: `"^(a|b)"`.
94+
95+
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+
5198
## Installing
5299

53100
Download the [latest binary release](https://github.com/axllent/wireguard-vanity-keygen/releases/latest) for your system,
@@ -60,6 +107,7 @@ or build from source `go install github.com/axllent/wireguard-vanity-keygen@late
60107

61108
Valid characters include `A-Z`, `a-z`, `0-9`, `/` and `+`. There are no other characters in a hash.
62109

110+
You can also use regex expressions to search.
63111

64112
### Why does `test` & `tes1` show different probabilities despite having 4 characters each?
65113

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ go 1.17
44

55
require (
66
github.com/spf13/pflag v1.0.5
7-
golang.org/x/crypto v0.21.0
7+
golang.org/x/crypto v0.23.0
88
)

go.sum

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
44
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
55
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
66
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
7-
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
8-
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
7+
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
8+
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
99
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
1010
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
1111
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -25,19 +25,20 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
2525
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2626
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2727
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
28-
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
28+
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
2929
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
3030
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
3131
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
3232
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
3333
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
34-
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
34+
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
3535
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
3636
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
3737
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
3838
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
3939
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
4040
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
41+
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
4142
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
4243
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
4344
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

keygen/utils.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,28 @@ import (
55
"math"
66
"regexp"
77
"strconv"
8+
"strings"
89
"time"
910
)
1011

12+
// regexChars contains the list of regex metacharacters, excluding +,
13+
// which is valid in a key
14+
const regexChars = `^$.|?*-[]{}()\`
15+
16+
// regexWillNeverMatch is a shared error message that the regex will never match
17+
const regexWillNeverMatch = "The regular expression will never match"
18+
1119
// IsValidSearch checks the search does not contain any invalid characters
1220
func IsValidSearch(s string) bool {
1321
var r = regexp.MustCompile(`[^a-zA-Z0-9\/\+]`)
1422
return !r.MatchString(s)
1523
}
1624

25+
// InvalidSearchMsg returns the error message the search term contains invalid characters
26+
func InvalidSearchMsg(s string) string {
27+
return fmt.Sprintf("\n\"%s\" contains invalid characters\nValid characters include letters [a-z], numbers [0-9], + and /", s)
28+
}
29+
1730
// HumanizeDuration returns a human-readable output of time.Duration
1831
func HumanizeDuration(duration time.Duration) string {
1932
// more than duration can handle
@@ -87,3 +100,111 @@ func NumberFormat(n int64) string {
87100
}
88101
}
89102
}
103+
104+
// IsRegex returns true if any regex metacharacters (except +) are in the search term
105+
func IsRegex(s string) bool {
106+
return strings.ContainsAny(s, regexChars)
107+
}
108+
109+
// invalidRegexMsg returns an error message how the regex is invalid
110+
func invalidRegexMsg(s string, errmsg string) string {
111+
return fmt.Sprintf("\n\"%s\" is an invalid regular expression\n%s", s, errmsg)
112+
}
113+
114+
// IsValidRegex checks the regex has any chance of matching a key
115+
func IsValidRegex(s string) string {
116+
// A consise guide on golang's regex syntax is at
117+
// https://pkg.go.dev/regexp/syntax
118+
119+
stripped := removeMetacharacters(s)
120+
if !IsValidSearch(stripped) {
121+
return InvalidSearchMsg(s)
122+
}
123+
124+
// Expressions with '^' character
125+
re := regexp.MustCompile(`.\^`)
126+
if re.MatchString(s) {
127+
return invalidRegexMsg(s, "The '^' character must appear at the beginning of the search term")
128+
}
129+
130+
// Expressions with '$' character
131+
re = regexp.MustCompile(`\$.`)
132+
if re.MatchString(s) {
133+
return invalidRegexMsg(s, "The '$' character must appear at the end of the search term")
134+
}
135+
re = regexp.MustCompile(`[^=]\$`)
136+
if re.MatchString(s) {
137+
return invalidRegexMsg(s, "A search at the end of the string must contain an '=' character, as all keys end with an `=`")
138+
}
139+
re = regexp.MustCompile(`=[^$]`)
140+
if re.MatchString(s) {
141+
return invalidRegexMsg(s, "The '=' character can only appear at the end of a key")
142+
}
143+
// The command:
144+
// wireguard-vanity-keygen -l 1000 . | grep private | cut -c 105- | sort -u | tr -d "=" | tr -d "\n"
145+
// outputs:
146+
// 048AEIMQUYcgkosw
147+
re = regexp.MustCompile(`[^048AEIMQUYcgkosw]=\$`)
148+
if re.MatchString(s) {
149+
return invalidRegexMsg(s, regexWillNeverMatch)
150+
}
151+
152+
// Expressions with backslashes:
153+
154+
// A regex of just a backslash and a single character will never match
155+
re = regexp.MustCompile(`^\\.$`)
156+
if re.MatchString(s) {
157+
return invalidRegexMsg(s, regexWillNeverMatch)
158+
}
159+
160+
// Control characters and many octal values will meter match, disallow them all
161+
re = regexp.MustCompile(`\\[aftnrxswWpP0-7]`)
162+
if re.MatchString(s) {
163+
return invalidRegexMsg(s, regexWillNeverMatch)
164+
}
165+
166+
// Disallow backslashes followed by any non-alnum or + character
167+
re = regexp.MustCompile(`\\[^A-Za-z0-9+]`)
168+
if re.MatchString(s) {
169+
return invalidRegexMsg(s, regexWillNeverMatch)
170+
}
171+
172+
// Expressions with character classes: [[:alnum:]], etc.
173+
174+
// [[:blank:]], [[:cntrl:]], [[:punct:]] and [[:space:]] will never match
175+
re = regexp.MustCompile(`\[\[:(blank|cntrl|punct|space):\]\]`)
176+
if re.MatchString(s) {
177+
return invalidRegexMsg(s, regexWillNeverMatch)
178+
}
179+
180+
// [[^:ascii:]], [[^:graph:]], [[^:print:]] will never match
181+
re = regexp.MustCompile(`\[\[\^:(ascii|graph|print):\]\]`)
182+
if re.MatchString(s) {
183+
return invalidRegexMsg(s, regexWillNeverMatch)
184+
}
185+
186+
return ""
187+
}
188+
189+
// removeMetacharacters removes regex metacharacters from the string
190+
func removeMetacharacters(s string) string {
191+
// This logic isn't needed anymore, as we don't attempt to calculate the probability of regular expressions
192+
// // remove (?i) from beginning of string
193+
// re := regexp.MustCompile(`^\([^)]*\)`)
194+
// s = re.ReplaceAllLiteralString(s, "")
195+
// // replace [a-b]+ with x
196+
// re = regexp.MustCompile(`\[[^]]*\]\+?`)
197+
// s = re.ReplaceAllLiteralString(s, "x")
198+
// // strip all {n}
199+
// re = regexp.MustCompile(`\{[^}]+\}`)
200+
// s = re.ReplaceAllLiteralString(s, "")
201+
// // replace = with x
202+
// re = regexp.MustCompile(`=`)
203+
// s = re.ReplaceAllLiteralString(s, "x")
204+
205+
// strip out remaining regexp metacharacters
206+
for _, rune1 := range []rune(regexChars) {
207+
s = strings.ReplaceAll(s, string(rune1), "")
208+
}
209+
return s
210+
}

keygen/worker.go

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

33
import (
4+
"regexp"
45
"strings"
56
"sync"
67
"time"
@@ -17,10 +18,11 @@ type Options struct {
1718
// Cruncher struct
1819
type Cruncher struct {
1920
Options
20-
WordMap map[string]int
21-
mapMutex sync.RWMutex
22-
thread chan int
23-
Abort bool // set to true to abort processing
21+
WordMap map[string]int
22+
mapMutex sync.RWMutex
23+
RegexpMap map[*regexp.Regexp]int
24+
thread chan int
25+
Abort bool // set to true to abort processing
2426
}
2527

2628
// Pair struct
@@ -32,9 +34,10 @@ type Pair struct {
3234
// New returns a Cruncher
3335
func New(options Options) *Cruncher {
3436
return &Cruncher{
35-
Options: options,
36-
WordMap: make(map[string]int),
37-
thread: make(chan int, options.Cores),
37+
Options: options,
38+
WordMap: make(map[string]int),
39+
RegexpMap: make(map[*regexp.Regexp]int),
40+
thread: make(chan int, options.Cores),
3841
}
3942
}
4043

@@ -71,6 +74,17 @@ func (c *Cruncher) crunch(cb func(match Pair)) bool {
7174
}
7275
}
7376

77+
for w, count := range c.RegexpMap {
78+
if count == 0 {
79+
continue
80+
}
81+
completed = false
82+
if w.MatchString(matchKey) {
83+
c.RegexpMap[w] = count - 1
84+
cb(Pair{Private: k.String(), Public: pub})
85+
}
86+
}
87+
7488
<-c.thread // removes an int from threads, allowing another to proceed
7589
return completed
7690
}
@@ -107,6 +121,11 @@ func (c *Cruncher) CalculateSpeed() (int64, time.Duration) {
107121
for w := range c.WordMap {
108122
_ = strings.HasPrefix(t, w)
109123
}
124+
125+
for w := range c.RegexpMap {
126+
_ = w.MatchString(t)
127+
}
128+
110129
<-c.thread // removes an int from threads, allowing another to proceed
111130
n++
112131
}()

0 commit comments

Comments
 (0)