Skip to content

Commit

Permalink
Merge branch 'release/0.0.9'
Browse files Browse the repository at this point in the history
  • Loading branch information
axllent committed May 12, 2024
2 parents 9e84919 + 6930451 commit 4a45a0f
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 27 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- uses: actions/checkout@v4

# build the binaries
- uses: wangyoucao577/go-release-action@v1.49
- uses: wangyoucao577/go-release-action@v1.50
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/dist/
/wireguard-vanity-keygen
/wireguard-vanity-keygen.exe
/cmd/wg-vanity-keygen/wg-vanity-keygen
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [0.0.9]

- Add regex support (#13)
- Update Go modules


## [0.0.8]

- Update Go modules
Expand Down
54 changes: 51 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ A command-line vanity (public) key generator for [WireGuard](https://www.wiregua
- Generates compliant [curve25519](https://cr.yp.to/ecdh.html) private and public keys
- Configurable multi-core processing (defaults to all cores)
- Optional case sensitive searching
- Optional regex searching
- Search multiple prefixes at once
- Exit after results limit reached (defaults to 1)
- Displays probability and estimated runtime based on quick benchmark
Expand All @@ -30,24 +31,70 @@ Options:
## Example

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

## Timings

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

| Length | Case-insensitive | Case-sensitive |
| :------ | :--------------- | :------------- |
| 3 chars | 0 seconds | 1 second |
| 4 chars | 9 seconds | 1 minute |
| 5 chars | 5 minutes | 1.25 hours |
| 6 chars | 4 hours | 3.5 days |
| 7 chars | 6 days | 7 months |
| 8 chars | 7 months | 38 years |
| 9 chars | 22 years | 175 years |

Note that the above timings are for finding a result for any search term.
Passing multiple search terms will not substantially increase the time,
but increasing the limit to two (`--limit 2`) will double the estimated time, three will triple the time, etc.

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

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

## Regular Expressions

Since each additional letter in a search term increases the search time exponentially, searching using a regular expression may
reduce the time considerably. Here are some examples:

1. `.*word.*` - find word anywhere in the key (`word.*` and `.*word` will also work)
2. `^.{0,10}word` - find word anywhere in the first 10 letters of the key
3. `word1.*word2` - find two words, anywhere in the key
4. `^[s5][o0][ll]ar` - find 'solar', or the visually similar 's01ar`, at the beginning of the key
5. `^(best|next)[/+]` - find 'best', or the 'next' best, at the beginning of the key, with `/` or `+` as a delimiter

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

To include a `+` in your regular expression, preface it with a backslash, like `\+`.

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

NOTE: Complex regular expressions, such as those using escape sequences, flags, or character classes, may never match a key.
To avoid that, consider testing your regex using a tool such as [this one](https://go.dev/play/p/6LJy51Wd08O).

## Installing

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

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

You can also use regex expressions to search.

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

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ go 1.17

require (
github.com/spf13/pflag v1.0.5
golang.org/x/crypto v0.21.0
golang.org/x/crypto v0.23.0
)
9 changes: 5 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
Expand All @@ -25,19 +25,20 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
Expand Down
121 changes: 121 additions & 0 deletions keygen/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,28 @@ import (
"math"
"regexp"
"strconv"
"strings"
"time"
)

// regexChars contains the list of regex metacharacters, excluding +,
// which is valid in a key
const regexChars = `^$.|?*-[]{}()\`

// regexWillNeverMatch is a shared error message that the regex will never match
const regexWillNeverMatch = "The regular expression will never match"

// IsValidSearch checks the search does not contain any invalid characters
func IsValidSearch(s string) bool {
var r = regexp.MustCompile(`[^a-zA-Z0-9\/\+]`)
return !r.MatchString(s)
}

// InvalidSearchMsg returns the error message the search term contains invalid characters
func InvalidSearchMsg(s string) string {
return fmt.Sprintf("\n\"%s\" contains invalid characters\nValid characters include letters [a-z], numbers [0-9], + and /", s)
}

// HumanizeDuration returns a human-readable output of time.Duration
func HumanizeDuration(duration time.Duration) string {
// more than duration can handle
Expand Down Expand Up @@ -87,3 +100,111 @@ func NumberFormat(n int64) string {
}
}
}

// IsRegex returns true if any regex metacharacters (except +) are in the search term
func IsRegex(s string) bool {
return strings.ContainsAny(s, regexChars)
}

// invalidRegexMsg returns an error message how the regex is invalid
func invalidRegexMsg(s string, errmsg string) string {
return fmt.Sprintf("\n\"%s\" is an invalid regular expression\n%s", s, errmsg)
}

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

stripped := removeMetacharacters(s)
if !IsValidSearch(stripped) {
return InvalidSearchMsg(s)
}

// Expressions with '^' character
re := regexp.MustCompile(`.\^`)
if re.MatchString(s) {
return invalidRegexMsg(s, "The '^' character must appear at the beginning of the search term")
}

// Expressions with '$' character
re = regexp.MustCompile(`\$.`)
if re.MatchString(s) {
return invalidRegexMsg(s, "The '$' character must appear at the end of the search term")
}
re = regexp.MustCompile(`[^=]\$`)
if re.MatchString(s) {
return invalidRegexMsg(s, "A search at the end of the string must contain an '=' character, as all keys end with an `=`")
}
re = regexp.MustCompile(`=[^$]`)
if re.MatchString(s) {
return invalidRegexMsg(s, "The '=' character can only appear at the end of a key")
}
// The command:
// wireguard-vanity-keygen -l 1000 . | grep private | cut -c 105- | sort -u | tr -d "=" | tr -d "\n"
// outputs:
// 048AEIMQUYcgkosw
re = regexp.MustCompile(`[^048AEIMQUYcgkosw]=\$`)
if re.MatchString(s) {
return invalidRegexMsg(s, regexWillNeverMatch)
}

// Expressions with backslashes:

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

// Control characters and many octal values will meter match, disallow them all
re = regexp.MustCompile(`\\[aftnrxswWpP0-7]`)
if re.MatchString(s) {
return invalidRegexMsg(s, regexWillNeverMatch)
}

// Disallow backslashes followed by any non-alnum or + character
re = regexp.MustCompile(`\\[^A-Za-z0-9+]`)
if re.MatchString(s) {
return invalidRegexMsg(s, regexWillNeverMatch)
}

// Expressions with character classes: [[:alnum:]], etc.

// [[:blank:]], [[:cntrl:]], [[:punct:]] and [[:space:]] will never match
re = regexp.MustCompile(`\[\[:(blank|cntrl|punct|space):\]\]`)
if re.MatchString(s) {
return invalidRegexMsg(s, regexWillNeverMatch)
}

// [[^:ascii:]], [[^:graph:]], [[^:print:]] will never match
re = regexp.MustCompile(`\[\[\^:(ascii|graph|print):\]\]`)
if re.MatchString(s) {
return invalidRegexMsg(s, regexWillNeverMatch)
}

return ""
}

// removeMetacharacters removes regex metacharacters from the string
func removeMetacharacters(s string) string {
// This logic isn't needed anymore, as we don't attempt to calculate the probability of regular expressions
// // remove (?i) from beginning of string
// re := regexp.MustCompile(`^\([^)]*\)`)
// s = re.ReplaceAllLiteralString(s, "")
// // replace [a-b]+ with x
// re = regexp.MustCompile(`\[[^]]*\]\+?`)
// s = re.ReplaceAllLiteralString(s, "x")
// // strip all {n}
// re = regexp.MustCompile(`\{[^}]+\}`)
// s = re.ReplaceAllLiteralString(s, "")
// // replace = with x
// re = regexp.MustCompile(`=`)
// s = re.ReplaceAllLiteralString(s, "x")

// strip out remaining regexp metacharacters
for _, rune1 := range []rune(regexChars) {
s = strings.ReplaceAll(s, string(rune1), "")
}
return s
}
33 changes: 26 additions & 7 deletions keygen/worker.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keygen

import (
"regexp"
"strings"
"sync"
"time"
Expand All @@ -17,10 +18,11 @@ type Options struct {
// Cruncher struct
type Cruncher struct {
Options
WordMap map[string]int
mapMutex sync.RWMutex
thread chan int
Abort bool // set to true to abort processing
WordMap map[string]int
mapMutex sync.RWMutex
RegexpMap map[*regexp.Regexp]int
thread chan int
Abort bool // set to true to abort processing
}

// Pair struct
Expand All @@ -32,9 +34,10 @@ type Pair struct {
// New returns a Cruncher
func New(options Options) *Cruncher {
return &Cruncher{
Options: options,
WordMap: make(map[string]int),
thread: make(chan int, options.Cores),
Options: options,
WordMap: make(map[string]int),
RegexpMap: make(map[*regexp.Regexp]int),
thread: make(chan int, options.Cores),
}
}

Expand Down Expand Up @@ -71,6 +74,17 @@ func (c *Cruncher) crunch(cb func(match Pair)) bool {
}
}

for w, count := range c.RegexpMap {
if count == 0 {
continue
}
completed = false
if w.MatchString(matchKey) {
c.RegexpMap[w] = count - 1
cb(Pair{Private: k.String(), Public: pub})
}
}

<-c.thread // removes an int from threads, allowing another to proceed
return completed
}
Expand Down Expand Up @@ -107,6 +121,11 @@ func (c *Cruncher) CalculateSpeed() (int64, time.Duration) {
for w := range c.WordMap {
_ = strings.HasPrefix(t, w)
}

for w := range c.RegexpMap {
_ = w.MatchString(t)
}

<-c.thread // removes an int from threads, allowing another to proceed
n++
}()
Expand Down
Loading

0 comments on commit 4a45a0f

Please sign in to comment.