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   959692 use strict;
  17         172  
  17         521  
11 17     17   95 use warnings;
  17         37  
  17         526  
12 17     17   97 use Fcntl;
  17         36  
  17         4862  
13 17     17   132 use Carp qw/croak/;
  17         35  
  17         1291  
14              
15             ## no critic (constant)
16             our $VERSION = '1.011';
17 17     17   126 use constant UINT32_SIZE => 4;
  17         31  
  17         12435  
18              
19             sub new {
20 14     14   1825 my ($class, %params) = @_;
21 14         61 $params{lc $_} = delete $params{$_} for keys %params;
22             $params{nonblocking}
23 14 100       70 = defined $params{nonblocking} ? $params{nonblocking} : 1;
24 14         35 my $self = {};
25             my @methodlist
26 14         62 = ( \&_try_win32, \&_try_dev_random, \&_try_dev_urandom );
27 14         44 foreach my $m (@methodlist) {
28 42         122 my ($name, $rsub, $isblocking, $isstrong) = $m->();
29 42 100       146 next unless defined $name;
30 28 50 66     182 next if $isblocking && $params{nonblocking};
31 14         45 @{$self}{qw( Name SourceSub Blocking Strong )}
  14         57  
32             = ( $name, $rsub, $isblocking, $isstrong );
33 14         39 last;
34             }
35 14 50       156 return defined $self->{SourceSub} ? bless $self, $class : ();
36             }
37              
38             sub random_values {
39 18     18   3019 my ($self, $nvalues) = @_;
40 18 100 100     115 return unless defined $nvalues && int($nvalues) > 0;
41 13         40 my $rsub = $self->{SourceSub};
42 13         64 return unpack( 'L*', $rsub->(UINT32_SIZE * int($nvalues)) );
43             }
44              
45             sub _try_dev_urandom {
46 14 50   14   225 return unless -r "/dev/urandom";
47 14     13   96 return ('/dev/urandom', sub { __read_file('/dev/urandom', @_); }, 0, 0);
  13         58  
48             }
49              
50             sub _try_dev_random {
51 14 50   14   346 return unless -r "/dev/random";
52 14 50       85 my $blocking = $^O eq 'freebsd' ? 0 : 1;
53 14     0   124 return ('/dev/random', sub {__read_file('/dev/random', @_)}, $blocking, 1);
  0         0  
54             }
55              
56             sub __read_file {
57 14     14   134 my ($file, $nbytes) = @_;
58 14 100 66     96 return unless defined $nbytes && $nbytes > 0;
59 13         494 sysopen(my $fh, $file, O_RDONLY);
60 13         79 binmode $fh;
61 13         53 my($s, $buffer, $nread) = ('', '', 0);
62 13         61 while ($nread < $nbytes) {
63 13         192 my $thisread = sysread $fh, $buffer, $nbytes-$nread;
64 13 50 33     124 croak "Error reading $file: $!\n"
65             unless defined $thisread && $thisread > 0;
66 13         41 $s .= $buffer;
67 13         56 $nread += length($buffer);
68             }
69 13 50       50 croak "Internal file read error: wanted $nbytes, read $nread"
70             unless $nbytes == length($s); # assert
71 13         398 return $s;
72             }
73              
74             sub _try_win32 {
75 14 50   14   88 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   138 use constant CRYPT_SILENT => 0x40; # Never display a UI.
  17         47  
  17         1115  
79 17     17   116 use constant PROV_RSA_FULL => 1; # Which service provider.
  17         33  
  17         939  
80 17     17   107 use constant VERIFY_CONTEXT => 0xF0000000; # Don't need existing keepairs
  17         39  
  17         914  
81 17     17   103 use constant W2K_MAJOR_VERSION => 5; # Windows 2000
  17         29  
  17         905  
82 17     17   103 use constant W2K_MINOR_VERSION => 0;
  17         36  
  17         7269  
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         42  
  17         492  
138 17     17   96 use warnings;
  17         51  
  17         1281  
139              
140             our $VERSION = '1.011';
141             use constant {
142 17         5134 randrsl => 0, randcnt => 1, randmem => 2,
143             randa => 3, randb => 4, randc => 5,
144 17     17   163 };
  17         67  
145              
146             sub new {
147 12     12   1036 my ($class, @seed) = @_;
148 12         33 my $seedsize = scalar(@seed);
149 12         25 my @mm;
150 12         161 $#mm = $#seed = 255; # predeclare arrays with 256 slots
151 12         695 $seed[$_] = 0 for $seedsize .. 255; # Zero-fill unused seed space.
152 12         65 my $self = [ \@seed, 0, \@mm, 0, 0, 0 ];
153 12         37 bless $self, $class;
154 12         72 $self->_randinit;
155 12         182 return $self;
156             }
157              
158             sub irand {
159 32294     32294   411817 my $self = shift;
160 32294 100       58391 if (!$self->[randcnt]--) {
161 123         339 $self->_isaac;
162 123         219 $self->[randcnt] = 255;
163             }
164 32294         103186 return sprintf('%u', $self->[randrsl][$self->[randcnt]]);
165             }
166              
167             ## no critic (RequireNumberSeparators,ProhibitCStyleForLoops)
168              
169             sub _isaac {
170 135     135   242 my $self = shift;
171 17     17   9280 use integer;
  17         260  
  17         86  
172 135         231 my($mm, $r, $aa) = @{$self}[randmem,randrsl,randa];
  135         356  
173 135         348 my $bb = ($self->[randb] + (++$self->[randc])) & 0xffffffff;
174 135         241 my ($x, $y); # temporary storage
175 135         391 for (my $i = 0; $i < 256; $i += 4) {
176 8640         11856 $x = $mm->[$i ];
177 8640         13690 $aa = (($aa ^ ($aa << 13)) + $mm->[($i + 128) & 0xff]);
178 8640         11609 $aa &= 0xffffffff; # Mask out high bits for 64-bit systems
179 8640         14341 $mm->[$i ] = $y = ($mm->[($x >> 2) & 0xff] + $aa + $bb) & 0xffffffff;
180 8640         13485 $r->[$i ] = $bb = ($mm->[($y >> 10) & 0xff] + $x) & 0xffffffff;
181              
182 8640         12269 $x = $mm->[$i+1];
183 8640         14418 $aa = (($aa ^ (0x03ffffff & ($aa >> 6))) + $mm->[($i+1+128) & 0xff]);
184 8640         11296 $aa &= 0xffffffff;
185 8640         14935 $mm->[$i+1] = $y = ($mm->[($x >> 2) & 0xff] + $aa + $bb) & 0xffffffff;
186 8640         14529 $r->[$i+1] = $bb = ($mm->[($y >> 10) & 0xff] + $x) & 0xffffffff;
187              
188 8640         11879 $x = $mm->[$i+2];
189 8640         13912 $aa = (($aa ^ ($aa << 2)) + $mm->[($i+2 + 128) & 0xff]);
190 8640         11392 $aa &= 0xffffffff;
191 8640         14870 $mm->[$i+2] = $y = ($mm->[($x >> 2) & 0xff] + $aa + $bb) & 0xffffffff;
192 8640         14122 $r->[$i+2] = $bb = ($mm->[($y >> 10) & 0xff] + $x) & 0xffffffff;
193              
194 8640         11996 $x = $mm->[$i+3];
195 8640         14533 $aa = (($aa ^ (0x0000ffff & ($aa >> 16))) + $mm->[($i+3 + 128) & 0xff]);
196 8640         11380 $aa &= 0xffffffff;
197 8640         14963 $mm->[$i+3] = $y = ($mm->[($x >> 2) & 0xff] + $aa + $bb) & 0xffffffff;
198 8640         18560 $r->[$i+3] = $bb = ($mm->[($y >> 10) & 0xff] + $x) & 0xffffffff;
199             }
200 135         247 @{$self}[randb, randa] = ($bb,$aa);
  135         345  
201 135         341 return;
202             }
203              
204             sub _randinit {
205 12     12   33 my $self = shift;
206 17     17   5176 use integer;
  17         37  
  17         108  
207 12         63 my ($c, $d, $e, $f, $g, $h, $j, $k) = (0x9e3779b9)x8; # The golden ratio.
208 12         30 my ($mm, $r) = @{$self}[randmem,randrsl];
  12         112  
209 12         58 for (1..4) {
210 48         97 $c ^= $d << 11; $f += $c; $d += $e;
  48         70  
  48         68  
211 48         84 $d ^= 0x3fffffff & ($e >> 2); $g += $d; $e += $f;
  48         87  
  48         64  
212 48         76 $e ^= $f << 8; $h += $e; $f += $g;
  48         73  
  48         65  
213 48         73 $f ^= 0x0000ffff & ($g >> 16); $j += $f; $g += $h;
  48         77  
  48         70  
214 48         84 $g ^= $h << 10; $k += $g; $h += $j;
  48         82  
  48         65  
215 48         82 $h ^= 0x0fffffff & ($j >> 4); $c += $h; $j += $k;
  48         65  
  48         74  
216 48         84 $j ^= $k << 8; $d += $j; $k += $c;
  48         140  
  48         71  
217 48         318 $k ^= 0x007fffff & ($c >> 9); $e += $k; $c += $d;
  48         98  
  48         89  
218             }
219 12         55 for (my $i = 0; $i < 256; $i += 8) {
220 384         532 $c += $r->[$i ]; $d += $r->[$i+1];
  384         573  
221 384         553 $e += $r->[$i+2]; $f += $r->[$i+3];
  384         545  
222 384         546 $g += $r->[$i+4]; $h += $r->[$i+5];
  384         529  
223 384         552 $j += $r->[$i+6]; $k += $r->[$i+7];
  384         519  
224 384         532 $c ^= $d << 11; $f += $c; $d += $e;
  384         505  
  384         484  
225 384         522 $d ^= 0x3fffffff & ($e >> 2); $g += $d; $e += $f;
  384         495  
  384         509  
226 384         546 $e ^= $f << 8; $h += $e; $f += $g;
  384         497  
  384         541  
227 384         526 $f ^= 0x0000ffff & ($g >> 16); $j += $f; $g += $h;
  384         482  
  384         533  
228 384         579 $g ^= $h << 10; $k += $g; $h += $j;
  384         513  
  384         489  
229 384         588 $h ^= 0x0fffffff & ($j >> 4); $c += $h; $j += $k;
  384         496  
  384         488  
230 384         530 $j ^= $k << 8; $d += $j; $k += $c;
  384         525  
  384         502  
231 384         525 $k ^= 0x007fffff & ($c >> 9); $e += $k; $c += $d;
  384         527  
  384         512  
232 384         581 $mm->[$i ] = $c; $mm->[$i+1] = $d;
  384         579  
233 384         560 $mm->[$i+2] = $e; $mm->[$i+3] = $f;
  384         553  
234 384         584 $mm->[$i+4] = $g; $mm->[$i+5] = $h;
  384         562  
235 384         574 $mm->[$i+6] = $j; $mm->[$i+7] = $k;
  384         817  
236             }
237 12         78 for (my $i = 0; $i < 256; $i += 8) {
238 384         542 $c += $mm->[$i ]; $d += $mm->[$i+1];
  384         546  
239 384         550 $e += $mm->[$i+2]; $f += $mm->[$i+3];
  384         561  
240 384         545 $g += $mm->[$i+4]; $h += $mm->[$i+5];
  384         562  
241 384         540 $j += $mm->[$i+6]; $k += $mm->[$i+7];
  384         538  
242 384         550 $c ^= $d << 11; $f += $c; $d += $e;
  384         541  
  384         495  
243 384         535 $d ^= 0x3fffffff & ($e >> 2); $g += $d; $e += $f;
  384         503  
  384         495  
244 384         512 $e ^= $f << 8; $h += $e; $f += $g;
  384         508  
  384         526  
245 384         554 $f ^= 0x0000ffff & ($g >> 16); $j += $f; $g += $h;
  384         518  
  384         505  
246 384         515 $g ^= $h << 10; $k += $g; $h += $j;
  384         499  
  384         508  
247 384         524 $h ^= 0x0fffffff & ($j >> 4); $c += $h; $j += $k;
  384         498  
  384         487  
248 384         527 $j ^= $k << 8; $d += $j; $k += $c;
  384         494  
  384         486  
249 384         534 $k ^= 0x007fffff & ($c >> 9); $e += $k; $c += $d;
  384         990  
  384         503  
250 384         559 $mm->[$i ] = $c; $mm->[$i+1] = $d;
  384         542  
251 384         546 $mm->[$i+2] = $e; $mm->[$i+3] = $f;
  384         564  
252 384         600 $mm->[$i+4] = $g; $mm->[$i+5] = $h;
  384         549  
253 384         539 $mm->[$i+6] = $j; $mm->[$i+7] = $k;
  384         818  
254             }
255 12         67 $self->_isaac;
256 12         34 $self->[randcnt] = 256;
257 12         31 return;
258             }
259              
260             1;
261              
262             package Math::Random::ISAAC::Embedded;
263              
264 17     17   9549 use strict;
  17         41  
  17         423  
265 17     17   88 use warnings;
  17         34  
  17         866  
266              
267             our $VERSION = '1.011';
268 17     17   105 use constant _backend => 0;
  17         59  
  17         4500  
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   59 my ($class, @seed) = @_;
278             our $EMBEDDED_CSPRNG =
279             defined $EMBEDDED_CSPRNG ? $EMBEDDED_CSPRNG :
280 11 100       56 defined $ENV{'BRST_EMBEDDED_CSPRNG'} ? $ENV{'BRST_EMBEDDED_CSPRNG'} : 0;
    100          
281             my $DRIVER =
282             $EMBEDDED_CSPRNG ? $CSPRNG{'EM'} :
283 3         434 eval {require Math::Random::ISAAC::XS; 1} ? $CSPRNG{'XS'} :
  0         0  
284 3         373 eval {require Math::Random::ISAAC::PP; 1} ? $CSPRNG{'PP'} :
  0         0  
285 11 50       56 $CSPRNG{'EM'};
    50          
    100          
286 11         86 return bless [$DRIVER->new(@seed)], $class;
287             }
288              
289 31694     31694   51669 sub irand {shift->[_backend]->irand}
290              
291             1;
292              
293             package Bytes::Random::Secure::Tiny;
294              
295 17     17   154 use strict;
  17         52  
  17         456  
