Writing my own password generator

There are many password generators available: online and offline, as independent applications or as part of password managers. But I decided to write my own password generator in Python. Why?

  • It is a fun and small Python project, which can be done in one evening.
  • It will do only what I need and nothing else.
  • I know how it works and I can trust it.
  • I can use my own dictionary of words and phrases to generate passwords.

So, let’s go!

If you don’t want to read the whole article, you can go directly to the GitHub repository.

Requirements

  • It must generate:
    • A pass phrase: a sequence of words, separated by ‘-’. Example: apple-banana-cherry.
      • The number of words in the pass phrase must be configurable.
      • Some words can start with a capital letter.
      • Some words can have a special character at the end.
      • I can use my own dictionary of words. Contrary to all other password generators, which use a dictionary of English words, I can use my own dictionary of words and phrases of any language (in original characters or transliterated).
    • A pass string: a sequence of low case characters, which can be split in fixed length groups. Example: abcde-fghij-klmno.
      • The number of groups and each group length must be configurable.
      • Delimiter between groups must be configurable. (Default is -).

Building

String generation

String generation function is the easiest one.

Python has random module which contains choice() function. This function returns a random element from the non-empty sequence. I am using it to generate a random string of a given length.

Because I want to select a random letter from the alphabet, I need to define the alphabet first. I could do it a hard, straightforward way and define it as a list like this: alphabet = ["a", "b", ..., "z"]. Luckily, Python has already a standard method of the string class. string.ascii_lowercase returns a string containing all ASCII lowercase letters.

Now bring it all together in one function:

 1from random import choice
 2
 3def get_random_string(length: int, delimiter: str = "-", split_step: int = 4) -> str:
 4    if length == 0:
 5        return ""
 6    letters = string.ascii_lowercase
 7    result_str = "".join(choice(letters) for _ in range(length))
 8    if split_step == 0:
 9        return result_str
10    result_str = delimiter.join(
11        [result_str[i : i + split_step] for i in range(0, len(result_str), split_step)]
12    )
13    return result_str

In this function, I use the join() function to concatenate all the random letters into one string. And to inject a delimiter between groups of letters, I use the join() function again, but this time with a list comprehension.

Pass phrase generation

This task is a bit more complicated. I need to generate a sequence of words from a dictionary. I must therefore do the following:

  1. I need to read the dictionary from a file.
  2. Select given number of words from the dictionary.
  3. Capitalize some words.
    • Number of capitalized words must not exceed the total number of words in the pass phrase.
  4. Add a special character to some words.
    • Number of words with a special character must not exceed the total number of words in the pass phrase.

I started with helper functions first.

Capitalization of the first letter in a word

This function is easy and does not require any adidtional modules.

1def capitalize_first_letter(word: str) -> str:
2    return word[0].upper() + word[1:]

Adding a special character to the end of a word

This function is also easy and does not require any additional modules. But I have to set a list of special characters inside it.

1def append_special_symbol(word: str) -> str:
2    special_symbols = "!@#$%^&*"
3    return word + choice(list(special_symbols))

Reading a dictionary from a file

Dictionary is a simple text file with words separated by a new line. In the repositiory I have a sample dictionary file dict.txt which contains list of English words which are longer than 3 characters and not longer than 12 characters. File encoding is UTF-8.

To open file and read all words from it, I use the recommended with statement.

1from pathlib import Path
2
3with open(
4    Path(__file__).parent.joinpath("dict.txt"), "r", encoding="utf-8"
5) as file:
6    words_massive = file.readlines()

As you can see, to set an absolute path to the dictionary file, I use Path(__file__).parent.joinpath("dict.txt"). Path is a Python class from the standard pathlib module. Because my password generation Python script is in the same directory as the dictionary file, I can use __file__ to get the path to the script file.

Selecting a random word from the dictionary

To selec a random word from the dictionary, I use the choice() function from the random module. Because dictionary also contains some capitalized words, I put every selected word to the lower case. I also apply the strip() function to remove the newline character from the end of the word.

