File Coverage

blib/lib/Bytes/Random/Secure/Tiny.pm
Criterion Covered Total %
statement 325 360 90.2
branch 52 82 63.4
condition 18 27 66.6
subroutine 45 48 93.7
pod 6 6 100.0
total 446 523 85.2


line stmt bran cond sub pod time code
1             ## no critic (ProhibitMultiplePackages,RequireFilenameMatchesPackage)
2              
3             # Bytes::Random::Secure::Tiny: A single source file implementation of
4             # Bytes::Random::Secure, and its dependencies.
5              
6             # Crypt::Random::Seed::Embedded, adapted with consent from #
7             # Crypt::Random::Seed, by Dana Jacobson. #
8              
9             package Crypt::Random::Seed::Embedded;
10 17     17   982982 use strict;
  17         169  
  17         565  
11 17     17   96 use warnings;
  17         34  
  17         507  
12 17     17   87 use Fcntl;
  17         56  
  17         4927  
13 17     17   127 use Carp qw/croak/;
  17         37  
  17         1326  
14              
15             ## no critic (constant)
16             our $VERSION = '1.010';
17 17     17   155 use constant UINT32_SIZE => 4;
  17         58  
  17         12770  
18              
19             sub new {
20 14     14   1450 my ($class, %params) = @_;
21 14         63 $params{lc $_} = delete $params{$_} for keys %params;
22             $params{nonblocking}
23 14 100       71 = defined $params{nonblocking} ? $params{nonblocking} : 1;
24 14         37 my $self = {};
25             my @methodlist
26 14         63 = ( \&_try_win32, \&_try_dev_random, \&_try_dev_urandom );
27 14         43 foreach my $m (@methodlist) {
28 42         139 my ($name, $rsub, $isblocking, $isstrong) = $m->();
29 42 100       167 next unless defined $name;
30 28 50 66     185 next if $isblocking && $params{nonblocking};
31 14         48 @{$self}{qw( Name SourceSub Blocking Strong )}
  14         68  
32             = ( $name, $rsub, $isblocking, $isstrong );
33 14         41 last;
34             }
35 14 50       154 return defined $self->{SourceSub} ? bless $self, $class : ();
36             }
37              
38             sub random_values {
39 18     18   3007 my ($self, $nvalues) = @_;
40 18 100 100     150 return unless defined $nvalues && int($nvalues) > 0;
41 13         53 my $rsub = $self->{SourceSub};
42 13         54 return unpack( 'L*', $rsub->(UINT32_SIZE * int($nvalues)) );
43             }
44              
45             sub _try_dev_urandom {
46 14 50   14   212 return unless -r "/dev/urandom";
47 14     13   98 return ('/dev/urandom', sub { __read_file('/dev/urandom', @_); }, 0, 0);
  13         47  
48             }
49              
50             sub _try_dev_random {
51 14 50   14   345 return unless -r "/dev/random";
52 14 50       96 my $blocking = $^O eq 'freebsd' ? 0 : 1;
53 14     0   112 return ('/dev/random', sub {__read_file('/dev/random', @_)}, $blocking, 1);
  0         0  
54             }
55              
56             sub __read_file {
57 14     14   126 my ($file, $nbytes) = @_;
58 14 100 66     81 return unless defined $nbytes && $nbytes > 0;
59 13         541 sysopen(my $fh, $file, O_RDONLY);
60 13         82 binmode $fh;
61 13         50 my($s, $buffer, $nread) = ('', '', 0);
62 13         73 while ($nread < $nbytes) {
63 13         212 my $thisread = sysread $fh, $buffer, $nbytes-$nread;
64 13 50 33     116 croak "Error reading $file: $!\n"
65             unless defined $thisread && $thisread > 0;
66 13         52 $s .= $buffer;
67 13         122 $nread += length($buffer);
68             }
69 13 50       58 croak "Internal file read error: wanted $nbytes, read $nread"
70             unless $nbytes == length($s); # assert
71 13         426 return $s;
72             }
73              
74             sub _try_win32 {
75 14 50   14   82 return unless $^O eq 'MSWin32';
76 0 0       0 eval { require Win32; require Win32::API; require Win32::API::Type; 1; }
  0         0  
  0         0  
  0         0  
  0         0  
77             or return;
78 17     17   145 use constant CRYPT_SILENT => 0x40; # Never display a UI.
  17         43  
  17         1081  
79 17     17   127 use constant PROV_RSA_FULL => 1; # Which service provider.
  17         36  
  17         957  
80 17     17   116 use constant VERIFY_CONTEXT => 0xF0000000; # Don't need existing keepairs
  17         36  
  17         911  
81 17     17   101 use constant W2K_MAJOR_VERSION => 5; # Windows 2000
  17         33  
  17         906  
82 17     17   134 use constant W2K_MINOR_VERSION => 0;
  17         47  
  17         7192  
83 0         0 my ($major, $minor) = (Win32::GetOSVersion())[1, 2];
84 0 0       0 return if $major < W2K_MAJOR_VERSION;
85              
86 0 0 0     0 if ($major == W2K_MAJOR_VERSION && $minor == W2K_MINOR_VERSION) {
87             # We are Windows 2000. Use the older CryptGenRandom interface.
88 0         0 my $crypt_acquire_context_a =
89             Win32::API->new('advapi32', 'CryptAcquireContextA', 'PPPNN','I');
90 0 0       0 return unless defined $crypt_acquire_context_a;
91 0         0 my $context = chr(0) x Win32::API::Type->sizeof('PULONG');
92 0         0 my $result = $crypt_acquire_context_a->Call(
93             $context, 0, 0, PROV_RSA_FULL, CRYPT_SILENT | VERIFY_CONTEXT );
94 0 0       0 return unless $result;
95 0         0 my $pack_type = Win32::API::Type::packing('PULONG');
96 0         0 $context = unpack $pack_type, $context;
97 0         0 my $crypt_gen_random =
98             Win32::API->new( 'advapi32', 'CryptGenRandom', 'NNP', 'I' );
99 0 0       0 return unless defined $crypt_gen_random;
100             return ('CryptGenRandom',
101             sub {
102 0     0   0 my $nbytes = shift;
103 0         0 my $buffer = chr(0) x $nbytes;
104 0         0 my $result = $crypt_gen_random->Call($context, $nbytes, $buffer);
105 0 0       0 croak "CryptGenRandom failed: $^E" unless $result;
106 0         0 return $buffer;
107 0         0 }, 0, 1); # Assume non-blocking and strong
108             } else {
109 0         0 my $rtlgenrand = Win32::API->new( 'advapi32', <<'_RTLGENRANDOM_PROTO_');
110             INT SystemFunction036(
111             PVOID RandomBuffer,
112             ULONG RandomBufferLength
113             )
114             _RTLGENRANDOM_PROTO_
115 0 0       0 return unless defined $rtlgenrand;
116             return ('RtlGenRand',
117             sub {
118 0     0   0 my $nbytes = shift;
119 0         0 my $buffer = chr(0) x $nbytes;
120 0         0 my $result = $rtlgenrand->Call($buffer, $nbytes);
121 0 0       0 croak "RtlGenRand failed: $^E" unless $result;
122 0         0 return $buffer;
123 0         0 }, 0, 1); # Assume non-blocking and strong
124             }
125 0         0 return;
126             }
127              
128             1;
129              
130             # Math::Random::ISAAC::PP::Embedded: Adapted from #
131             # Math::Random::ISAAC and Math::Random::ISAAC::PP. #
132              
133             ## no critic (constant,unpack)
134              
135             package Math::Random::ISAAC::PP::Embedded;
136              
137 17     17   139 use strict;
  17         33  
  17         508  
