My goal in creating this project was to create a password generator that was simple enough to be useful as well as sophisticated enough in its random-number generator so as to not be contrived.
I used Boost Program Options for argument parsing. The interface seems extremely easy to use, and the fact that it's both more readable and allows for writing idiomatic C++ seem pretty good reasons to not use getopt.h
in this case.
Using the --passwords
or -n
flags the user may set the number of passwords to generate. Likewise, the --length
or -l
flags set the length of the password. I looked around for a standard list of character symbols to use, but I couldn't find anything like a standard, so my default symbol list is "!@#$``~|\\%^&*()[]-_=+{}';:,<.>/?"
. (Note: the backtick is repeated so StackOverflow's markdown highlights everything properly.) The user may use the --simple-symbols
flag to use only "!@#$&?"
.
The actual random number generation was what I wanted to explore with this project. While researching this question I came across some very similar projects like this one, which used Sodium, but I'm wondering if the method I'm using here is as good, or at least viable.
I used std::random_device to generate my random numbers, along with std::uniform_int_distribution, to ensure as random and uniform an output as possible. I'm looking for a way to check whether std::random_device will work as expected; since it may not be non-deterministic if there is no hardware implementation, I would like to at least warn the user during execution.
Lastly, I wanted to make the alphabets used for password generation as extensible as possible, but after implementing them, it seems a little too much for the benefit gained in return. It theoretically allows a user to not have to use numbers or letters, or whatever, but with the benefit of hindsight this doesn't look like a good feature.
PGEN.hpp
#ifndef PGEN_INCLUDES_PGEN_H_
#define PGEN_INCLUDES_PGEN_H_
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <random>
#include <string>
#include <boost/program_options.hpp>
#endif // PGEN_INCLUDES_PGEN_H_
main.cpp
#include "PGEN.hpp"
namespace Options = boost::program_options;
int main(int argc, char *argv[])
{
/** Program Defaults
*
*/
const auto DefaultPasswordsToCreate = 1;
const auto DefaultPasswordLength = 16;
const std::string DefaultSymbolList = "!@#$`~|\\%^&*()[]-_=+{}';:,<.>/?";
const std::string SimplerSymbolList = "!@#$&?";
/** Program Options
*
*/
bool verbose = false;
/** Password parameters:
*
*/
int N = DefaultPasswordsToCreate;
int PasswordLength = DefaultPasswordLength;
std::string AlphabetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::string AlphabetLower = "abcdefghijklmnopqrstuvwxyz";
std::string AlphabetNumeric = "0123456789";
std::string AlphabetSymbols = DefaultSymbolList;
Options::options_description OptionsDescription("Program Options");
OptionsDescription.add_options()
("help,h", "Display this help message and exit.")
("version", "Print program version information.")
("passwords,n", Options::value<int>(&N)->default_value(DefaultPasswordsToCreate), "Number of passwords to generate.")
("length,l", Options::value<int>(&PasswordLength)->default_value(DefaultPasswordLength), "Length of passwords to generate.")
("symbols", Options::value<std::string>(&AlphabetSymbols)->default_value(DefaultSymbolList), "Pass in custom legal symbols list.")
("simple-symbols", "Use simpler symbol list.")
("verbose,v", "Log additional info to stdout during execution.")
;
Options::variables_map ArgsInput;
Options::store(Options::parse_command_line(argc, argv, OptionsDescription), ArgsInput);
Options::notify(ArgsInput);
if (ArgsInput.count("help"))
{
std::cout << OptionsDescription << std::endl;
return EXIT_SUCCESS;
}
if (ArgsInput.count("version"))
{
std::cout << "[Program Version Information...]" << std::endl;
return EXIT_SUCCESS;
}
if (ArgsInput.count("verbose"))
{
verbose = true;
}
if (ArgsInput.count("symbols"))
{
// TODO: Validate symbol list input.
if (verbose) {
std::cout << "Legal Symbols: " << AlphabetSymbols << std::endl;
}
}
if (ArgsInput.count("simple-symbols"))
{
AlphabetSymbols = SimplerSymbolList;
if (verbose) {
std::cout << "Legal Symbols: " << AlphabetSymbols << std::endl;
}
}
// TODO: Refactor to idiomatic C++.
const auto AlphabetSize = 62 + strlen(AlphabetSymbols.c_str());
char* Alphabet = static_cast<char *>(malloc(sizeof(char) * AlphabetSize));
sprintf(Alphabet, "%s%s%s%s", AlphabetUpper.c_str(), AlphabetLower.c_str(), AlphabetNumeric.c_str(), AlphabetSymbols.c_str());
std::random_device rd;
std::uniform_int_distribution<int> dist(0, AlphabetSize);
for (auto i = 0; i < N; ++i) {
for (auto n = 0; n < PasswordLength; ++n) {
const auto r = dist(rd);
std::cout << Alphabet[r];
}
std::cout << std::endl;
}
return EXIT_SUCCESS;
}
Directory Structure
PGEN
|-- Makefile
|
|-- src
| |_ main.cpp
|
|__ include
|_ PGEN.hpp
Makefile
SHELL = /bin/sh
.SUFFIXES:
.SUFFIXES: .cpp .hpp
vpath %.cpp src
vpath %.hpp include
PROGRAM = pgen
all: $(PROGRAM)
$(PROGRAM): main.o
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -o $@ $^ -lboost_program_options
main.o: main.cpp
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -I include $(TARGET_ARCH) -c -o $@ $^
.PHONY: clean
clean:
rm -rf *.o $(PROGRAM)
To build I usually used the following:
make CXX=g++ CXXFLAGS="-std=c++17 -Wall -Wextra -pedantic"
Example:
$ ./pgen
i=2TBdeDK8E8L%xk
$ ./pgen -n 10 -l 24
zE'S9l(6C3r_F8!V6)[Q!ZiW
-2iJVbW3(4@556vL-@v&naM
96T`-Bl3;MkVmAVHogx]|}X
F~~UJ$tpmrZ2Phf2/WEzlY>$
QLfD5m}29%o#E;\-}MM.QiGb
V'Rwv^\)Rw~5zL5X6a'9{;MJ
RO,m`-K&^G=f/^7IK+@>4@7b
F?fdM6)+.Qcmd<&II;R}1ao
xI6-VCv'ct\g2Da;OfhN^-td
Ayai>)'PQ=T,=zyoEg[Fod`G
$ ./pgen -n -10 -l 24 --simple-symbols
z@?IquGoOw8IhqBJCoat4YM0
qjOXC!1o0ZjfHQezxfHyYe2
4#uyBN5oGoWqbmw!eYTNwW@e
I65bEWLHHg8&3WXhtUXIxX6W
i95SuYP0Ab#d0FUUmpIbGGzk
gOF!EHe1!LeaiP#I?UoD$84v
jPLzBFv2IIqp7iQ$z4@ORW$&
d4eM#NeCaxNf?hGroLBgp&E
QPNZ849rKJnDO$rVhFGkfWWZ
0Ch&IzAQN8PjfUFCMBtVwaVC