CPAN modules for generating passwords

Neil Bowers

2012-08-04

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.

App::Genpass

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.

Crypt::GeneratePassword

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.

Crypt::PassGen

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

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.

Crypt::RandPasswd

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

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.

Crypt::YAPassGen

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):

linearsqrtlogflat
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.

Data::Random

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.

Data::Random::String

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.

Data::SimplePassword

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

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.

String::MkPasswd

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.

String::Random

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.

String::Urandom

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.

Text::Password::Pronounceable

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

Comparison

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:

ModuleTime (s)
Session::Token0.12
Crypt::GeneratePassword::chars1.29
Crypt::RandPasswd::letters1.45
Crypt::RandPasswd::chars1.49
String::Random::pattern1.56
Data::Random::String1.77
String::Random::regex2.58
String::MkPasswd3.53
Data::Random4.36
App::Genpass25.71
String::Urandom67.88
Data::SimplePassword1187.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:

ModuleTime (s)
Crypt::PW441.22
Crypt::XkcdPassword1.60
Text::Password::Pronounceable3.59
Crypt::PassGen14.06
Crypt::YAPassGen14.11
Crypt::GeneratePassword::word515.35
Crypt::GeneratePassword::word32898.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# WordsExamples
Text::Password::Pronounceable22penchant, islander
Crypt::GeneratePassword::word6unpoetic, eurafric

Conclusion

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