File Coverage

lib/CtrlO/Crypt/XkcdPassword.pm
Criterion Covered Total %
statement 98 98 100.0
branch 20 20 100.0
condition 11 13 84.6
subroutine 21 21 100.0
pod 2 2 100.0
total 152 154 98.7


line stmt bran cond sub pod time code
1             package CtrlO::Crypt::XkcdPassword;
2 6     6   611773 use strict;
  6         49  
  6         173  
3 6     6   31 use warnings;
  6         8  
  6         168  
4 6     6   117 use 5.010;
  6         19  
5              
6             # ABSTRACT: Yet another xkcd style password generator
7              
8             our $VERSION = '1.010'; # VERSION
9              
10 6     6   42 use Carp qw(croak);
  6         8  
  6         357  
11 6     6   3056 use Crypt::Rijndael;
  6         2639  
  6         176  
12 6     6   2762 use Crypt::URandom;
  6         29026  
  6         284  
13 6     6   2855 use Data::Entropy qw(with_entropy_source);
  6         24032  
  6         382  
14 6     6   3080 use Data::Entropy::Algorithms qw(rand_int pick_r shuffle_r choose_r);
  6         64721  
  6         503  
15 6     6   3202 use Data::Entropy::RawSource::CryptCounter;
  6         13431  
  6         181  
16 6     6   2971 use Data::Entropy::Source;
  6         8748  
  6         441  
17 6     6   2861 use Data::Handle;
  6         193804  
  6         370  
18 6     6   54 use Module::Runtime qw(use_module);
  6         13  
  6         42  
19              
20 6     6   372 use base 'Class::Accessor::Fast';
  6         11  
  6         3248  
21              
22             __PACKAGE__->mk_accessors(qw(entropy wordlist language _list _pid));
23              
24              
25             sub new {
26 17     17 1 34283 my ( $class, %args ) = @_;
27              
28 17         44 my %object;
29              
30             # init the word list
31             my @list;
32 17 100       68 if ( $args{wordlist} ) {
33 11         41 $object{wordlist} = $args{wordlist};
34             }
35             else {
36 6   100     46 my $lang = lc( $args{language} || 'en-GB' );
37 6         33 $lang =~ s/-/_/g;
38 6         32 $object{wordlist} = 'CtrlO::Crypt::XkcdPassword::Wordlist::' . $lang;
39             }
40              
41 17 100       566 if ( -r $object{wordlist} ) {
    100          
42 3     1   181 open( my $fh, '<:encoding(UTF-8)', $object{wordlist} );
  1         8  
  1         1  
  1         8  
43 3         14441 while ( my $word = <$fh> ) {
44 18         69 chomp($word);
45 18         31 $word =~ s/\s//g;
46 18         78 push( @list, $word );
47             }
48 3         68 $object{_list} = \@list;
49             }
50             elsif ( $object{wordlist} =~ /::/ ) {
51 13         47 eval { use_module( $object{wordlist} ); };
  13         128  
52 13 100       9035 if ($@) {
53 1         22 croak( "Cannot load word list module " . $object{wordlist} );
54             }
55 12         42 my $pkg = $object{wordlist};
56 6     6   17463 no strict 'refs';
  6         12  
  6         3990  
57              
58             # do we have a __DATA__ section, indication a subclass of https://metacpan.org/release/WordList
59 12         23 my $handle = eval { Data::Handle->new($pkg) };
  12         177  
60 12 100       10346 if ($handle) {
    100          
61 9         330 $object{_list} = [ map { s/\n//g; chomp; $_ } $handle->getlines ];
  55575         131795  
  55575         62693  
  55575         74178  
62             }
63              
64             # do we have @Words, indication Crypt::Diceware
65 3         25 elsif ( @{"${pkg}::Words"} ) {
66 2         3 $object{_list} = \@{"${pkg}::Words"};
  2         10  
67             }
68             else {
69 1         12 croak("Cannot find word list in $pkg");
70             }
71             }
72             else {
73             croak( 'Invalid word list: >'
74             . $object{wordlist}
75 1         38 . '<. Has to be either a Perl module or a file' );
76             }
77              
78             # poor person's lazy_build
79 14   66     4425 $object{entropy} = $args{entropy} || $class->_build_entropy;
80 14         14985 $object{_pid} = $$;
81              
82 14         98 return bless \%object, $class;
83             }
84              
85             sub _build_entropy {
86 15     15   64 my $class = shift;
87 15         159 return Data::Entropy::Source->new(
88             Data::Entropy::RawSource::CryptCounter->new(
89             Crypt::Rijndael->new( Crypt::URandom::urandom(32) )
90             ),
91             "getc"
92             );
93             }
94              
95              
96             sub xkcd {
97 41     41 1 5398800 my ( $self, %args ) = @_;
98 41 100       1228 if ( $self->_pid != $$ ) {
99 2         80 $self->_reinit_after_fork;
100             }
101              
102 41         4426 foreach my $key (keys %args) {
103 39 100 100     394 croak "Invalid key [$key] received."
104             unless ($key eq 'words' || $key eq 'digits');
105              
106 37 100 66     382 if (defined $args{$key} && ($args{$key} !~ /^[0-9]+$/)) {
107 12         102 croak "Invalid value [$args{$key}] for key [$key].";
108             }
109             }
110              
111 27   100     191 my $word_count = $args{words} || 4;
112              
113             my $words = with_entropy_source(
114             $self->entropy,
115             sub {
116 27     27   1072 shuffle_r( choose_r( $word_count, $self->_list ) );
117             }
118 27         600 );
119              
120 27 100       7139055 if ( my $d = $args{digits} ) {
121             push(
122             @$words,
123             sprintf(
124             '%0' . $d . 'u',
125             with_entropy_source(
126 10     10   205 $self->entropy, sub { rand_int( 10**$d ) }
127             )
128 10         396 )
129             );
130             }
131 27         1226 return join( '', map {ucfirst} @$words );
  93         404  
132             }
133              
134             sub _reinit_after_fork {
135 2     2   9 my $self = shift;
136 2         86 $self->_pid($$);
137 2         36 $self->entropy( $self->_build_entropy );
138             }
139              
140             'correct horse battery staple';
141              
142             __END__