Skip to content

Commit 781b66d

Browse files
authored
PR #37: Password Checker
Password Checker Merge pull request #37 from iamwatchdogs/password-checker
2 parents ba8cf53 + 39a343e commit 781b66d

File tree

2 files changed

+327
-0
lines changed

2 files changed

+327
-0
lines changed

Password-Checker/README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Password-Checker
2+
3+
Password-Checker is a simple script that checks the strength of a given password and provides suggestions to improve its security. It also suggests a better password based on the given password.
4+
5+
You can either provide your input as a command line argument or interactively through the terminal _(but it's always recommended to use interactive session on console)_. This is a basic script that perform minimum basic checks and suggestion.
6+
7+
> [!IMPORTANT]
8+
> Since this script serves as a foundational example, it may not be fully suitable for real-world use cases yet. However, its purpose is to establish a solid groundwork for more advanced versions. As the author, I look forward to seeing further improvements and refactoring that will enhance this script to meet real-world requirements.
9+
10+
## Requirements
11+
12+
The only requirement for running this script in your local system is Python 3.6 or above. No external dependencies are required.
13+
14+
## Usage
15+
16+
### For *unix-based systems
17+
To use this script, run the following command:
18+
19+
```bash
20+
curl -s https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Password-Checker/check-password.py | python
21+
```
22+
23+
or
24+
25+
```bash
26+
wget -qO- https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Password-Checker/check-password.py | python
27+
```
28+
29+
or you can download the file from GitHub and then run the script by giving permission to execute the file as shown below:
30+
31+
```bash
32+
# Downloading the script
33+
wget https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Password-Checker/check-password.py
34+
35+
# --- OR ---
36+
# curl -o script.py https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Password-Checker/check-password.py
37+
# ----------
38+
39+
# Giving permission to execute the file
40+
chmod +x check-password.py
41+
42+
# Running the script
43+
./check-password.py
44+
```
45+
46+
### For Windows
47+
48+
Usually, powershell in Window 10 or later version consist of `curl` binary, so you can do that same thing as shown above. But in case it doesn't work, you can use the following command:
49+
50+
```powershell
51+
Invoke-WebRequest https://raw.githubusercontent.com/Grow-with-Open-Source/Python-Projects/main/Password-Checker/check-password.py -OutFile "$env:TEMP\temp_script.py"
52+
python "$env:TEMP\temp_script.py"
53+
```
54+
55+
If you want to save the script for later usage, then it's best recommended to download the script in a desired location and run the script using python interpreter.
56+
57+
## Contributing
58+
59+
Please make sure you have used it this script before you start contributing, and then please go through the [Contributing Guidelines](https://github.com/Grow-with-Open-Source/Python-Projects/blob/main/CONTRIBUTING.md) to make your contribution.
60+
61+
> [!NOTE]
62+
> Since this mini-project was meant to be a sample groundwork for more advancements, add your changes and contributions into the following [Change Log](#change-log) in the given format.
63+
64+
## Change Log
65+
66+
- PR [#37](https://github.com/Grow-with-Open-Source/Python-Projects/pull/37): Created the basic script with minimum features.
67+
68+
## License
69+
70+
This project is released under the [Apache License 2.0](https://github.com/Grow-with-Open-Source/Python-Projects/blob/main/LICENSE).

Password-Checker/check-password.py

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
#!/usr/bin/env python
2+
import re
3+
import sys
4+
import random
5+
from getpass import getpass
6+
7+
# ANSI escape codes for colors
8+
COLOR = {
9+
"RED": '\033[91m',
10+
"YELLOW": '\033[93m',
11+
"GREEN": '\033[92m',
12+
"BLUE": '\033[94m',
13+
"RESET": '\033[0m'
14+
}
15+
16+
KEYBOARD_PATTERNS = ['qwerty', 'asdfgh', 'zxcvbn']
17+
COMMON_SUBSTITUTIONS = {
18+
'@': 'a', '4': 'a', '3': 'e', '0': 'o',
19+
'1': 'i', '$': 's', '7': 't'
20+
}
21+
22+
COMMON_WORDS = {
23+
'adjectives': ['Happy', 'Clever', 'Swift', 'Brave', 'Bright'],
24+
'nouns': ['Tiger', 'River', 'Mountain', 'Storm', 'Star'],
25+
'numbers': ['365', '42', '777', '314', '999'],
26+
'separators': ['_', '.', '#', '*', '@']
27+
}
28+
29+
PATTERNS = {
30+
'uppercase': re.compile(r'[A-Z]'),
31+
'lowercase': re.compile(r'[a-z]'),
32+
'numbers': re.compile(r'\d'),
33+
'special': re.compile(r'[!@#$%^&*(),.?":{}|<>]')
34+
}
35+
36+
37+
def format_to_header(
38+
msg: str,
39+
*,
40+
rep: float = 1,
41+
new_line_at_end: bool = False,
42+
is_main_header: bool = False):
43+
if not isinstance(msg, str):
44+
raise TypeError("msg must be a string")
45+
46+
res_str = "\n"
47+
no_of_hypens = int(len(msg)*rep)
48+
49+
if is_main_header:
50+
no_of_hypens += 4
51+
msg = f"| {msg.upper()} |"
52+
53+
header_str = [
54+
'-'*no_of_hypens,
55+
msg,
56+
'-'*no_of_hypens,
57+
]
58+
59+
res_str += "\n".join(header_str)
60+
if new_line_at_end:
61+
res_str += "\n"
62+
return res_str
63+
64+
65+
def check_password_strength(password):
66+
score = 0
67+
suggestions = []
68+
69+
# Check length
70+
if len(password) < 12:
71+
suggestions.append("Password should be at least 12 characters long.")
72+
elif len(password) >= 16:
73+
score += 2
74+
else:
75+
score += 1
76+
77+
# Check for uppercase
78+
if not PATTERNS['uppercase'].search(password):
79+
suggestions.append("Add uppercase letters.")
80+
else:
81+
score += 1
82+
83+
# Check for lowercase
84+
if not PATTERNS['lowercase'].search(password):
85+
suggestions.append("Add lowercase letters.")
86+
else:
87+
score += 1
88+
89+
# Check for numbers
90+
if not PATTERNS['numbers'].search(password):
91+
suggestions.append("Add numbers.")
92+
else:
93+
score += 1
94+
95+
# Check for special characters
96+
if not PATTERNS['special'].search(password):
97+
suggestions.append("Add special characters.")
98+
else:
99+
score += 1
100+
101+
# Check for repeated patterns (like 'testtest')
102+
half_length = len(password) // 2
103+
for i in range(2, half_length + 1):
104+
if password[:i] * (len(password) // i) == password[:len(password) // i * i]:
105+
suggestions.append("Avoid repeating patterns in your password.")
106+
score -= 1
107+
break
108+
109+
# Check for keyboard patterns
110+
lower_pass = password.lower()
111+
for pattern in KEYBOARD_PATTERNS:
112+
if pattern in lower_pass:
113+
suggestions.append("Avoid common keyboard patterns")
114+
score -= 1
115+
break
116+
117+
# Check for simple character substitutions
118+
substituted = password.lower()
119+
for k, v in COMMON_SUBSTITUTIONS.items():
120+
substituted = substituted.replace(k, v)
121+
if substituted.isalpha() and len(substituted) > 3:
122+
suggestions.append(
123+
"Using symbol substitutions (like '@' for 'a') isn't very secure.")
124+
score -= 1
125+
126+
# Ensure score doesn't go below 0
127+
score = max(0, score)
128+
129+
return score, suggestions
130+
131+
132+
def categorize_password(score):
133+
if score < 2:
134+
return "WEAK", COLOR["RED"]
135+
if score < 4:
136+
return "GOOD", COLOR["YELLOW"]
137+
return "STRONG", COLOR["GREEN"]
138+
139+
140+
def create_memorable_suggestion(base_word):
141+
adj = random.choice(COMMON_WORDS['adjectives'])
142+
noun = random.choice(COMMON_WORDS['nouns'])
143+
num = random.choice(COMMON_WORDS['numbers'])
144+
sep = random.choice(COMMON_WORDS['separators'])
145+
146+
# Use the base word if it's good enough (not too short and has letters)
147+
if len(base_word) >= 4 and any(c.isalpha() for c in base_word):
148+
base = base_word.capitalize()
149+
else:
150+
base = noun
151+
152+
patterns = [
153+
f"{adj}{sep}{base}{num}",
154+
f"{base}{sep}{noun}{num}",
155+
f"{num}{sep}{adj}{base}"
156+
]
157+
158+
return random.choice(patterns)
159+
160+
161+
def suggest_better_password(password):
162+
# If password is very weak, create a completely new memorable one
163+
score, _ = check_password_strength(password)
164+
if score < 2:
165+
return create_memorable_suggestion(password)
166+
167+
suggestion = password
168+
169+
# Smart character substitutions (maintain readability)
170+
smart_subs = {
171+
'a': '@', 'e': '3', 'i': '!', 'o': '0', 's': '$',
172+
'ate': '8', 'to': '2', 'for': '4'
173+
}
174+
175+
# Apply substitutions intelligently
176+
for word, replacement in smart_subs.items():
177+
if word in suggestion.lower() and random.random() < 0.5: # 50% chance
178+
suggestion = suggestion.replace(word, replacement)
179+
180+
# Ensure at least one capital letter in a natural position
181+
if not any(c.isupper() for c in suggestion):
182+
words = suggestion.split()
183+
if words:
184+
words[0] = words[0].capitalize()
185+
suggestion = ''.join(words)
186+
187+
# Add complexity if needed while keeping it memorable
188+
if len(suggestion) < 12:
189+
suggestion += random.choice(COMMON_WORDS['numbers'])
190+
191+
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', suggestion):
192+
suggestion += random.choice(COMMON_WORDS['separators'])
193+
194+
return suggestion
195+
196+
197+
def input_handler():
198+
if len(sys.argv) > 1:
199+
password = sys.argv[1]
200+
print(
201+
f"{COLOR['RED']}It is recommended to avoid entering passwords directly on the command line,{COLOR['RESET']}")
202+
print(
203+
f"{COLOR['RED']}as they may be visible to others and recorded in the shell history.{COLOR['RESET']}")
204+
return password
205+
print(
206+
format_to_header(
207+
"Password Strength Checker",
208+
new_line_at_end=True,
209+
is_main_header=True
210+
)
211+
)
212+
print("For enhanced security, your input will be hidden.")
213+
print("Hence, you may not see the characters as you type.")
214+
try:
215+
password = getpass("\nEnter password to check: ")
216+
except KeyboardInterrupt:
217+
print("\nExiting...")
218+
sys.exit(0)
219+
return password
220+
221+
222+
def output_handler(password, category, color, suggestions):
223+
print(f"\nPassword Strength: {color}{category}{COLOR['RESET']}")
224+
225+
if suggestions:
226+
print(format_to_header("Suggestions to improve:"))
227+
for suggestion in suggestions:
228+
print(f"{COLOR['BLUE']}- {suggestion}{COLOR['RESET']}")
229+
230+
# Add this block to show suggested password
231+
if category != "STRONG":
232+
better_password = suggest_better_password(password)
233+
print(
234+
f"\nSuggested stronger password: {COLOR['GREEN']}{better_password}{COLOR['RESET']}")
235+
236+
points_to_remember = [
237+
"Never use your personal information while creating a password.",
238+
"Consider using a passphrase made up of multiple words for better security.",
239+
"Avoid using common phrases or easily guessable patterns.",
240+
"Avoid using the same password for multiple accounts.",
241+
"Regularly update your passwords to enhance security.",
242+
"Use a reputable password manager to generate and store complex passwords securely."
243+
]
244+
print(format_to_header('Points to Remember:'))
245+
for points in points_to_remember:
246+
print(f"{COLOR['BLUE']}- {points}{COLOR['RESET']}")
247+
248+
249+
def main():
250+
password = input_handler()
251+
score, suggestions = check_password_strength(password)
252+
category, color = categorize_password(score)
253+
output_handler(password, category, color, suggestions)
254+
255+
256+
if __name__ == "__main__":
257+
main()

0 commit comments

Comments
 (0)