package CtrlO::Crypt::XkcdPassword;
use strict;
use warnings;

# ABSTRACT: Yet another xkcd style password generator

our $VERSION = '1.008';

use Carp qw(croak);
use Crypt::Rijndael;
use Crypt::URandom;
use Data::Entropy qw(with_entropy_source);
use Data::Entropy::Algorithms qw(rand_int pick_r shuffle_r choose_r);
use Data::Entropy::RawSource::CryptCounter;
use Data::Entropy::Source;
use Data::Handle;
use Module::Runtime qw(use_module);

use base 'Class::Accessor::Fast';

__PACKAGE__->mk_accessors(qw(entropy wordlist language _list _pid));

sub new {
    my ( $class, %args ) = @_;

    my %object;

    # init the word list
    my @list;
    if ( $args{wordlist} ) {
        $object{wordlist} = $args{wordlist};
    else {
        my $lang = lc( $args{language} || 'en-GB' );
        $lang =~ s/-/_/g;
        $object{wordlist} = 'CtrlO::Crypt::XkcdPassword::Wordlist::' . $lang;

    if ( -r $object{wordlist} ) {
        open( my $fh, '<:encoding(UTF-8)', $object{wordlist} );
        while ( my $word = <$fh> ) {
            $word =~ s/\s//g;
            push( @list, $word );
        $object{_list} = \@list;
    elsif ( $object{wordlist} =~ /::/ ) {
        eval { use_module( $object{wordlist} ); };
        if ($@) {
            croak( "Cannot load word list module " . $object{wordlist} );
        my $pkg = $object{wordlist};
        no strict 'refs';

        # do we have a __DATA__ section, indication a subclass of
        my $handle = eval { Data::Handle->new($pkg) };
        if ($handle) {
            $object{_list} = [ map { s/\n//g; chomp; $_ } $handle->getlines ];

        # do we have @Words, indication Crypt::Diceware
        elsif ( @{"${pkg}::Words"} ) {
            $object{_list} = \@{"${pkg}::Words"};
        else {
            croak("Cannot find word list in $pkg");
    else {
        croak(    'Invalid word list: >'
                . $object{wordlist}
                . '<. Has to be either a Perl module or a file' );

    # poor person's lazy_build
    $object{entropy} = $args{entropy} || $class->_build_entropy;
    $object{_pid} = $$;

    return bless \%object, $class;

sub _build_entropy {
    my $class = shift;
    return Data::Entropy::Source->new(
            Crypt::Rijndael->new( Crypt::URandom::urandom(32) )

sub xkcd {
    my ( $self, %args ) = @_;
    if ( $self->_pid != $$ ) {

    foreach my $key (keys %args) {
        croak "Invalid key [$key] received."
            unless ($key eq 'words' || $key eq 'digits');

        if (defined $args{$key} && ($args{$key} !~ /^[0-9]+$/)) {
            croak "Invalid value [$args{$key}] for key [$key].";

    my $word_count = $args{words} || 4;

    my $words = with_entropy_source(
        sub {
            shuffle_r( choose_r( $word_count, $self->_list ) );

    if ( my $d = $args{digits} ) {
                '%0' . $d . 'u',
                    $self->entropy, sub { rand_int( 10**$d ) }
    return join( '', map {ucfirst} @$words );

sub _reinit_after_fork {
    my $self = shift;
    $self->entropy( $self->_build_entropy );

'correct horse battery staple';



=encoding UTF-8

=head1 NAME

CtrlO::Crypt::XkcdPassword - Yet another xkcd style password generator

=head1 VERSION

version 1.008


  use CtrlO::Crypt::XkcdPassword;
  my $password_generator = CtrlO::Crypt::XkcdPassword->new;

  say $password_generator->xkcd;
  # LimousineAllegeClergymanEconomic

  say $password_generator->xkcd( words => 3 );
  # ObservantFiresideMacho

  say $password_generator->xkcd( words => 3, digits => 3 );
  # PowerfulSpreadScarf645

  # Use custom word list
    wordlist => '/path/to/wordlist'
    wordlist => 'Some::Wordlist::From::CPAN'

  # Use another source of randomness (aka entropy)
    entropy => Data::Entropy::Source->new( ... );


C<CtrlO::Crypt::XkcdPassword> generates a random password using the
algorithm suggested in L<>: It selects 4 words
from a curated list of words and combines them into a hopefully easy
to remember password (actually a passphrase, but we're all trying to
getting things done, so who cares..).

See L<this
for detailed information on the security of passwords generated from a
known word list.

But L<> also applies to this module, as there are
already a lot of modules on CPAN implementing
L<>. We still wrote a new one, mainly because we
wanted to use a strong source of entropy and a fine-tuned word list.

=head1 METHODS

=head2 new

  my $pw_generator = CtrlO::Crypt::XkcdPassword->new;

Initialize a new object. Uses C<CtrlO::Crypt::XkcdPassword::Wordlist::en_gb>
as a word list per default. The default entropy is based on
C<Crypt::URandom>, i.e. C</dev/urandom> and should be random enough (at
least more random than plain old C<rand()>).

If you want / need to supply another source of entropy, you can do so
by setting up an instance of C<Data::Entropy::Source> and passing it
to C<new> as C<entropy>.

  my $pw_generator = CtrlO::Crypt::XkcdPassword->new(
      entropy => Data::Entropy::Source->new( ... )

To use one of the included language-specific word lists, do:

  my $pw_generator = CtrlO::Crypt::XkcdPassword->new(
      language => 'en-GB',

Available languages are:


=item * en-GB


You can also provide your own custom word list, either in a file:

  my $pw_generator = CtrlO::Crypt::XkcdPassword->new(
      wordlist => '/path/to/file'

Or in a module:

  my $pw_generator = CtrlO::Crypt::XkcdPassword->new(
      wordlist => 'My::Wordlist'

See L</"DEFINING CUSTOM WORD LISTS"> for more info

=head2 xkcd

  my $pw = $pw_generator->xkcd;
  my $pw = $pw_generator->xkcd( words  => 3 );
  my $pw = $pw_generator->xkcd( digits => 2 );

Generate a random, xkcd-style password.

Per default will return 4 randomly chosen words from the word list,
each word's first letter turned to upper case, and concatenated
together into one string:

  # CorrectHorseBatteryStaple

You can get a different number of words by passing in C<words>. But
remember that anything smaller than 3 will probably make for rather
poor passwords, and anything bigger than 7 will be hard to remember.

You can also pass in C<digits> to append a random number consisting of
C<digits> digits to the password:

  $pw_generator->xkcd( words => 3, digits => 2 );
  # StapleBatteryCorrect75


Please note that C<language> is only supported for the word lists
included in this distribution.

=head2 in a plain file

Put your word list into a plain file, one line per word. Install this
file somewhere on your system. You can now use your word list like

    wordlist => '/path/to/wordlist'

=head2 in a Perl module using the Wordlist API

L<Perlancar|> came up with a unified API for various word list modules,
implemented in L<Wordlist|>. Pack
your list into a module adhering to this API, install the module, and
load your word list:

    wordlist => 'Your::Cool::Wordlist'

You can check out L<CtrlO::Crypt::XkcdPassword::Wordlist::en_gb> (included in
this distribution) for an example. But it's really quite simple: Just
subclass C<Wordlist> and put your list of words into the C<__DATA__>
section of the module, one line per word.

=head2 in a Perl module using the Crypt::Diceware API

David Golden uses a different API in his L<Crypt::Diceware> module,
which inspired the design of L<CtrlO::Crypt::XkcdPassword>. To use one
of those word lists, use:

    wordlist => 'Crypt::Diceware::Wordlist::Common'

(yes, this looks just like when using C<Wordlist>. We inspect the
wordlist module and try to figure out what kind of API you're using)

To create a module using the L<Crypt::Diceware> wordlist API, just
create a package containing a public array C<@Words> containing your
word list.


This distributions includes a simple wrapper script, L<>.


This is B<not> the recommended way to install / use this module. But
it's handy if you want to submit a patch or play around with the code
prior to a proper installation.

=head2 Carton

  git clone
  carton install
  carton exec perl -Ilib -MCtrlO::Crypt::XkcdPassword -E 'say CtrlO::Crypt::XkcdPassword->new->xkcd'

=head2 cpanm & local::lib

  git clone
  cpanm -L local --installdeps .
  perl -Mlocal::lib=local -Ilib -MCtrlO::Crypt::XkcdPassword -E 'say CtrlO::Crypt::XkcdPassword->new->xkcd'

=head1 SEE ALSO

Inspired by L<> and L<>

There are a lot of similar modules on CPAN, so we just point you to
L<Neil Bower's comparison of CPAN modules for generating passwords|>

=head2 But we did we write yet another module?


=item * Good entropy

Most of the password generating modules just use C<rand()>, which "is
not cryptographically secure" (according to perldoc).
C<CtrlO::Crypt::XkcdPassword> uses L<Crypt::URandom> via
L<Data::Entropy>, which provides good entropy while still being portable.

=item * Good word list

While L<Crypt::Diceware> has good entropy, we did not like its word
lists. Of course we could have just provided a word list better suited
to our needs, but we wanted it to be very easy to generate xkcd-Style

=item * Easy API

C<< my $pwd = CtrlO::Crypt::XkcdPassword->new->xkcd >> returns 4 words
starting with an uppercase letter as a string, which is our main use
case. But the API also allows for more or less words, or even some digits.

=item * Fork save

=item * L<>


=head1 THANKS


=item * Thanks to L<Ctrl O|> for funding the development of this module.

=item * We learned the usage of C<Data::Entropy> from
L<>, which also implements an
algorithm to generate a random passphrase.

=item * L<m_ueberall|>
for pointing out


=head1 AUTHOR

Thomas Klausner <>


This software is copyright (c) 2018 - 2019 by Thomas Klausner.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.