pslist 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pslist-0.1.0.dist-info/METADATA +117 -0
- pslist-0.1.0.dist-info/RECORD +14 -0
- pslist-0.1.0.dist-info/WHEEL +5 -0
- pslist-0.1.0.dist-info/entry_points.txt +2 -0
- pslist-0.1.0.dist-info/licenses/LICENSE +21 -0
- pslist-0.1.0.dist-info/top_level.txt +1 -0
- wl/__init__.py +0 -0
- wl/__main__.py +3 -0
- wl/cli.py +55 -0
- wl/fetcher.py +57 -0
- wl/generator.py +32 -0
- wl/leet.py +42 -0
- wl/patterns.py +51 -0
- wl/tokens.py +96 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pslist
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Username-targeted password wordlist generator
|
|
5
|
+
Author: Shanmukha
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/5h4nnu-dev/wl
|
|
8
|
+
Project-URL: Repository, https://github.com/5h4nnu-dev/wl
|
|
9
|
+
Project-URL: BugTracker, https://github.com/5h4nnu-dev/wl/issues
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# wl — Username-Targeted Password Wordlist Generator
|
|
26
|
+
|
|
27
|
+
Generate highly targeted password candidates for a given username by applying structural patterns derived from real-world breach data (RockYou, SecLists, etc.). Every generated password relates to the target username — no filler entries.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install pslist # once published
|
|
33
|
+
# or from source:
|
|
34
|
+
git clone https://github.com/5h4nnu-dev/wl.git
|
|
35
|
+
cd wl
|
|
36
|
+
pip install -e .
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
wl <username> [options]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Basic
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
wl john
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Generates `john_wordlist.txt` with ~78k unique candidates using the common word pool and structural patterns.
|
|
52
|
+
|
|
53
|
+
### Options
|
|
54
|
+
|
|
55
|
+
| Flag | Description |
|
|
56
|
+
|------|-------------|
|
|
57
|
+
| `--keywords` | Custom keywords (pets, partners, etc.) |
|
|
58
|
+
| `--birth-year` | Birth year (e.g. 1990) |
|
|
59
|
+
| `--year-range` | Year range (default: 2020-2026) |
|
|
60
|
+
| `--leet` | Apply leet substitutions |
|
|
61
|
+
| `--output FILE` | Output file (default: `<username>_wordlist.txt`) |
|
|
62
|
+
| `--min-len` | Minimum password length (default: 4) |
|
|
63
|
+
| `--max-len` | Maximum password length (default: 32) |
|
|
64
|
+
| `--wordlist` | External wordlist file (adds to token pools) |
|
|
65
|
+
| `--refresh` | Re-download common passwords cache |
|
|
66
|
+
|
|
67
|
+
### Examples
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
wl john --leet --output john.txt
|
|
71
|
+
wl admin --birth-year 1990 --year-range 2020 2026 --keywords rocket star
|
|
72
|
+
wl alice --wordlist rockyou.txt --refresh
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## How It Works
|
|
76
|
+
|
|
77
|
+
Token pools are built from the username (variants, truncations, reversals) and combined using structural patterns observed in real breaches:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
{name}{digits} → john123
|
|
81
|
+
{name}{special} → john!
|
|
82
|
+
{name}{digits}{special} → john123!
|
|
83
|
+
{Name}{digits} → John123
|
|
84
|
+
{name}{year} → john2024
|
|
85
|
+
{word}{digits} → password123
|
|
86
|
+
{name}{word} → johnpassword
|
|
87
|
+
{l33t}{digits} → j0hn123
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Token Sources
|
|
91
|
+
|
|
92
|
+
| Token | Source |
|
|
93
|
+
|-------|--------|
|
|
94
|
+
| `{name}` | username + case variants + truncations + reversed |
|
|
95
|
+
| `{Name}` | capitalized variants |
|
|
96
|
+
| `{digits}` | 0–99, common sequences, birth/year range |
|
|
97
|
+
| `{special}` | `! @ # $ % & *` |
|
|
98
|
+
| `{year}` | birth year + year range |
|
|
99
|
+
| `{word}` | top words from SecLists 10k-most-common |
|
|
100
|
+
| `{l33t}` | leet-speak variants |
|
|
101
|
+
| `{keyword}` | user-supplied custom keywords |
|
|
102
|
+
|
|
103
|
+
The common password pool is downloaded from [SecLists](https://github.com/danielmiessler/SecLists) on first run and cached at `~/.cache/wl/common_passwords.txt`.
|
|
104
|
+
|
|
105
|
+
## Use Cases
|
|
106
|
+
|
|
107
|
+
- **Offline hash cracking** — feed wordlist into Hashcat / John
|
|
108
|
+
- **Internal password spraying** — targeted low-and-slow attempts per user
|
|
109
|
+
- **Credential pattern analysis** — understand a target's password habits
|
|
110
|
+
|
|
111
|
+
## Dependencies
|
|
112
|
+
|
|
113
|
+
Python stdlib only — no third-party packages required.
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
pslist-0.1.0.dist-info/licenses/LICENSE,sha256=ESYyLizI0WWtxMeS7rGVcX3ivMezm-HOd5WdeOh-9oU,1056
|
|
2
|
+
wl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
wl/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
|
|
4
|
+
wl/cli.py,sha256=DLCKeglaJ-mNUbrr0fnLWJcitQQMqlDB_XtVnBw8ubM,2240
|
|
5
|
+
wl/fetcher.py,sha256=RhnSI5WbXY-Z9WV4ut9W0nsL2yysNzmbCLqjUqcHrHo,1641
|
|
6
|
+
wl/generator.py,sha256=rln9V3goycQF3pEDb5wMqCD7aeMQH4B1JIpH5Y4L8ic,899
|
|
7
|
+
wl/leet.py,sha256=ELICju1QagdsggUlyC2mbqBDjDx41kOYkR2DDGdu9z8,842
|
|
8
|
+
wl/patterns.py,sha256=Z8leVAew0-Z3ommOOrJ8H13SpnBRtH_wHwycrc5oHm0,1224
|
|
9
|
+
wl/tokens.py,sha256=uoftFRJB9ZChaPGeflTtDTIUuQ8owUkPdShgy9g6JZM,2461
|
|
10
|
+
pslist-0.1.0.dist-info/METADATA,sha256=Y2E4cJRPBqQIQwOguCM0mONGA0zcgkjywh6TwG5r0fI,3669
|
|
11
|
+
pslist-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
12
|
+
pslist-0.1.0.dist-info/entry_points.txt,sha256=Rw3pr85HWo0z_drJoMccDLQ5znMipC649xaEK0ob6sE,35
|
|
13
|
+
pslist-0.1.0.dist-info/top_level.txt,sha256=5xIGnFBetCT7B4-eHKWhTl8HjW0MZVbbtlIBf45fhN0,3
|
|
14
|
+
pslist-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
wl
|
wl/__init__.py
ADDED
|
File without changes
|
wl/__main__.py
ADDED
wl/cli.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from .generator import generate_wordlist
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
parser = argparse.ArgumentParser(
|
|
9
|
+
description="Generate a targeted password wordlist for a given username.",
|
|
10
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
11
|
+
epilog=(
|
|
12
|
+
"Examples:\n"
|
|
13
|
+
" wl john\n"
|
|
14
|
+
" wl john --leet --output john.txt\n"
|
|
15
|
+
" wl admin --birth-year 1990 --year-range 2020 2026 --keywords rocket star\n"
|
|
16
|
+
" wl alice --wordlist rockyou.txt --refresh\n"
|
|
17
|
+
),
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument("username", help="Target username")
|
|
20
|
+
parser.add_argument("--keywords", nargs="+", default=[], help="Additional keywords (pets, partners, etc.)")
|
|
21
|
+
parser.add_argument("--birth-year", type=int, help="Birth year (e.g. 1990)")
|
|
22
|
+
parser.add_argument("--year-range", nargs=2, type=int, default=[2020, 2026], metavar=("START", "END"), help="Year range (default: 2020 2026)")
|
|
23
|
+
parser.add_argument("--leet", action="store_true", help="Apply leet speak substitutions")
|
|
24
|
+
parser.add_argument("--output", help="Output file (default: <username>_wordlist.txt)")
|
|
25
|
+
parser.add_argument("--min-len", type=int, default=4, help="Minimum password length (default: 4)")
|
|
26
|
+
parser.add_argument("--max-len", type=int, default=32, help="Maximum password length (default: 32)")
|
|
27
|
+
parser.add_argument("--wordlist", help="External wordlist file for additional tokens")
|
|
28
|
+
parser.add_argument("--refresh", action="store_true", help="Re-download common passwords cache")
|
|
29
|
+
|
|
30
|
+
args = parser.parse_args()
|
|
31
|
+
|
|
32
|
+
output = args.output or f"{args.username}_wordlist.txt"
|
|
33
|
+
|
|
34
|
+
passwords = generate_wordlist(
|
|
35
|
+
username=args.username,
|
|
36
|
+
keywords=args.keywords,
|
|
37
|
+
birth_year=args.birth_year,
|
|
38
|
+
year_range=tuple(args.year_range),
|
|
39
|
+
leet=args.leet,
|
|
40
|
+
min_len=args.min_len,
|
|
41
|
+
max_len=args.max_len,
|
|
42
|
+
wordlist_path=args.wordlist,
|
|
43
|
+
refresh=args.refresh,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
with open(output, "w") as f:
|
|
47
|
+
for pwd in passwords:
|
|
48
|
+
f.write(pwd + "\n")
|
|
49
|
+
|
|
50
|
+
print(f"[*] Generated {len(passwords):,} unique candidates", file=sys.stderr)
|
|
51
|
+
print(f"[*] Saved to {output}", file=sys.stderr)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
main()
|
wl/fetcher.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import urllib.request
|
|
4
|
+
|
|
5
|
+
COMMON_PASSWORDS_URL = (
|
|
6
|
+
"https://raw.githubusercontent.com/danielmiessler/SecLists/" # change
|
|
7
|
+
"master/Passwords/Common-Credentials/10k-most-common.txt"
|
|
8
|
+
)
|
|
9
|
+
CACHE_DIR = os.path.expanduser("~/.cache/wl")
|
|
10
|
+
CACHE_FILE = os.path.join(CACHE_DIR, "common_passwords.txt")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _ensure_cache_dir():
|
|
14
|
+
os.makedirs(CACHE_DIR, exist_ok=True)
|
|
15
|
+
return CACHE_DIR
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def fetch_common_passwords(refresh=False):
|
|
19
|
+
_ensure_cache_dir()
|
|
20
|
+
|
|
21
|
+
if not refresh and os.path.exists(CACHE_FILE):
|
|
22
|
+
return CACHE_FILE
|
|
23
|
+
|
|
24
|
+
print("[*] No cached word pool found — downloading common passwords...", file=sys.stderr)
|
|
25
|
+
try:
|
|
26
|
+
urllib.request.urlretrieve(COMMON_PASSWORDS_URL, CACHE_FILE)
|
|
27
|
+
print(f"[*] Cached to {CACHE_FILE}", file=sys.stderr)
|
|
28
|
+
except Exception as e:
|
|
29
|
+
print(f"[!] Failed to download: {e}", file=sys.stderr)
|
|
30
|
+
if os.path.exists(CACHE_FILE):
|
|
31
|
+
return CACHE_FILE
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
return CACHE_FILE
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def extract_word_pool(wordlist_path=None, refresh=False):
|
|
38
|
+
cache_file = fetch_common_passwords(refresh)
|
|
39
|
+
words = set()
|
|
40
|
+
|
|
41
|
+
sources = []
|
|
42
|
+
if cache_file:
|
|
43
|
+
sources.append(cache_file)
|
|
44
|
+
if wordlist_path and os.path.exists(wordlist_path):
|
|
45
|
+
sources.append(wordlist_path)
|
|
46
|
+
|
|
47
|
+
for src in sources:
|
|
48
|
+
try:
|
|
49
|
+
with open(src, "r", errors="ignore") as f:
|
|
50
|
+
for line in f:
|
|
51
|
+
word = line.strip()
|
|
52
|
+
if word and word.isalpha() and 3 <= len(word) <= 15:
|
|
53
|
+
words.add(word.lower())
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
return sorted(words)
|
wl/generator.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from .tokens import build_token_pools
|
|
4
|
+
from .patterns import generate_all
|
|
5
|
+
from .fetcher import extract_word_pool
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def generate_wordlist(
|
|
9
|
+
username, keywords=None, birth_year=None, year_range=None,
|
|
10
|
+
leet=False, min_len=4, max_len=32, wordlist_path=None, refresh=False,
|
|
11
|
+
):
|
|
12
|
+
word_pool = extract_word_pool(wordlist_path, refresh)
|
|
13
|
+
if word_pool:
|
|
14
|
+
print(f"[*] Extracted {len(word_pool):,} common words as pattern tokens", file=sys.stderr)
|
|
15
|
+
|
|
16
|
+
pools = build_token_pools(
|
|
17
|
+
username=username,
|
|
18
|
+
leet=leet,
|
|
19
|
+
keywords=keywords,
|
|
20
|
+
word_pool=word_pool,
|
|
21
|
+
birth_year=birth_year,
|
|
22
|
+
year_range=year_range,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
for key, values in pools.items():
|
|
26
|
+
if values:
|
|
27
|
+
print(f"[*] {key}: {len(values)} tokens", file=sys.stderr)
|
|
28
|
+
|
|
29
|
+
passwords = list(generate_all(pools, min_len, max_len))
|
|
30
|
+
passwords.sort()
|
|
31
|
+
|
|
32
|
+
return passwords
|
wl/leet.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from itertools import product
|
|
2
|
+
|
|
3
|
+
LEET_MAP = {
|
|
4
|
+
"a": ["4", "@"],
|
|
5
|
+
"e": ["3"],
|
|
6
|
+
"i": ["1", "!"],
|
|
7
|
+
"o": ["0"],
|
|
8
|
+
"s": ["5", "$"],
|
|
9
|
+
"t": ["7"],
|
|
10
|
+
"b": ["8"],
|
|
11
|
+
"g": ["9"],
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def apply_leet(text):
|
|
16
|
+
if not text:
|
|
17
|
+
return []
|
|
18
|
+
|
|
19
|
+
text_lower = text.lower()
|
|
20
|
+
positions = []
|
|
21
|
+
for i, c in enumerate(text_lower):
|
|
22
|
+
if c in LEET_MAP:
|
|
23
|
+
positions.append((i, c))
|
|
24
|
+
|
|
25
|
+
if not positions:
|
|
26
|
+
return [text]
|
|
27
|
+
|
|
28
|
+
if len(positions) > 6:
|
|
29
|
+
positions = positions[:6]
|
|
30
|
+
|
|
31
|
+
results = set()
|
|
32
|
+
substitutions = []
|
|
33
|
+
for _idx, char in positions:
|
|
34
|
+
substitutions.append(LEET_MAP[char])
|
|
35
|
+
|
|
36
|
+
for combo in product(*substitutions):
|
|
37
|
+
chars = list(text)
|
|
38
|
+
for (idx, _), sub in zip(positions, combo):
|
|
39
|
+
chars[idx] = sub
|
|
40
|
+
results.add("".join(chars))
|
|
41
|
+
|
|
42
|
+
return list(results)
|
wl/patterns.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from itertools import product
|
|
3
|
+
|
|
4
|
+
TOKEN_RE = re.compile(r"\{(\w+)\}")
|
|
5
|
+
|
|
6
|
+
PATTERNS = [
|
|
7
|
+
"{name}{digits}",
|
|
8
|
+
"{name}{special}",
|
|
9
|
+
"{name}{digits}{special}",
|
|
10
|
+
"{Name}{digits}",
|
|
11
|
+
"{Name}{special}",
|
|
12
|
+
"{name}{year}",
|
|
13
|
+
"{word}{digits}",
|
|
14
|
+
"{digits}{name}",
|
|
15
|
+
"{name}{special}{digits}",
|
|
16
|
+
"{l33t}{digits}",
|
|
17
|
+
"{name}{word}",
|
|
18
|
+
"{word}{name}",
|
|
19
|
+
"{Name}{year}",
|
|
20
|
+
"{name}{special}{year}",
|
|
21
|
+
"{name}{keyword}",
|
|
22
|
+
"{keyword}{name}",
|
|
23
|
+
"{Keyword}{digits}",
|
|
24
|
+
"{name}{special}{keyword}",
|
|
25
|
+
"{keyword}{special}{digits}",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def generate_for_pattern(pattern, pools, min_len, max_len):
|
|
30
|
+
tokens = TOKEN_RE.findall(pattern)
|
|
31
|
+
|
|
32
|
+
token_lists = []
|
|
33
|
+
for t in tokens:
|
|
34
|
+
vals = pools.get(t)
|
|
35
|
+
if not vals:
|
|
36
|
+
return
|
|
37
|
+
token_lists.append(vals)
|
|
38
|
+
|
|
39
|
+
for combo in product(*token_lists):
|
|
40
|
+
pwd = pattern.format(**dict(zip(tokens, combo)))
|
|
41
|
+
if min_len <= len(pwd) <= max_len:
|
|
42
|
+
yield pwd
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def generate_all(pools, min_len=4, max_len=32):
|
|
46
|
+
seen = set()
|
|
47
|
+
for pattern in PATTERNS:
|
|
48
|
+
for pwd in generate_for_pattern(pattern, pools, min_len, max_len):
|
|
49
|
+
if pwd not in seen:
|
|
50
|
+
seen.add(pwd)
|
|
51
|
+
yield pwd
|
wl/tokens.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from .leet import apply_leet
|
|
2
|
+
|
|
3
|
+
COMMON_DIGITS = [
|
|
4
|
+
"00", "000", "0000", "007", "069", "123", "234", "345",
|
|
5
|
+
"456", "567", "678", "789", "69", "88", "420", "666",
|
|
6
|
+
"777", "999", "111", "222", "333",
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_name_variants(username):
|
|
11
|
+
variants = set()
|
|
12
|
+
u = username
|
|
13
|
+
|
|
14
|
+
variants.add(u)
|
|
15
|
+
variants.add(u.lower())
|
|
16
|
+
variants.add(u.upper())
|
|
17
|
+
variants.add(u.capitalize())
|
|
18
|
+
|
|
19
|
+
rev = u[::-1]
|
|
20
|
+
if len(rev) >= 3:
|
|
21
|
+
variants.add(rev)
|
|
22
|
+
variants.add(rev.capitalize())
|
|
23
|
+
|
|
24
|
+
max_trunc = min(5, len(u))
|
|
25
|
+
for i in range(3, max_trunc + 1):
|
|
26
|
+
variants.add(u[:i].lower())
|
|
27
|
+
variants.add(u[-i:].lower())
|
|
28
|
+
|
|
29
|
+
if len(u) >= 3:
|
|
30
|
+
variants.add(u.lower() * 2)
|
|
31
|
+
|
|
32
|
+
return [v for v in variants if v]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def build_token_pools(
|
|
36
|
+
username, leet=False, keywords=None,
|
|
37
|
+
word_pool=None, birth_year=None, year_range=None,
|
|
38
|
+
):
|
|
39
|
+
name_variants = build_name_variants(username)
|
|
40
|
+
|
|
41
|
+
pools = {}
|
|
42
|
+
|
|
43
|
+
pools["name"] = sorted(set(v.lower() for v in name_variants if v))
|
|
44
|
+
|
|
45
|
+
pools["Name"] = sorted(set(
|
|
46
|
+
v[0].upper() + v[1:].lower() if len(v) > 1 else v.upper()
|
|
47
|
+
for v in name_variants if v
|
|
48
|
+
))
|
|
49
|
+
|
|
50
|
+
pools["NAME"] = sorted(set(v.upper() for v in name_variants if v))
|
|
51
|
+
|
|
52
|
+
if leet:
|
|
53
|
+
l33t_set = set()
|
|
54
|
+
for v in name_variants:
|
|
55
|
+
l33t_set.update(apply_leet(v))
|
|
56
|
+
pools["l33t"] = sorted(l33t_set)
|
|
57
|
+
else:
|
|
58
|
+
pools["l33t"] = []
|
|
59
|
+
|
|
60
|
+
digits = set()
|
|
61
|
+
for d in range(10):
|
|
62
|
+
digits.add(str(d))
|
|
63
|
+
for d in range(10, 100):
|
|
64
|
+
digits.add(str(d))
|
|
65
|
+
for seq in COMMON_DIGITS:
|
|
66
|
+
digits.add(seq)
|
|
67
|
+
|
|
68
|
+
if birth_year is not None:
|
|
69
|
+
digits.add(str(birth_year))
|
|
70
|
+
if year_range:
|
|
71
|
+
for y in range(year_range[0], year_range[1] + 1):
|
|
72
|
+
digits.add(str(y))
|
|
73
|
+
|
|
74
|
+
pools["digits"] = sorted(digits, key=lambda x: (len(x), x))
|
|
75
|
+
|
|
76
|
+
pools["special"] = ["!", "@", "#", "$", "%", "&", "*"]
|
|
77
|
+
|
|
78
|
+
years = set()
|
|
79
|
+
if birth_year is not None:
|
|
80
|
+
years.add(str(birth_year))
|
|
81
|
+
if year_range:
|
|
82
|
+
for y in range(year_range[0], year_range[1] + 1):
|
|
83
|
+
years.add(str(y))
|
|
84
|
+
pools["year"] = sorted(years, key=lambda x: int(x))
|
|
85
|
+
|
|
86
|
+
if word_pool:
|
|
87
|
+
pools["word"] = word_pool[:500]
|
|
88
|
+
else:
|
|
89
|
+
pools["word"] = []
|
|
90
|
+
|
|
91
|
+
kw = [k.lower() for k in (keywords or []) if k]
|
|
92
|
+
pools["keyword"] = sorted(set(kw))
|
|
93
|
+
pools["Keyword"] = sorted(set(k.capitalize() for k in kw))
|
|
94
|
+
pools["KEYWORD"] = sorted(set(k.upper() for k in kw))
|
|
95
|
+
|
|
96
|
+
return pools
|