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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ wl = wl.cli:main
@@ -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
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ main()
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