1select_words = []
2i = 0
3while i < number_of_words:
4    selected_word = choice(words_massive).strip().lower()
5    if selected_word not in select_words:
6        select_words.append(selected_word)
7        i += 1

Capitalize some words

To capitalize some words, I select a number of words from the generated above select_words list, and use already defined capitalize_first_letter() function to them.

1capitalized_words = []
2i = 0
3while i < number_of_words_to_capitalize:
4    word = choice(select_words)
5    if capitalize_first_letter(word) not in capitalized_words:
6        select_words.remove(word)
7        select_words.append(capitalize_first_letter(word))
8        capitalized_words.append(capitalize_first_letter(word))
9        i += 1

Add a special character to some words

The same approach is used to add a special character to some words.

1words_with_special_symbols = []
2i = 0
3while i < number_of_words_to_special_symbols:
4    word = choice(select_words)
5    if append_special_symbol(word) not in words_with_special_symbols:
6        select_words.remove(word)
7        select_words.append(append_special_symbol(word))
8        words_with_special_symbols.append(append_special_symbol(word))
9        i += 1

Putting it all together

Now I can put all the above steps together in one function.

 1from itertools import permutations
 2from random import choice
 3
 4def make_pass_phrase(
 5    number_of_words: int,
 6    number_of_words_to_capitalize: int,
 7    number_of_words_to_special_symbols: int,
 8) -> str:
 9    with open(
10        Path(__file__).parent.joinpath("dict.txt"), "r", encoding="utf-8"
11    ) as file:
12        words_massive = file.readlines()
13
14    select_words = []
15    i = 0
16    while i < number_of_words:
17        selected_word = choice(words_massive).strip().lower()
18        if selected_word not in select_words:
19            select_words.append(selected_word)
20            i += 1
21
22    capitalized_words = []
23    i = 0
24    while i < number_of_words_to_capitalize:
25        word = choice(select_words)
26        if capitalize_first_letter(word) not in capitalized_words:
27            select_words.remove(word)
28            select_words.append(capitalize_first_letter(word))
29            capitalized_words.append(capitalize_first_letter(word))
30            i += 1
31
32    words_with_special_symbols = []
33    i = 0
34    while i < number_of_words_to_special_symbols:
35        word = choice(select_words)
36        if append_special_symbol(word) not in words_with_special_symbols:
37            select_words.remove(word)
38            select_words.append(append_special_symbol(word))
39            words_with_special_symbols.append(append_special_symbol(word))
40            i += 1
41
42    return "-".join(choice(list(permutations(select_words))))

As you can see, I use the permutations() function from the itertools module to shuffle the words in the pass phrase.

Use examples

Pass phrase generation

With default parameters:

1$ python3 simple-pass-generator.py phrase
2sickliness-telos-jasey-macrotone-lolly

Pass phrase from 3 words, non-capitalized and 2 special symbols:

1$ python3 simple-pass-generator.py phrase -w 3 -c 0 -s 2
2answerer!-enneahedrons$-murumuru

Pass phrase from 5 words, 3 capitalized and 4 special symbols:

1$ python3 simple-pass-generator.py phrase -w 5 -c 3 -s 4
2sparklingly-Campaigner#-Nonperverse*-Abhorrers@-artisanship@

Random string generation

With default parameters:

1$ python3 simple-pass-generator.py string
2mfnye-rsbmq-ixzdg-zewcs-ipyfy

String with 8 blocks, 5 characters each:

1$ python3 simple-pass-generator.py string -b 8
2zrhhj-wejyh-ykzxl-bsmzg-ndvky-vcekj-xqkpi-wxjws

String with 5 blocks (default), 6 characters each and # delimiter:

1$ python3 simple-pass-generator.py string -b 5 -l 6 -d "#"
2xktprb#sfybqt#locgdd#hwfyqr#fayooi

Complete code

Script also has arppropriate command line arguments parsing and processing of the input switches/options. You can find the complete code with all pieces put together in the GitHub repository.

Conclusion

Using default Python modules, it is not hard to write my own pass phrase and pass string generator. I trust this tool and I use it on all my Linux machines.

If you want - you can use it too, modify it, add your own dictionary of words and phrases.