138 17     17   92 use warnings;
  17         58  
  17         1223  
139              
140             our $VERSION = '1.010';
141             use constant {
142 17         5232 randrsl => 0, randcnt => 1, randmem => 2,
143             randa => 3, randb => 4, randc => 5,
144 17     17   121 };
  17         52  
145              
146             sub new {
147 12     12   1115 my ($class, @seed) = @_;
148 12         42 my $seedsize = scalar(@seed);
149 12         36 my @mm;
150 12         159 $#mm = $#seed = 255; # predeclare arrays with 256 slots
151 12         725 $seed[$_] = 0 for $seedsize .. 255; # Zero-fill unused seed space.
152 12         62 my $self = [ \@seed, 0, \@mm, 0, 0, 0 ];
153 12         33 bless $self, $class;
154 12         54 $self->_randinit;
155 12         202 return $self;
156             }
157              
158             sub irand {
159 32644     32644   345977 my $self = shift;
160 32644 100       58121 if (!$self->[randcnt]--) {
161 124         343 $self->_isaac;
162 124         216 $self->[randcnt] = 255;
163             }
164 32644         104564 return sprintf('%u', $self->[randrsl][$self->[randcnt]]);
165             }
166              
167             ## no critic (RequireNumberSeparators,ProhibitCStyleForLoops)
168              
169             sub _isaac {
170 136     136   225 my $self = shift;
171 17     17   9314 use integer;
  17         292  
  17         83  
172 136         247 my($mm, $r, $aa) = @{$self}[randmem,randrsl,randa];
  136         369  
173 136         333 my $bb = ($self->[randb] + (++$self->[randc])) & 0xffffffff;
174 136         241 my ($x, $y); # temporary storage
175 136         435 for (my $i = 0; $i < 256; $i += 4) {
176 8704         12079 $x = $mm->[$i ];
177 8704         13796 $aa = (($aa ^ ($aa << 13)) + $mm->[($i + 128) & 0xff]);
178 8704         11648 $aa &= 0xffffffff; # Mask out high bits for 64-bit systems
179 8704         14407 $mm->[$i ] = $y = ($mm->[($x >> 2) & 0xff] + $aa + $bb) & 0xffffffff;
180 8704         13498 $r->[$i ] = $bb = ($mm->[($y >> 10) & 0xff] + $x) & 0xffffffff;
181              
182 8704         12253 $x = $mm->[$i+1];
183 8704         14327 $aa = (($aa ^ (0x03ffffff & ($aa >> 6))) + $mm->[($i+1+128) & 0xff]);
184 8704         11398 $aa &= 0xffffffff;
185 8704         14749 $mm->[$i+1] = $y = ($mm->[($x >> 2) & 0xff] + $aa + $bb) & 0xffffffff;
186 8704         14245 $r->[$i+1] = $bb = ($mm->[($y >> 10) & 0xff] + $x) & 0xffffffff;
187              
188 8704         11999 $x = $mm->[$i+2];
189 8704         14016 $aa = (($aa ^ ($aa << 2)) + $mm->[($i+2 + 128) & 0xff]);
190 8704         11345 $aa &= 0xffffffff;
191 8704         14873 $mm->[$i+2] = $y = ($mm->[($x >> 2) & 0xff] + $aa + $bb) & 0xffffffff;
192 8704         14442 $r->[$i+2] = $bb = ($mm->[($y >> 10) & 0xff] + $x) & 0xffffffff;
193              
194 8704         12341 $x = $mm->[$i+3];
195 8704         14215 $aa = (($aa ^ (0x0000ffff & ($aa >> 16))) + $mm->[($i+3 + 128) & 0xff]);
196 8704         11507 $aa &= 0xffffffff;
197 8704         15004 $mm->[$i+3] = $y = ($mm->[($x >> 2) & 0xff] + $aa + $bb) & 0xffffffff;
198 8704         18539 $r->[$i+3] = $bb = ($mm->[($y >> 10) & 0xff] + $x) & 0xffffffff;
199             }
200 136         260 @{$self}[randb, randa] = ($bb,$aa);
  136         314  
201 136         351 return;
202             }
203              
204             sub _randinit {
205 12     12   56 my $self = shift;
206 17     17   5323 use integer;
  17         38  
  17         113  
207 12         65 my ($c, $d, $e, $f, $g, $h, $j, $k) = (0x9e3779b9)x8; # The golden ratio.
208 12         28 my ($mm, $r) = @{$self}[randmem,randrsl];
  12         85  
209 12         47 for (1..4) {
210 48         98 $c ^= $d << 11; $f += $c; $d += $e;
  48         76  
  48         67  
211 48         86 $d ^= 0x3fffffff & ($e >> 2); $g += $d; $e += $f;
  48         73  
  48         81  
212 48         72 $e ^= $f << 8; $h += $e; $f += $g;
  48         87  
  48         89  
213 48         97 $f ^= 0x0000ffff & ($g >> 16); $j += $f; $g += $h;
  48         76  
  48         69  
214 48         77 $g ^= $h << 10; $k += $g; $h += $j;
  48         86  
  48         67  
215 48         79 $h ^= 0x0fffffff & ($j >> 4); $c += $h; $j += $k;
  48         69  
  48         81  
216 48         94 $j ^= $k << 8; $d += $j; $k += $c;
  48         148  
  48         74  
217 48         344 $k ^= 0x007fffff & ($c >> 9); $e += $k; $c += $d;
  48         81  
  48         89  
218             }
219 12         108 for (my $i = 0; $i < 256; $i += 8) {
220 384         537 $c += $r->[$i ]; $d += $r->[$i+1];
  384         694  
221 384         572 $e += $r->[$i+2]; $f += $r->[$i+3];
  384         535  
222 384         545 $g += $r->[$i+4]; $h += $r->[$i+5];
  384         551  
223 384         522 $j += $r->[$i+6]; $k += $r->[$i+7];
  384         548  
224 384         528 $c ^= $d << 11; $f += $c; $d += $e;
  384         502  
  384         549  
225 384         614 $d ^= 0x3fffffff & ($e >> 2); $g += $d; $e += $f;
  384         494  
  384         480  
226 384         518 $e ^= $f << 8; $h += $e; $f += $g;
  384         513  
  384         506  
227 384         637 $f ^= 0x0000ffff & ($g >> 16); $j += $f; $g += $h;
  384         531  
  384         478  
228 384         525 $g ^= $h << 10; $k += $g; $h += $j;
  384         506  
  384         540  
229 384         506 $h ^= 0x0fffffff & ($j >> 4); $c += $h; $j += $k;
  384         510  
  384         510  
230 384         503 $j ^= $k << 8; $d += $j; $k += $c;
  384         507  
  384         551  
231 384         515 $k ^= 0x007fffff & ($c >> 9); $e += $k; $c += $d;
  384         502  
  384         503  
232 384         567 $mm->[$i ] = $c; $mm->[$i+1] = $d;
  384         560  
233 384         580 $mm->[$i+2] = $e; $mm->[$i+3] = $f;
  384         546  
234 384         596 $mm->[$i+4] = $g; $mm->[$i+5] = $h;
  384         615  
235 384         576 $mm->[$i+6] = $j; $mm->[$i+7] = $k;
  384         780  
236             }
237 12         62 for (my $i = 0; $i < 256; $i += 8) {
238 384         534 $c += $mm->[$i ]; $d += $mm->[$i+1];
  384         561  
239 384         538 $e += $mm->[$i+2]; $f += $mm->[$i+3];
  384         535  
240 384         537 $g += $mm->[$i+4]; $h += $mm->[$i+5];
  384         573  
241 384         528 $j += $mm->[$i+6]; $k += $mm->[$i+7];
  384         565  
242 384         518 $c ^= $d << 11; $f += $c; $d += $e;
  384         513  
  384         493  
243 384         565 $d ^= 0x3fffffff & ($e >> 2); $g += $d; $e += $f;
  384         510  
  384         529  
244 384         531 $e ^= $f << 8; $h += $e; $f += $g;
  384         511  
  384         514  
245 384         605 $f ^= 0x0000ffff & ($g >> 16); $j += $f; $g += $h;
  384         502  
  384         532  
246 384         515 $g ^= $h << 10; $k += $g; $h += $j;
  384         520  
  384         885  
247 384         620 $h ^= 0x0fffffff & ($j >> 4); $c += $h; $j += $k;
  384         509  
  384         502  
248 384         526 $j ^= $k << 8; $d += $j; $k += $c;
  384         491  
  384         493  
249 384         525 $k ^= 0x007fffff & ($c >> 9); $e += $k; $c += $d;
  384         517  
  384         505  
250 384         560 $mm->[$i ] = $c; $mm->[$i+1] = $d;
  384         544  
251 384         564 $mm->[$i+2] = $e; $mm->[$i+3] = $f;
  384         546  
252 384         597 $mm->[$i+4] = $g; $mm->[$i+5] = $h;
  384         536  
253 384         533 $mm->[$i+6] = $j; $mm->[$i+7] = $k;
  384         746  
254             }
255 12         79 $self->_isaac;
256 12         23 $self->[randcnt] = 256;
257 12         28 return;
258             }
259              
260             1;
261              
262             package Math::Random::ISAAC::Embedded;
263              
264 17     17   9898 use strict;
  17         38  
  17         490  