296 17     17   88 use warnings;
  17         43  
  17         686  
297 17     17   529 use 5.006000;
  17         68  
298 17     17   124 use Carp qw(croak);
  17         62  
  17         1181  
299 17     17   9434 use Hash::Util;
  17         49564  
  17         111  
300              
301             our $VERSION = '1.011';
302              
303             # See Math::Random::ISAAC https://rt.cpan.org/Public/Bug/Display.html?id=64324
304 17     17   1332 use constant SEED_SIZE => 256; # bits; eight 32-bit words.
  17         86  
  17         13368  
305              
306             sub new {
307 14     14 1 17914 my($self, $class, %args) = ({}, @_);
308 14         106 $args{lc $_} = delete $args{$_} for keys %args; # Convert args to lc names
309 14         37 my $bits = SEED_SIZE; # Default: eight 32bit words.
310 14 100       63 $bits = delete $args{bits} if exists $args{bits};
311 14 100 100     443 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         33 _rng => Math::Random::ISAAC::Embedded->new(do{
316 11 50       92 my $source = Crypt::Random::Seed::Embedded->new(%args)
317             or croak 'Could not get a seed source.';
318 11         72 $source->random_values($bits/32);
319             }),
320             }, $class;
321             }
322              
323 12   66 12   30 sub _ispowerof2 {my $n = shift; return ($n >= 0) && (($n & ($n-1)) ==0 )}
  12         198  
