Building a Password Generator in Python: A Step-by-Step Guide

Arun Chahar
5 min readMay 31, 2024

--

Introduction

In this tutorial, we will create a `PasswordGenerator` class in Python that generates random passwords based on specified length, complexity, and periodicity. By the end of this tutorial, you will have a solid understanding of how to create and test a password generator in Python.

Requirements

1. Length: The length of the generated password should be between 8 and 32 characters.
2. Complexity: The complexity of the password can be:
Easy (lowercase letters only)
Medium (uppercase and lowercase letters)
Hard (uppercase, lowercase letters, and digits)
Extreme (uppercase, lowercase letters, digits, and punctuation)
3. Periodicity: A boolean indicating whether the password should have repeated substrings (True) or not (False).

Step 1: Create the `PasswordGenerator` Class

Directory Structure of Repository

First, let’s create our `PasswordGenerator` class in a file named `password_generator.py`.

import random
import string
from typing import List

class PasswordGenerator:
"""
A class to generate random passwords based on specified length, complexity, and periodicity.

Attributes:
-----------
length : int
The length of the generated password (between 8 and 32).
complexity : str
The complexity of the generated password ('easy', 'medium', 'hard', 'extreme').
periodicity : bool
If True, the password will contain repeated substrings. If False, the password will not have any repeated substrings.
"""

def __init__(self, length: int, complexity: str, periodicity: bool):
if not 8 <= length <= 32:
raise ValueError("Length must be between 8 and 32 characters.")
if complexity not in ('easy', 'medium', 'hard', 'extreme'):
raise ValueError("Complexity must be 'easy', 'medium', 'hard', or 'extreme'.")

self.length = length
self.complexity = complexity
self.periodicity = periodicity

def generate(self) -> str:
"""
Generate a random password based on the specified length, complexity, and periodicity.

Returns:
--------
str
The generated password.
"""
char_sets = {
'easy': string.ascii_lowercase,
'medium': string.ascii_letters,
'hard': string.ascii_letters + string.digits,
'extreme': string.ascii_letters + string.digits + string.punctuation
}

chars = char_sets[self.complexity]

if self.periodicity:
password = self._generate_with_periodicity(chars)
else:
password = self._generate_without_periodicity(chars)

return password