265 17     17   94 use warnings;
  17         42  
  17         914  
266              
267             our $VERSION = '1.010';
268 17     17   116 use constant _backend => 0;
  17         33  
  17         4575  
269              
270             my %CSPRNG = (
271             XS => 'Math::Random::ISAAC::XS',
272             PP => 'Math::Random::ISAAC::PP',
273             EM => 'Math::Random::ISAAC::PP::Embedded',
274             );
275              
276             sub new {
277 11     11   78 my ($class, @seed) = @_;
278             our $EMBEDDED_CSPRNG =
279             defined $EMBEDDED_CSPRNG ? $EMBEDDED_CSPRNG :
280 11 100       79 defined $ENV{'BRST_EMBEDDED_CSPRNG'} ? $ENV{'BRST_EMBEDDED_CSPRNG'} : 0;
    100          
281             my $DRIVER =
282             $EMBEDDED_CSPRNG ? $CSPRNG{'EM'} :
283 3         546 eval {require Math::Random::ISAAC::XS; 1} ? $CSPRNG{'XS'} :
  0         0  
284 3         414 eval {require Math::Random::ISAAC::PP; 1} ? $CSPRNG{'PP'} :
  0         0  
285 11 50       55 $CSPRNG{'EM'};
    50          
    100          
286 11         84 return bless [$DRIVER->new(@seed)], $class;
287             }
288              
289 32044     32044   53181 sub irand {shift->[_backend]->irand}
290              
291             1;
292              
293             package Bytes::Random::Secure::Tiny;
294              
295 17     17   149 use strict;
  17         51  
  17         523  
