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
-
).
- A pass phrase: a sequence of words, separated by ‘-’. Example:
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:
- I need to read the dictionary from a file.
- Select given number of words from the dictionary.
- Capitalize some words.
- Number of capitalized words must not exceed the total number of words in the pass phrase.
- 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.