This is a review of the 15 modules available on CPAN that could be used to generate a random password. Skip to the conclusion if you just want the recommendation for which module to use.
I wanted the ability to generate random passwords for our service. I turned to CPAN and quickly found Crypt::RandPasswd, which seemed to do the job, and the pod says "random password generator based on FIPS-181". Sounded good. But if you call it repeatedly, it will eventually just sit there chewing up CPU. The pod says, in the Bugs section, "The function to generate a password can sometimes take an extremely long time". Bugger.
Back to CPAN. Here's the hopefully full list of modules on CPAN for generating passwords:
Module | Doc | Version | Author | # bugs | # users | Last update |
---|---|---|---|---|---|---|
App::Genpass | CPAN | 2.32 | Sawyer X | 0 | 1 | 2012-06-30 |
Crypt::GeneratePassword | CPAN | 0.03 | Jörg Walter | 2 | 1 | 2003-09-09 |
Crypt::PassGen | CPAN | 0.05 | Tim Jenness | 1 | 1 | 2008-03-26 |
Crypt::PW44 | CPAN | 0.13 | Brian Dickson | 4 | 0 | 2011-12-29 |
Crypt::RandPasswd | CPAN | 0.02 | John Douglas Porter | 1 | 1 | 2000-07-21 |
Crypt::XkcdPassword | CPAN | 0.004 | Toby Inkster | 0 | 0 | 2012-07-05 |
Crypt::YAPassGen | CPAN | 0.02 | Giulio Motta | 0 | 0 | 2004-05-30 |
Data::Random | CPAN | 0.07 | Buddy Burden | 0 | 14 | 2012-06-02 |
Data::Random::String | CPAN | 0.03 | Makis Marmaridis | 1 | 0 | 2007-01-05 |
Data::SimplePassword | CPAN | 0.07 | Ryo Okamoto | 0 | 3 | 2011-02-23 |
Session::Token | CPAN | 0.82 | Doug Hoyte | 1 | 0 | 2012-07-21 |
String::MkPasswd | CPAN | 0.03 | Chris Grau | 0 | 1 | 2010-10-18 |
String::Random | CPAN | 0.22 | Steven Pritchard | 1 | 23 | 2006-09-21 |
String::Urandom | CPAN | 0.16 | Marc S. Brooks | 0 | 0 | 2011-05-03 |
Text::Password::Pronounceable | CPAN | 0.30 | Thomas Sibley | 1 | 7 | 2010-08-16 |
Crikey! Classic CPAN: 15 modules in 6 different namespaces. I had to do multiple searches to compile that list, and I still missed 6 the first time round. Which one is "best"? A search on "random password generation perl" will also turn up plenty more scripts, one-liners, etc. I decided to evaluate all CPAN modules and write this, to save other people time.
In theory there are broadly two types of password generator: one which generates a random sequence of characters, and the other which generates something which looks like a word, but isn't, making it easier to remember. In practice, things aren't so cut and dried: some of the "random sequence" type put more effort into making the password stronger, by ensuring a mix of lower case, upper case, and digits, for example.
Before starting, I wrote down my minimal set of requirements:
As a result of working through them all, I've identified some other factors that might be relevant to you:
First we'll look at each of the modules in turn, and then provide a summary, and recommendations.
This is used to generate passwords which are random sequences of characters. It has a number of options which let you control the characteristics of the generated passwords. Simplest usage:
use App::Genpass; $generator = App::Genpass->new(); $password = $generator->generate();
This will generate a ten-character password.
You can pass a number of parameters to the constructor:
$generator = App::Genpass->new( number => 1, readable => 1, special => 0, verify => 1, minlength => 8, maxlength => 12, );
The number parameter says how many passwords should be generated. Setting readable to true will exclude confusable characters such as o, O, 0, 1, l, and I. special enables inclusion of characters like '@', '#', etc. Turning on verify will ensure generated passwords have the different characters wanted.
You can specify minimum and maximum lengths for the generated password, using the minlength and maxlength parameters, which have default values of 8 and 10. You can also request a specific password length, with the length parameter.
The readable and special are mutually exclusive: if you want a readable password then you can't have special characters in it. If you specify both, readability takes precedence.
You can also override the character classes that are used internally: providing lists for lower case letters, upper-case, digits, and special characters. This would presumably let you include things like ß, å, ð, etc.
Ten passwords generated using the settings above:
QysPJ8mL MG3ahxKS w8D3Mq39 gkL6p5Cy 3xFXAGf2 k3REf7Rj V7JkjMb6 T5DFvii4 K3kuV9WD 3nybmAVj
This is still actively maintained: while writing this I emailed Sawyer a number of comments, and he released updates before I'd even completed the first draft.
The distribution includes a genpass script, which gives you command-line access, with control over all the options.
For batch generation of passwords, where pronouncability is not a requirement, this module would be a good choice. I like the readability option, where potentially confusable characters aren't included.
This module provides three functions: word() is used to generate a random pronounceable password using 4-grams; word3() generates a pronounceable password using tri-grams; and chars() generates a password by randomly selecting characters from a specific set. The documentation claims that the approach used in this module is more secure than that used in Crypt::RandPasswd, and refers the interested reader to an article by Ravi Ganesan and Chris Davies.
use Crypt::GeneratePassword qw(word word3 chars); print "word password = '", word(8, 8), "'\n"; print "word3 password = '", word3(8, 8), "'\n"; print "chars password = '", chars(8,8), "'\n";
When calling chars, you can pass an arrayref which provides the set of characters to select from.
When calling word or word3, you can pass a language code. Only 'en' (English) and 'de' (German) are supported, but the module provides helper functions for generating and loading your own language model. The author encourages submissions for additional languages, but it doesn't look like he's had any.
Sample passwords generated by the three functions, with default settings:
word() word3() chars() -------- -------- -------- ddametoo xichoref !+Esvvwe imingadm ahicatru KI_TFO&_ ndirifef ospograr w+qtMk&k giparauc ferworea mbXAXs89 dleroupc tomanisk AWHQg1k- xalephui hrescird O7Hbw_bG efaliops quilenno S9zeAV psulvent awalonta xz_&=loz iteteogo mbilurou wlokXmUx breabosh omengesm =55ZGwAC
The module will also scan generated passwords to remove any potentially offensive sequences. You can provide your own function for this.
The documentation doesn't really explain the different between word() and word3, apart from saying that one uses 3-grams and one uses 4-grams. The functions could be better named, but the module works fine.
This provides a function passgen, which generates random passwords that look like real words. The two arguments you're likely to use specify the number of words to generate, and the number of letters in each password.
use Crypt::PassGen qw/ passgen /; @passwords = passgen( NWORDS => 10, NLETT => 8 );
Here's ten passwords generated with the above line:
phystonp colyphyp costriph salielyt anticous ungonest subsemis cocheleu scionerh pasifica
It works from frequency data (letter, letter bigram and letter trigram) which has been generated from a word list. You can generate your own frequency data using the ingest function, which writes the data to a file. You can then pass the filename to the passgen function.
Crypt::PW44 was inspired by an xkcd cartoon, the basic message of which was that a random password is likely to be hard for a human to remember but relatively easy for a computer to crack. Instead, the cartoon suggests that you construct a password from 4 (say) words, randomly selected from a dictionary. This will take a lot more CPU cycles to find, and probably be easier for a human to remember.
Although the documentation describes it as a simple password generator, the example usage in the SYNOPSIS (for generating a four-word passphrase) is:
use Crypt::PW44 qw(generate); print generate(pack('Ll',int(rand(2**32)),int(rand(2**16))),3), "\n";
Note: this is slightly different from the example in the SYNOPSIS, as that has a syntax error. Running this ten times produces the following:
RIM BURY TIC KURD COCA JURY GWEN HAWK LENS CROW TERM HAP BERG QUOD BAND ROSS OTT ROB GONE AS WAD CALM BACK GOOD FLEW FAY MOTH ROSE MARE HIKE TORE CULL LAC ELLA TOUT BURY JUNE YET IFFY WAYS
The generate function takes two arguments. The first is described as "entropic data", which must be binary and more than N*11 bits in size. The second is N, one less than the number of words to include in the password. Neither of these arguments is very user-friendly. Most people aren't likely to understand what's required for the first argument, so will just copy the synopsis. In which case, drop the argument, or make it optional. And if the user has to specify the number of words to use, let them specify that, not N-1.
Crypt::PW44 has a dictionary of 2048 words of length 1 to 4 characters. Selecting a word from the list requires 11 bits, so a password of four words requires 44 bits, the value used in the cartoon. That's where the 44 in the PW44 name comes from. Given that, you should be able to pass no arguments to the generate function, and it would return a four word passphrase.
The documentation says that the wordlist is from an OPIE dictionary, but doesn't make clear what that is, or where it came from. It does contain some questionable 'words', such as AUG, DEL, HAAG, and TRAG. These should really be cleaned out, given the goal is easy-to-remember passphrases.
This provides three functions:
The module isn't particularly flexible -- it only supports English, and doesn't provide tools for generating your own models -- but the documentation does show how you can override some of the internal functions:
*Crypt::RandPasswd::rng = \&my_random_number_generator;
The following table shows 10 passwords generated by the 3 functions:
word() letters() chars() cofityeb jegewmat fonewvec quocebur ajyumayg exotsjob ajeekyul woadeadi moidskat cocyaudo igbxephb cwyhnaoi wihvbett nqsgkqiq zxfjxmda nkctsfod lmroobjq zpzthgzm cftbjmge vllykqsd Bc2LGx$3 q9O>5gKJ ==LuvlDj ~)`78b/6 ~Q=;7H?m 8:J+?^>D %XA}c~`- Kds:W/XD RJe$t^;" <~`*G:}|
As noted in the introduction, the word function regularly locks up: it will chew up CPU and not return.
Crypt::XkcdPassword was also inspired by the xkcd cartoon that inspired Crypt::PW44. See the description of Crypt::PW44 above for more on the cartoon.
Here's the usage based on creating an instance of the generator:
use Crypt::XkcdPassword; $generator = Crypt::XkcdPassword->new(); print $generator->make_password(4), "\n";
The single argument to make_password specifies the number of words in the password, and defaults to 4. Here are ten passwords generated by running the above:
spots haven't identical reservation lindsay's participate terry bugging rid count plan london lucinda's foods circuit deserved nasty called stuck fights back confessing fucked frightening toys uptight scare replacement ping squared swamped mid tapes handful handful timmy tone freaked westbridge gordon
The constructor supports two parameters:
$generator = Crypt::XkcdPassword->new( words => 'EN', rng => sub { int(rand($_[0])) }, );
The words parameter is used to select the wordlist: it is used to construct a classname, in this case Crypt::XkcdPassword::Words::EN. The distribution also comes with an IT class for Italian.
The rng parameter lets you specify your own function for selecting words at random, so you could use /dev/random or /dev/urandom, for example.
If you don't want to parameterise the generator, and don't want to pass an instance around, you can also call make_password as a class method:
use Crypt::XkcdPassword; print Crypt::XkcdPassword->make_password(4), "\n";
I can see a number of potential problems with this module. Some of these have been partially addressed with a recent release which includes an alternate English word list, EN::Roget.
Another generator for pronounceable passwords. This was created because Giulio couldn't make Crypt::PassGen (above) work with Italian frequency data.
use Crypt::YAPassGen; my $passgen = Crypt::YAPassGen->new(); my $password = $passgen->generate();
As with many others, it works off a file that has frequency information generated from a word list. A method is provided for generating frequency data from your own word list.
The constructor takes optional parameters to control the generation: you can specify the length of password, and whether you only want 7-bit ASCII characters (regardless of the characters seen in the word list). You can also select one of four standard algorithms for generating the password, or roll your own.
Here's 10 passwords from each of the built-in generating algorithms ('sqrt' is the default):
linear sqrt log flat ianistiz tatedrum emulusla feracksc onactimi atersiza cuttermi ialcarde palecons igrovens rtrouchf dyinlieb enollwov nnanchou hodyiked otoosmse curagaea vattedba iciptsio muguidsp kesuntly nreetsbu japudidu sahnensm sayonell osconigs eulceduc ggleorvo aframira lnequais vywagmio viseidor rhigpirn gzagosli chgoinuu erxinsso wflutobi jewfaxts kwolkefc equouich
You can also select one or more post-generation filters, which modify generated passwords before they're returned by the generate method. There are three built-in ("haxor" changes some characters into l33t versions; "caps" will insert some random upper-case characters; "digits" will insert digits. You can also provide your own filter functions.
This is more of a swiss-army knife version than some of the other modules.
This module provides function for generating various types of random string: words, character strings, dates, times, and selecting items from sets. The relevant function for generating a password is rand_chars().
use Data::Random qw(rand_chars); $password = rand_chars(set => 'alphanumeric', size => 8);
The size parameter specifies an exact number of characters desired; you can alternatively pass min and max parameters, and will get back a random number of characters between the limits specified. The set parameter selects which set of characters should be used. Options are:
alpha - alphabetic characters: a-z, A-Z upperalpha - upper case alphabetic characters: A-Z loweralpha - lower case alphabetic characters: a-z numeric - numeric characters: 0-9 alphanumeric - alphanumeric characters: a-z, A-Z, 0-9 char - non-alphanumeric characters: # ~ ! @ $ % ^ & * ( ) _ + = - { } | : " < > ? / . ' ; ] [ \ ` all - all of the above
The following 10 passwords were generated using the example invocation above:
wtXN890S W4c9Z713 GBb5LQ0q aLCzsKqS RHOPlg6b YQVX4nwF SQmhfCdz ZS0rcemJ pF7OTKLo HQpA6iM3
This module is a useful collection of functions for randomly generating data, but rand_chars() doesn't really have the right characteristics for generating passwords.
This module provides a single function for generating strings of a fixed length, with characters randomly selected from alpha, numeric, or alphanumeric. To generate an 8-character alphanumeric password you'd use:
use Data::Random::String; $password = Data::Random::String->create_random_string(length => 8, contains => 'alphanumeric');
I've submitted an RT report, suggesting that the interface be changed so you don't have to invoke the function as a class method.
Ten passwords:
fYSC7Qj2 b5O7IQoR ymezC2VD sxtbTagV ua50HJfd IoBjwww7 QP3qM0hf N8nDDmDc bMRqUNLF 3VSeekNW
This module doesn't add anything over Data::Random, other than being slightly faster.
This is a simple module for generating random character-string passwords:
use Data::SimplePassword; $passgen = Data::SimplePassword->new(); $password = $passgen->make_password(8);
There are only two ways you can configure this. The chars method is use to get/set the set of characters from which the password will be generated. The provider method is used to set the random number generator used (see Crypt::Random::Provider::*).
Again, here are 10 passwords generated using the above code:
ZKHXjrAn gZ8rQ6pv VUZ2FPBg OOcwuaU5 dgbbJD9T Nl3u1aHG bidw7YM1 OR0w49CX jcznAwVd 6Kacde3x
While this scores low on feature set, it scores well for taking random number generation seriously.
Session::Token aims to provide "secure, efficient and simple random session token generation". The following shows how you can use it to generate eight-character passwords:
use Session::Token; $generator = Session::Token->new(length => 8); print $generator->get(), "\n";
Here are ten passwords generated using the above:
2Q2JnqW0 b5vgI07F bEv2s6NX co36UTeL k4xaNwse YD0B9qte LVZaT5MF r2ReFvJo cryalhLP Ph9nUr3x
You can specify the length of password either using the length argument (which specifies a number of characters), or using the entropy argument, which specifies the minimum entropy in number of bits. If using entropy, you must also take into account the number of characters in the alphabet. The default alphabet is [A-Za-z0-9], but you can change this with the alphabet argument, which takes either a string or an array of characters.
The following example shows how you could emulate the readable option from App::Genpass, specifying an alphabet which doesn't contain confusible characters:
use Session::Token; @alphabet = qw( A B C D E F G H J K M N P Q R S T U V W X Y Z a b c d e f g h j k m n p q r s t u v w x y z 2 3 4 5 6 7 8 9 ); $generator = Session::Token->new(alphabet => \@alphabet, entropy => 32); print $generator->get(), "\n";
Session::Token reads from /dev/urandom if available (/dev/random otherwise), to seed the ISAAC random number generator. The documentation provides a thorough description of the underlying algorithm, including how it avoids bias. The SEE ALSO section provides a good list of pointers to similar modules, which brief comparison to Session::Token.
As you can see from the performance comparison below, this is the fastest module, by a comfortable margin.
Another simple module, this exports one function which is used to generate a password:
use String::MkPasswd qw(mkpasswd); $password = mkpasswd();
Here are 10 passwords generated using the above code:
Uu1b<7vzO m9fH[Lk4s 3pTw0@pxL Y9<lz2Qqe 6ksJ)Mbj8 )tdY3rk9X yN3qi1Fb{ h{I3l7Uum 5Jkgb9%yZ w6pQy4Ic&
The function takes a number of optional parameters, which control password generation:
$password = mkpasswd( -length => 9, # number of characters in password -minnum => 2, # min number of digits in password -minlower => 2, # min number of lower case characters -minupper => 2, # min number of upper case characters -minspecial => 2, # min number of special characters -distribute => 1, );
If distribute is true, "password characters will be distributed between the left- and right-hand sides of the keyboard. This makes it more difficult for an onlooker to see the password as it is typed. The default is false". Interesting feature.
This module provides two mechanisms for creating random strings based on a pattern. The documentation mentions password generation as an example, but it probably has uses elsewhere.
The random_string function generates a random string according to a pattern. Each character in the pattern can be one of:
c Any lowercase character [a-z] C Any uppercase character [A-Z] n Any digit [0-9] ! A punctuation character [~`!@$%^&*()-_+={}[]|\:;"'.<>?/#,] . Any of the above s A "salt" character [A-Za-z0-9./] b Any binary data
So to generate an 8-character password made up of salt characters only:
$password = random_string('ssssssss');
You can extend the standard list of pattern characters with your own, which would let you control exactly which characters can appear in passwords.
The random_regex function is similar, but takes a simplified form of regular expression:
\w Alphanumeric + "_". \d Digits. \W Printable characters other than those in \w. \D Printable characters other than those in \d. . Printable characters. [] Character classes. {} Repetition. * Same as {0,}. ? Same as {0,1}. + Same as {1,}.
To generate an 8-character password made up of alphanumeric characters and underscore:
$password = random_regex('\w{8}');
The module also supports an OO style interface:
use String::Random; $random = String::Random->new(); $password = $random->randregex('\w{8}'); $password = $random->randpattern('ssssssss');
Here's the 10 passwords, generated with random_string('ssssssss') and random_regex('\w{8}'):
string regex -------- -------- 8YPfzMzZ @p??%A&m MOjfNgH_ R}8Hq9|5 BL0V3mny .D<;ZIm1 FOHJAnuz 'BOzE6?# UVHh9PB6 (#elP~&{ EtUXo8TP 7bAY2IS? RJKkEYLt fSaO[(;[ zHbc8qtN 'P_F|WAj Ovyz9hbt T~`x`@2p 8hUUTh0j UL3T]+n|
This module gives you a lot of low-level control, but unlike App::Genpass it doesn't provide you with sensible defaults for generating a password. And if you used the 's' pattern as I did above, there's a small chance that you'd generate a dictionary word. A small chance admittedly, but the module isn't doing anything to ensure the quality of the generated string as a password -- it's too low-level.
Like many of the modules here, I think the functions and methods could be better named. In particular the assymetry between the function and method names: random_regex and randregex, but random_string and randpattern.
This module used /dev/urandom to generate a random string of characters, with an OO interface:
use String::Urandom; $generator = String::Urandom->new(LENGTH => 8, CHARS => [ 'a' .. 'z', 'A' .. 'Z', '0' .. '9' ]); $password = $generator->rand_string();
If you don't pass the CHARS parameter, it will default to alphanumeric characters, which I specified explicitly above.
Here's the standard 10 passwords, generated using the default alphanumeric:
1YlDp6iu OysIZDqu UWFiB2jY 5SL9SBRD crQgVaji rVzYswO9 ETyKfLQs DvATAD77 nw3UmiTm aT2x4y5w
This is similar in function to Data::Random and Data::Random::String, but it has a better interface than the latter, and gives you control over the character set, which the other two don't. Presumably it generates more truly random data, since it's using /dev/urandom, but as a result it's noticeably slower.
A module for generating English pronounceable passwords. The only control you have is specifying a min and max length for the password:
use Text::Password::Pronounceable; $generator = Text::Password::Pronounceable->new(8); $password = $generator->generate();
You can specify the length of password on each generation, as well as to the constructor.
Again, here are 10 passwords generated using the above code:
ptofilet softhait ttexwore astouama allstssi ffyfanou toicores adthssth trabyith fflertem
I used Benchmark to compare performance, generating 100,000 passwords with each module. I used the default settings with each module, other than specifying a password length of 8 characters.
First, let's look at the relative performance of modules/functions for generating random character strings:
Module | Time (s) |
---|---|
Session::Token | 0.12 |
Crypt::GeneratePassword::chars | 1.29 |
Crypt::RandPasswd::letters | 1.45 |
Crypt::RandPasswd::chars | 1.49 |
String::Random::pattern | 1.56 |
Data::Random::String | 1.77 |
String::Random::regex | 2.58 |
String::MkPasswd | 3.53 |
Data::Random | 4.36 |
App::Genpass | 25.71 |
String::Urandom | 67.88 |
Data::SimplePassword | 1187.08 |
Impressive figures for Session::Token — an order of magnitude faster than the next fastest module. And Data::SimplePassword, by far the slowest. Interesting in that those are two of the modules that take random number generation seriously.
And now, for those which generate pronounceable passwords:
Module | Time (s) |
---|---|
Crypt::PW44 | 1.22 |
Crypt::XkcdPassword | 1.60 |
Text::Password::Pronounceable | 3.59 |
Crypt::PassGen | 14.06 |
Crypt::YAPassGen | 14.11 |
Crypt::GeneratePassword::word | 515.35 |
Crypt::GeneratePassword::word3 | 2898.13 |
Even though there's a big range of times here, even the slowest function can generated 32 passwords per second.
I also wanted some measure of password quality. I haven't done any formal analysis, but I generated 100,000 passwords with each module/function, and checked how many of the generated passwords appeared in an English word list I have. Only two candidates failed this test:
Module | # Words | Examples |
---|---|---|
Text::Password::Pronounceable | 22 | penchant, islander |
Crypt::GeneratePassword::word | 6 | unpoetic, eurafric |
Which module should you use? As ever, it depends.
If you're not looking for pronounceable passwords, I'd go with App::Genpass: it's more mature than the others, has sensible defaults, and generates good passwords without tuning. There are a few features it could borrow from other modules, but many of the comments I made in previous versions of this review have been addressed in recent releases.
If Session::Token borrowed a few features from the other modules, it would be a serious contender: it's very fast, and takes random number generation very seriously.
If you are looking for pronounceable passwords, I'd try Crypt::YAPassGen: you can build your data files, and it supports a lot of options for tweaking the passwords. I was going to suggest Text::Password::Pronounceable if you're looking for something simple in English only, until it failed my simple password quality check. If some of the issues I listed were addressed, Crypt::XkcdPassword would be a good choice for a pronounceable and secure password.
If you want to generate random instances of other types of data, then Data::Random and String::Random would be helpful.
As an end-user looking for this functionality, I'd like to take the best of each module, as there's no outright winner:
Now, let's see if I can work out why Crypt::RandPasswd locks up...
comments powered by Disqus