def _generate_with_periodicity(self, chars: str) -> str:
"""
Generate a password with repeated substrings.

Parameters:
-----------
chars : str
The characters to use for generating the password.

Returns:
--------
str
The generated password with repeated substrings.
"""
repeat_length = random.randint(2, max(2, self.length // 4))
repeat_segment = ''.join(random.choice(chars) for _ in range(repeat_length))
repeats = self.length // repeat_length
remainder = self.length % repeat_length

password = repeat_segment * repeats + ''.join(random.choice(chars) for _ in range(remainder))
return ''.join(random.sample(password, len(password))) # Shuffle to mix the pattern

def _generate_without_periodicity(self, chars: str) -> str:
"""
Generate a password without repeated substrings.

Parameters:
-----------
chars : str
The characters to use for generating the password.

Returns:
--------
str
The generated password without repeated substrings.
"""
password = ''.join(random.choice(chars) for _ in range(self.length))
while self._has_repeated_substring(password):
password = ''.join(random.choice(chars) for _ in range(self.length))
return password

def _has_repeated_substring(self, s: str) -> bool:
"""
Check if a string contains repeated substrings.

Parameters:
-----------
s : str
The string to check for repeated substrings.

Returns:
--------
bool
True if the string contains repeated substrings, False otherwise.
"""
n = len(s)
for i in range(1, n // 2 + 1):
seen = set()
for j in range(n - i + 1):
substring = s[j:j + i]
if substring in seen:
return True
seen.add(substring)
return False

if __name__ == "__main__":
# Generating Password
pg = PasswordGenerator(16, 'hard', False)
print("Generated Password:", pg.generate())

Explanation

  • Constructor (`__init__` method): Initializes the class with `length`, `complexity`, and `periodicity` parameters. It validates that the length is between 8 and 32 and the complexity is one of the allowed values.
    - Generate Method (`generate`): This method selects the appropriate character set based on the complexity and generates a password using either periodic or non-periodic methods.
    - Periodicity Methods: The `_generate_with_periodicity` method creates a password with repeated substrings, while `_generate_without_periodicity` ensures no repeated substrings by regenerating the password until it meets the criteria.
    - Check for Repeated Substrings (`_has_repeated_substring`): This helper method checks if a string contains repeated substrings by using a sliding window approach and a set to track seen substrings.
Generated Password

Step 2: Writing Tests

Next, let’s write tests to ensure our password generator works correctly. Create a file named `test_password_generator.py` and add the following code:

import unittest
from password_generator import PasswordGenerator

class TestPasswordGenerator(unittest.TestCase):
def test_easy_complexity(self):
pg_easy = PasswordGenerator(10, 'easy', False)
password = pg_easy.generate()
self.assertEqual(len(password), 10)
self.assertTrue(all(c in "abcdefghijklmnopqrstuvwxyz" for c in password))

def test_medium_complexity(self):
pg_medium = PasswordGenerator(15, 'medium', False)
password = pg_medium.generate()
self.assertEqual(len(password), 15)
self.assertTrue(all(c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" for c in password))

def test_hard_complexity(self):
pg_hard = PasswordGenerator(20, 'hard', True)
password = pg_hard.generate()
self.assertEqual(len(password), 20)
self.assertTrue(all(c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" for c in password))

def test_extreme_complexity(self):
pg_extreme = PasswordGenerator(25, 'extreme', True)
password = pg_extreme.generate()
self.assertEqual(len(password), 25)
self.assertTrue(all(c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" for c in password))

def test_periodicity(self):
pg_periodic = PasswordGenerator(16, 'medium', True)
password = pg_periodic.generate()
# Since it is periodic, it should have a repeated substring
self.assertTrue(any(password[i:i+2] in password for i in range(len(password)-2)))

def test_no_periodicity(self):
pg_non_periodic = PasswordGenerator(16, 'medium', False)
password = pg_non_periodic.generate()
# Since it is non-periodic, it should not have any repeated substring
self.assertFalse(any(password[i:i+2] in password[i+2:] for i in range(len(password)-2)))

if __name__ == '__main__':
unittest.main()

Explanation

- Testing Each Complexity: We test each complexity level (easy, medium, hard, extreme) to ensure the generated password meets the required criteria.
- Testing Periodicity: We test both periodic and non-periodic passwords to verify that the generated passwords follow the specified periodicity rules.
- Assertions: We use assertions to check the length of the password and the presence of appropriate characters based on the complexity.

Step 3: Running the Tests

To run the tests, open a terminal in your development environment and navigate to the directory containing your files. Then, run the following command:

python -m unittest test_password_generator.py

If all tests pass, you should see output similar to this:

All tests passed for test_password_generator.py

Conclusion

By following this guide, you have learned how to create a `PasswordGenerator` class in Python using a test-driven development approach. This class generates random passwords based on specified length, complexity, and periodicity, ensuring they meet security requirements for various use cases.

Through this process, we:
1. Defined Requirements: Established the need for a password generator with customizable length, complexity, and periodicity.
2. Implemented the Password Generator: Developed the `PasswordGenerator` class with methods to generate passwords with and without repeated substrings.
3. Wrote Tests: Created test cases to validate our class, ensuring it behaves correctly under different scenarios.
4. Executed Tests: Ran the tests to verify that our implementation meets the specified requirements.

Suggested Enhancements

Here are a few ideas to further improve the `PasswordGenerator` class:

1. Custom Character Sets: Allow users to define custom character sets for generating passwords.
2. Advanced Periodicity Options: Provide more granular control over the types and lengths of repeated substrings.
3. User Interface: Create a simple command-line interface (CLI) or graphical user interface (GUI) for easier usage.
4. Password Strength Indicator: Implement a method to assess and display the strength of the generated password.

Powered by ChatGPT

--

--