296 17     17   157 use warnings;
  17         77  
  17         718  
297 17     17   435 use 5.006000;
  17         59  
298 17     17   128 use Carp qw(croak);
  17         32  
  17         1189  
299 17     17   9885 use Hash::Util;
  17         50472  
  17         103  
300              
301             our $VERSION = '1.010';
302              
303             # See Math::Random::ISAAC https://rt.cpan.org/Public/Bug/Display.html?id=64324
304 17     17   1280 use constant SEED_SIZE => 256; # bits; eight 32-bit words.
  17         38  
  17         13676  
305              
306             sub new {
307 14     14 1 18098 my($self, $class, %args) = ({}, @_);
308 14         103 $args{lc $_} = delete $args{$_} for keys %args; # Convert args to lc names
309 14         38 my $bits = SEED_SIZE; # Default: eight 32bit words.
310 14 100       57 $bits = delete $args{bits} if exists $args{bits};
311 14 100 100     494 croak "Number of bits must be 64 <= n <= 8192, and a multipe in 2^n: $bits"
      100        
312             if $bits < 64 || $bits > 8192 || !_ispowerof2($bits);
313             return Hash::Util::lock_hashref bless {
314             bits => $bits,
315 11         53 _rng => Math::Random::ISAAC::Embedded->new(do{
316 11 50       89 my $source = Crypt::Random::Seed::Embedded->new(%args)
317             or croak 'Could not get a seed source.';
318 11         69 $source->random_values($bits/32);
319             }),
320             }, $class;
321             }
322              
323 12   66 12   34 sub _ispowerof2 {my $n = shift; return ($n >= 0) && (($n & ($n-1)) ==0 )}
  12         194  