324 31694     31694 1 110481 sub irand {shift->{'_rng'}->irand}
325 28     28 1 9088 sub bytes_hex {unpack 'H*', shift->bytes(shift)} # lc Hex digits only, no '0x'
326              
327             sub bytes {
328 2543     2543 1 14058 my($self, $bytes) = @_;
329 2543 100       4303 $bytes = defined $bytes ? int abs $bytes : 0; # Default 0, coerce to UINT.
330 2543         3548 my $str = q{};
331 2543         4751 while ($bytes >= 4) { # Utilize irand()'s 32 bits.
332 8348         13366 $str .= pack("L", $self->irand);
333 8348         17236 $bytes -= 4;
334             }
335 2543 100       4420 if ($bytes > 0) { # Handle 16b and 8b respectively.
336 2522 100       4283 $str .= pack("S", ($self->irand >> 8) & 0xFFFF) if $bytes >= 2;
337 2522 100       5367 $str .= pack("C", $self->irand & 0xFF) if $bytes % 2;
338             }
339 2543         7543 return $str;
340             }
341              
342             sub string_from {
343 228     228 1 4939 my($self, $bag, $bytes) = @_;
344 228 100       429 $bag = defined $bag ? $bag : q{};
345 228 100       407 $bytes = defined $bytes ? int abs $bytes : 0;
346 228         335 my $range = length $bag;
347 228 100       602 croak 'Bag size must be at least one character.' unless $range;
348 227         325 my $rand_bytes = q{}; # We need an empty, defined string.
349             $rand_bytes .= substr $bag, $_, 1
350 227         313 for @{$self->_ranged_randoms($range, $bytes)};
  227         401  
351 227         853 return $rand_bytes;
352             }
353              
354             sub shuffle {
355 2     2 1 4723 my($self, $aref) = @_;
356 2 100       217 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         28 my $r = $self->_ranged_randoms($i+1, 1)->[0];
360 15         46 ($aref->[$i],$aref->[$r]) = ($aref->[$r], $aref->[$i]);
361             }
362 1         4 return $aref;
363             }
364              
365             sub _ranged_randoms {
366 10339     10339   40443 my ($self, $range, $count) = @_;
367 10339 100       27403 $_ = defined $_ ? $_ : 0 for $count, $range;
368 10339 100       19897 croak "$range exceeds irand max limit of 2^^32." if $range > 2**32;
369             # Find nearest factor of 2**32 >= $range.
370 10338         13670 my $divisor = do {
371 10338         16797 my ($n, $d) = (0,0);
372 10338   66     30981 while ($n <= 32 && $d < $range) {$d = 2 ** $n++}
  52220         140632  
373 10338         18024 $d;
374             };
375 10338         14913 my @randoms;
376 10338         22844 $#randoms = $count-1; @randoms = (); # Microoptimize: Preextend & purge.
  10338         16358  
377 10338         17965 for my $n (1 .. $count) { # re-roll if r-num is out of bag range (modbias)
378 10670         18541 my $rand = $self->irand % $divisor;
379 10670         23914 $rand = $self->irand % $divisor while $rand >= $range;
380 10670         19788 push @randoms, $rand;
381             }
382 10338         34233 return \@randoms;
383             }
384              
385             1; # POD contained in Bytes/Random/Secure/Tiny.pod
386