File Coverage

lib/CtrlO/Crypt/XkcdPassword.pm
Criterion Covered Total %
statement 96 96 100.0
branch 20 20 100.0
condition 11 13 84.6
subroutine 20 20 100.0
pod 2 2 100.0
total 149 151 98.6


line stmt bran cond sub pod time code
1             package CtrlO::Crypt::XkcdPassword;
2 6     6   593600 use strict;
  6         47  
  6         169  
3 6     6   27 use warnings;
  6         10  
  6         244  
4              
5             # ABSTRACT: Yet another xkcd style password generator
6              
7             our $VERSION = '1.009'; # VERSION
8              
9 6     6   28 use Carp qw(croak);
  6         11  
  6         268  
10 6     6   2805 use Crypt::Rijndael;
  6         2714  
  6         219  
11 6     6   2827 use Crypt::URandom;
  6         27966  
  6         300  
12 6     6   2800 use Data::Entropy qw(with_entropy_source);
  6         22636  
  6         365  
13 6     6   3073 use Data::Entropy::Algorithms qw(rand_int pick_r shuffle_r choose_r);
  6         65264  
  6         470  
14 6     6   3137 use Data::Entropy::RawSource::CryptCounter;
  6         13508  
  6         186  
15 6     6   2970 use Data::Entropy::Source;
  6         8880  
  6         195  
16 6     6   2736 use Data::Handle;
  6         192932  
  6         353  
17 6     6   48 use Module::Runtime qw(use_module);
  6         13  
  6         38  
18              
19 6     6   417 use base 'Class::Accessor::Fast';
  6         11  
  6         3047  
20              
21             __PACKAGE__->mk_accessors(qw(entropy wordlist language _list _pid));
22              
23              
24             sub new {
25 17     17 1 32531 my ( $class, %args ) = @_;
26              
27 17         36 my %object;
28              
29             # init the word list
30             my @list;
31 17 100       55 if ( $args{wordlist} ) {
32 11         28 $object{wordlist} = $args{wordlist};
33             }
34             else {
35 6   100     47 my $lang = lc( $args{language} || 'en-GB' );
36 6         32 $lang =~ s/-/_/g;
37 6         33 $object{wordlist} = 'CtrlO::Crypt::XkcdPassword::Wordlist::' . $lang;
38             }
39              
40 17 100       488 if ( -r $object{wordlist} ) {
    100          
41 3     1   167 open( my $fh, '<:encoding(UTF-8)', $object{wordlist} );
  1         7  
  1         2  
  1         7  
42 3         13240 while ( my $word = <$fh> ) {
43 18         64 chomp($word);
44 18         29 $word =~ s/\s//g;
45 18         74 push( @list, $word );
46             }
47 3         68 $object{_list} = \@list;
48             }
49             elsif ( $object{wordlist} =~ /::/ ) {
50 13         40 eval { use_module( $object{wordlist} ); };
  13         84  
51 13 100       8437 if ($@) {
52 1         21 croak( "Cannot load word list module " . $object{wordlist} );
53             }
54 12         29 my $pkg = $object{wordlist};
55 6     6   17678 no strict 'refs';
  6         11  
  6         4120  
56              
57             # do we have a __DATA__ section, indication a subclass of https://metacpan.org/release/WordList
58 12         25 my $handle = eval { Data::Handle->new($pkg) };
  12         97  
59 12 100       10004 if ($handle) {
    100          
60 9         281 $object{_list} = [ map { s/\n//g; chomp; $_ } $handle->getlines ];
  55575         135285  
  55575         65177  
  55575         73057  
61             }
62              
63             # do we have @Words, indication Crypt::Diceware
64 3         20 elsif ( @{"${pkg}::Words"} ) {
65 2         3 $object{_list} = \@{"${pkg}::Words"};
  2         6  
66             }
67             else {
68 1         9 croak("Cannot find word list in $pkg");
69             }
70             }
71             else {
72             croak( 'Invalid word list: >'
73             . $object{wordlist}
74 1         45 . '<. Has to be either a Perl module or a file' );
75             }
76              
77             # poor person's lazy_build
78 14   66     4440 $object{entropy} = $args{entropy} || $class->_build_entropy;
79 14         15006 $object{_pid} = $$;
80              
81 14         85 return bless \%object, $class;
82             }
83              
84             sub _build_entropy {
85 15     15   49 my $class = shift;
86 15         144 return Data::Entropy::Source->new(
87             Data::Entropy::RawSource::CryptCounter->new(
88             Crypt::Rijndael->new( Crypt::URandom::urandom(32) )
89             ),
90             "getc"
91             );
92             }
93              
94              
95             sub xkcd {
96 41     41 1 5807100 my ( $self, %args ) = @_;
97 41 100       1520 if ( $self->_pid != $$ ) {
98 2         105 $self->_reinit_after_fork;
99             }
100              
101 41         3518 foreach my $key (keys %args) {
102 39 100 100     382 croak "Invalid key [$key] received."
103             unless ($key eq 'words' || $key eq 'digits');
104              
105 37 100 66     405 if (defined $args{$key} && ($args{$key} !~ /^[0-9]+$/)) {
106 12         119 croak "Invalid value [$args{$key}] for key [$key].";
107             }
108             }
109              
110 27   100     174 my $word_count = $args{words} || 4;
111              
112             my $words = with_entropy_source(
113             $self->entropy,
114             sub {
115 27     27   1124 shuffle_r( choose_r( $word_count, $self->_list ) );
116             }
117 27         644 );
118              
119 27 100       8026063 if ( my $d = $args{digits} ) {
120             push(
121             @$words,
122             sprintf(
123             '%0' . $d . 'u',
124             with_entropy_source(
125 10     10   256 $self->entropy, sub { rand_int( 10**$d ) }
126             )
127 10         585 )
128             );
129             }
130 27         1450 return join( '', map {ucfirst} @$words );
  93         428  
131             }
132              
133             sub _reinit_after_fork {
134 2     2   20 my $self = shift;
135 2         100 $self->_pid($$);
136 2         53 $self->entropy( $self->_build_entropy );
137             }
138              
139             'correct horse battery staple';
140              
141             __END__