324 32044     32044 1 109814 sub irand {shift->{'_rng'}->irand}
325 28     28 1 8332 sub bytes_hex {unpack 'H*', shift->bytes(shift)} # lc Hex digits only, no '0x'
326              
327             sub bytes {
328 2543     2543 1 14359 my($self, $bytes) = @_;
329 2543 100       4364 $bytes = defined $bytes ? int abs $bytes : 0; # Default 0, coerce to UINT.
330 2543         3799 my $str = q{};
331 2543         4965 while ($bytes >= 4) { # Utilize irand()'s 32 bits.
332 8348         13526 $str .= pack("L", $self->irand);
333 8348         18261 $bytes -= 4;
334             }
335 2543 100       4337 if ($bytes > 0) { # Handle 16b and 8b respectively.
336 2522 100       4430 $str .= pack("S", ($self->irand >> 8) & 0xFFFF) if $bytes >= 2;
337 2522 100       5369 $str .= pack("C", $self->irand & 0xFF) if $bytes % 2;
338             }
339 2543         7909 return $str;
340             }
341              
342             sub string_from {
343 367     367 1 5517 my($self, $bag, $bytes) = @_;
344 367 100       691 $bag = defined $bag ? $bag : q{};
345 367 100       629 $bytes = defined $bytes ? int abs $bytes : 0;
346 367         535 my $range = length $bag;
347 367 100       892 croak 'Bag size must be at least one character.' unless $range;
348 366         519 my $rand_bytes = q{}; # We need an empty, defined string.
349             $rand_bytes .= substr $bag, $_, 1
350 366         477 for @{$self->_ranged_randoms($range, $bytes)};
  366         664  
351 366         1335 return $rand_bytes;
352             }
353              
354             sub shuffle {
355 2     2 1 4811 my($self, $aref) = @_;
356 2 100       212 croak 'Argument must be an array reference.' unless 'ARRAY' eq ref $aref;
357 1 50       4 return $aref unless @$aref;
358 1         5 for (my $i = @$aref; --$i;) {
359 15         30 my $r = $self->_ranged_randoms($i+1, 1)->[0];
360 15         45 ($aref->[$i],$aref->[$r]) = ($aref->[$r], $aref->[$i]);
361             }
362 1         4 return $aref;
363             }
364              
365             sub _ranged_randoms {
366 10636     10636   42948 my ($self, $range, $count) = @_;
367 10636 100       27841 $_ = defined $_ ? $_ : 0 for $count, $range;
368 10636 100       20015 croak "$range exceeds irand max limit of 2^^32." if $range > 2**32;
369             # Find nearest factor of 2**32 >= $range.
370 10635         14017 my $divisor = do {
371 10635         16750 my ($n, $d) = (0,0);
372 10635   66     32143 while ($n <= 32 && $d < $range) {$d = 2 ** $n++}
  54476         146010  
373 10635         18313 $d;
374             };
375 10635         15959 my @randoms;
376 10635         24249 $#randoms = $count-1; @randoms = (); # Microoptimize: Preextend & purge.
  10635         17214  
377 10635         19723 for my $n (1 .. $count) { # re-roll if r-num is out of bag range (modbias)
378 10967         19944 my $rand = $self->irand % $divisor;
379 10967         24887 $rand = $self->irand % $divisor while $rand >= $range;
380 10967         21377 push @randoms, $rand;
381             }
382 10635         36092 return \@randoms;
383             }
384              
385             1; # POD contained in Bytes/Random/Secure/Tiny.pod
386