Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add regex support #13

Merged
merged 12 commits into from
May 12, 2024
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
12 changes: 9 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,22 +31,24 @@ Options:
## Example

```
$ wireguard-vanity-keygen -l 4 test pc1/
$ wireguard-vanity-keygen -l 3 test pc1/ "^(abc|def)"
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)
Probability for "^(abc|def)": 1 in 3,010,936,384 (approx 3 hours, 40 minutes per match) (approximation may be wildly off, as 'abcdef' is test string)

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 +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 ACcI8j3TfWtGtZIqaf8a6qAxUx5fcuROMls2HRR3yGs= public AbcP+qWv8OtXGHW2s2xWi8/uMNU7PxyDJZWcb0kQ5Ds=
private KLBbXjsdWoF+FKVzTsJh//90rNxrUeBAxw5b4CaXDXI= public DEfYIHnja/EP4KYcvwdbQcu03ITMXIHLoeC3d0ppkAw=
private 0DB5zytfbxDs/fo0wmZBO12KbfeSTmxUD8S9ZQUXpWg= public defP6d76lpIGu6aoBjKza16dZKirr5yzr5SKqihx2xw=
```


Expand All @@ -70,6 +73,8 @@ but increasing the limit to two (`--limit 2`) will double the estimated time, th

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

Most regex expressions that include the pipe character (`|`), such as `^(abc|def)`, will cause the calculation to be wildly off.

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

## Installing
Expand All @@ -84,6 +89,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
25 changes: 25 additions & 0 deletions keygen/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ import (
"math"
"regexp"
"strconv"
"strings"
"time"
)

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

// IsValidSearch checks the search does not contain any invalid characters
func IsValidSearch(s string) bool {
var r = regexp.MustCompile(`[^a-zA-Z0-9\/\+]`)
Expand Down Expand Up @@ -87,3 +92,23 @@ func NumberFormat(n int64) string {
}
}
}

func RemoveMetacharacters(s string) string {
if !strings.ContainsAny(s, regexChars) {
return s
}
// remove (?i)
re1 := regexp.MustCompile(`^\([^)]*\)`)
s = re1.ReplaceAllLiteralString(s, "")
// replace [a-b]+ with a
re2 := regexp.MustCompile(`\[[^]]*\]\+?`)
s = re2.ReplaceAllLiteralString(s, "a")
// strip all {n}
re3 := regexp.MustCompile(`\{[^}]+\}`)
s = re3.ReplaceAllLiteralString(s, "")
// strip out remaining regexp metacharacters
for _, rune1 := range []rune(regexChars) {
s = strings.ReplaceAll(s, string(rune1), "")
}
return s
}
76 changes: 54 additions & 22 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,12 @@ 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
regexpMapMutex sync.RWMutex
thread chan int
Abort bool // set to true to abort processing
}

// Pair struct
Expand All @@ -32,9 +35,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 All @@ -58,17 +62,34 @@ func (c *Cruncher) crunch(cb func(match Pair)) bool {

// Allow only one routine at a time to avoid
// "concurrent map iteration and map write"
c.mapMutex.Lock()
defer c.mapMutex.Unlock()
for w, count := range c.WordMap {
if count == 0 {
continue
if len(c.WordMap) > 0 {
c.mapMutex.Lock()
for w, count := range c.WordMap {
if count == 0 {
continue
}
completed = false
if strings.HasPrefix(matchKey, w) {
c.WordMap[w] = count - 1
cb(Pair{Private: k.String(), Public: pub})
}
}
completed = false
if strings.HasPrefix(matchKey, w) {
c.WordMap[w] = count - 1
cb(Pair{Private: k.String(), Public: pub})
c.mapMutex.Unlock()
}

if len(c.RegexpMap) > 0 {
c.regexpMapMutex.Lock()
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.regexpMapMutex.Unlock()
}

<-c.thread // removes an int from threads, allowing another to proceed
Expand Down Expand Up @@ -100,13 +121,24 @@ func (c *Cruncher) CalculateSpeed() (int64, time.Duration) {
_ = k.String()
t := strings.ToLower(k.Public().String())

// Allow only one routine at a time to avoid
// "concurrent map iteration and map write"
c.mapMutex.Lock()
defer c.mapMutex.Unlock()
for w := range c.WordMap {
_ = strings.HasPrefix(t, w)
if len(c.WordMap) > 0 {
// Allow only one routine at a time to avoid
// "concurrent map iteration and map write"
c.mapMutex.Lock()
for w := range c.WordMap {
_ = strings.HasPrefix(t, w)
}
c.mapMutex.Unlock()
}

if len(c.RegexpMap) > 0 {
c.regexpMapMutex.Lock()
for w := range c.RegexpMap {
_ = w.MatchString(t)
}
c.regexpMapMutex.Unlock()
}

<-c.thread // removes an int from threads, allowing another to proceed
n++
}()
Expand Down
31 changes: 22 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"
"os"
"regexp"
"runtime"
"strings"
"time"
Expand Down Expand Up @@ -76,22 +77,34 @@ func main() {

for _, word := range args {
sword := word
if !options.CaseSensitive {
sword = strings.ToLower(sword)
}
if !keygen.IsValidSearch(sword) {
stripped := keygen.RemoveMetacharacters(sword)
if !keygen.IsValidSearch(stripped) {
fmt.Printf("\n\"%s\" contains invalid characters\n", word)
fmt.Println("Valid characters include letters [a-z], numbers [0-9], + and /")
os.Exit(2)
}
c.WordMap[sword] = options.LimitResults

probability := keygen.CalculateProbability(sword, options.CaseSensitive)
if stripped != sword {
if !options.CaseSensitive {
sword = "(?i)" + sword
}
regex := regexp.MustCompile(sword)
c.RegexpMap[regex] = options.LimitResults
} else {
if !options.CaseSensitive {
sword = strings.ToLower(sword)
}
c.WordMap[sword] = options.LimitResults
}
probability := keygen.CalculateProbability(stripped, options.CaseSensitive)
estimate64 := int64(speed) * probability
estimate := time.Duration(estimate64)

fmt.Printf("Probability for \"%s\": 1 in %s (approx %s per match)\n",
word, keygen.NumberFormat(probability), keygen.HumanizeDuration(estimate))
comment := ""
if len(stripped) != len(sword) {
comment = fmt.Sprintf(" (approximation may be wildly off, as '%s' is test string)", stripped)
}
fmt.Printf("Probability for \"%s\": 1 in %s (approx %s per match)%s\n",
word, keygen.NumberFormat(probability), keygen.HumanizeDuration(estimate), comment)
}

fmt.Printf("\nPress Ctrl-c to cancel\n\n")
Expand Down