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   985282 use strict;
  17         242  
  17         502  
11 17     17   107 use warnings;
  17         33  
  17         504  
12 17     17   96 use Fcntl;
  17         72  
  17         4944  
13 17     17   138 use Carp qw/croak/;
  17         34  
  17         1249  
14              
15             ## no critic (constant)
16             our $VERSION = '1.009';
17 17     17   145 use constant UINT32_SIZE => 4;
  17         55  
  17         12720  
18              
19             sub new {
20 14     14   1546 my ($class, %params) = @_;
21 14         61 $params{lc $_} = delete $params{$_} for keys %params;
22             $params{nonblocking}
23 14 100       71 = defined $params{nonblocking} ? $params{nonblocking} : 1;
24 14         38 my $self = {};
25             my @methodlist
26 14         69 = ( \&_try_win32, \&_try_dev_random, \&_try_dev_urandom );
27 14         44 foreach my $m (@methodlist) {
28 42         121 my ($name, $rsub, $isblocking, $isstrong) = $m->();
29 42 100       152 next unless defined $name;
30 28 50 66     171 next if $isblocking && $params{nonblocking};
31 14         45 @{$self}{qw( Name SourceSub Blocking Strong )}
  14         68  
32             = ( $name, $rsub, $isblocking, $isstrong );
33 14         45 last;
34             }
35 14 50       339 return defined $self->{SourceSub} ? bless $self, $class : ();
36             }
37              
38             sub random_values {
39 18     18   3003 my ($self, $nvalues) = @_;
40 18 100 100     139 return unless defined $nvalues && int($nvalues) > 0;
41 13         37 my $rsub = $self->{SourceSub};
42 13         52 return unpack( 'L*', $rsub->(UINT32_SIZE * int($nvalues)) );
43             }
44              
45             sub _try_dev_urandom {
46 14 50   14   208 return unless -r "/dev/urandom";
47 14     13   101 return ('/dev/urandom', sub { __read_file('/dev/urandom', @_); }, 0, 0);
  13         51  
48             }
49              
50             sub _try_dev_random {
51 14 50   14   335 return unless -r "/dev/random";
52 14 50       103 my $blocking = $^O eq 'freebsd' ? 0 : 1;
53 14     0   105 return ('/dev/random', sub {__read_file('/dev/random', @_)}, $blocking, 1);
  0         0  
54             }
55              
56             sub __read_file {
57 14     14   173 my ($file, $nbytes) = @_;
58 14 100 66     85 return unless defined $nbytes && $nbytes > 0;
59 13         503 sysopen(my $fh, $file, O_RDONLY);
60 13         83 binmode $fh;
61 13         55 my($s, $buffer, $nread) = ('', '', 0);
62 13         70 while ($nread < $nbytes) {
63 13         216 my $thisread = sysread $fh, $buffer, $nbytes-$nread;
64 13 50 33     127 croak "Error reading $file: $!\n"
65             unless defined $thisread && $thisread > 0;
66 13         39 $s .= $buffer;
67 13         68 $nread += length($buffer);
68             }
69 13 50       62 croak "Internal file read error: wanted $nbytes, read $nread"
70             unless $nbytes == length($s); # assert
71 13         443 return $s;
72             }
73              
74             sub _try_win32 {
75 14 50   14   80 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   154 use constant CRYPT_SILENT => 0x40; # Never display a UI.
  17         64  
  17         1092  
79 17     17   142 use constant PROV_RSA_FULL => 1; # Which service provider.
  17         43  
  17         948  
80 17     17   104 use constant VERIFY_CONTEXT => 0xF0000000; # Don't need existing keepairs
  17         39  
  17         913  
81 17     17   118 use constant W2K_MAJOR_VERSION => 5; # Windows 2000
  17         31  
  17         916  
82 17     17   133 use constant W2K_MINOR_VERSION => 0;
  17         41  
  17         7410  
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   135 use strict;
  17         34  
  17         519  
138 17     17   94 use warnings;
  17         46  
  17         1306  
139              
140             our $VERSION = '1.007';
141             use constant {
142 17         5221 randrsl => 0, randcnt => 1, randmem => 2,
143             randa => 3, randb => 4, randc => 5,
144 17     17   117 };
  17         50  
145              
146             sub new {
147 12     12   1063 my ($class, @seed) = @_;
148 12         43 my $seedsize = scalar(@seed);
149 12         36 my @mm;
150 12         170 $#mm = $#seed = 255; # predeclare arrays with 256 slots
151 12         713 $seed[$_] = 0 for $seedsize .. 255; # Zero-fill unused seed space.
152 12         62 my $self = [ \@seed, 0, \@mm, 0, 0, 0 ];
153 12         34 bless $self, $class;
154 12         60 $self->_randinit;
155 12         193 return $self;
156             }
157              
158             sub irand {
159 32465     32465   346791 my $self = shift;
160 32465 100       57867 if (!$self->[randcnt]--) {
161 123         340 $self->_isaac;
162 123         225 $self->[randcnt] = 255;
163             }
164 32465         101764 return sprintf('%u', $self->[randrsl][$self->[randcnt]]);
165             }
166              
167             ## no critic (RequireNumberSeparators,ProhibitCStyleForLoops)
168              
169             sub _isaac {
170 135     135   239 my $self = shift;
171 17     17   9154 use integer;
  17         269  
  17         88  
172 135         221 my($mm, $r, $aa) = @{$self}[randmem,randrsl,randa];
  135         677  
173 135         320 my $bb = ($self->[randb] + (++$self->[randc])) & 0xffffffff;
174 135         222 my ($x, $y); # temporary storage
175 135         416 for (my $i = 0; $i < 256; $i += 4) {
176 8640         12164 $x = $mm->[$i ];
177 8640         13535 $aa = (($aa ^ ($aa << 13)) + $mm->[($i + 128) & 0xff]);
178 8640         11545 $aa &= 0xffffffff; # Mask out high bits for 64-bit systems
179 8640         14184 $mm->[$i ] = $y = ($mm->[($x >> 2) & 0xff] + $aa + $bb) & 0xffffffff;
180 8640         13692 $r->[$i ] = $bb = ($mm->[($y >> 10) & 0xff] + $x) & 0xffffffff;
181              
182 8640         12158 $x = $mm->[$i+1];
183 8640         14380 $aa = (($aa ^ (0x03ffffff & ($aa >> 6))) + $mm->[($i+1+128) & 0xff]);
184 8640         11405 $aa &= 0xffffffff;
185 8640         15098 $mm->[$i+1] = $y = ($mm->[($x >> 2) & 0xff] + $aa + $bb) & 0xffffffff;
186 8640         14060 $r->[$i+1] = $bb = ($mm->[($y >> 10) & 0xff] + $x) & 0xffffffff;
187              
188 8640         11961 $x = $mm->[$i+2];
189 8640         13945 $aa = (($aa ^ ($aa << 2)) + $mm->[($i+2 + 128) & 0xff]);
190 8640         11307 $aa &= 0xffffffff;
191 8640         15242 $mm->[$i+2] = $y = ($mm->[($x >> 2) & 0xff] + $aa + $bb) & 0xffffffff;
192 8640         13895 $r->[$i+2] = $bb = ($mm->[($y >> 10) & 0xff] + $x) & 0xffffffff;
193              
194 8640         12077 $x = $mm->[$i+3];
195 8640         14060 $aa = (($aa ^ (0x0000ffff & ($aa >> 16))) + $mm->[($i+3 + 128) & 0xff]);
196 8640         11388 $aa &= 0xffffffff;
197 8640         15105 $mm->[$i+3] = $y = ($mm->[($x >> 2) & 0xff] + $aa + $bb) & 0xffffffff;
198 8640         18657 $r->[$i+3] = $bb = ($mm->[($y >> 10) & 0xff] + $x) & 0xffffffff;
199             }
200 135         261 @{$self}[randb, randa] = ($bb,$aa);
  135         297  
201 135         334 return;
202             }
203              
204             sub _randinit {
205 12     12   46 my $self = shift;
206 17     17   5504 use integer;
  17         36  
  17         130  
207 12         70 my ($c, $d, $e, $f, $g, $h, $j, $k) = (0x9e3779b9)x8; # The golden ratio.
208 12         30 my ($mm, $r) = @{$self}[randmem,randrsl];
  12         83  
209 12         44 for (1..4) {
210 48         90 $c ^= $d << 11; $f += $c; $d += $e;
  48         78  
  48         70  
211 48         81 $d ^= 0x3fffffff & ($e >> 2); $g += $d; $e += $f;
  48         74  
  48         76  
212 48         74 $e ^= $f << 8; $h += $e; $f += $g;
  48         90  
  48         74  
213 48         124 $f ^= 0x0000ffff & ($g >> 16); $j += $f; $g += $h;
  48         76  
  48         77  
214 48         88 $g ^= $h << 10; $k += $g; $h += $j;
  48         75  
  48         67  
215 48         77 $h ^= 0x0fffffff & ($j >> 4); $c += $h; $j += $k;
  48         71  
  48         82  
216 48         74 $j ^= $k << 8; $d += $j; $k += $c;
  48         141  
  48         76  
217 48         336 $k ^= 0x007fffff & ($c >> 9); $e += $k; $c += $d;
  48         88  
  48         84  
218             }
219 12         81 for (my $i = 0; $i < 256; $i += 8) {
220 384         618 $c += $r->[$i ]; $d += $r->[$i+1];
  384         630  
221 384         526 $e += $r->[$i+2]; $f += $r->[$i+3];
  384         549  
222 384         558 $g += $r->[$i+4]; $h += $r->[$i+5];
  384         522  
223 384         534 $j += $r->[$i+6]; $k += $r->[$i+7];
  384         543  
224 384         532 $c ^= $d << 11; $f += $c; $d += $e;
  384         501  
  384         505  
225 384         534 $d ^= 0x3fffffff & ($e >> 2); $g += $d; $e += $f;
  384         488  
  384         503  
226 384         522 $e ^= $f << 8; $h += $e; $f += $g;
  384         528  
  384         605  
227 384         599 $f ^= 0x0000ffff & ($g >> 16); $j += $f; $g += $h;
  384         509  
  384         475  
228 384         520 $g ^= $h << 10; $k += $g; $h += $j;
  384         1149  
  384         481  
229 384         529 $h ^= 0x0fffffff & ($j >> 4); $c += $h; $j += $k;
  384         505  
  384         488  
230 384         513 $j ^= $k << 8; $d += $j; $k += $c;
  384         493  
  384         498  
231 384         546 $k ^= 0x007fffff & ($c >> 9); $e += $k; $c += $d;
  384         497  
  384         501  
232 384         545 $mm->[$i ] = $c; $mm->[$i+1] = $d;
  384         558  
233 384         562 $mm->[$i+2] = $e; $mm->[$i+3] = $f;
  384         573  
234 384         569 $mm->[$i+4] = $g; $mm->[$i+5] = $h;
  384         581  
235 384         585 $mm->[$i+6] = $j; $mm->[$i+7] = $k;
  384         786  
236             }
237 12         95 for (my $i = 0; $i < 256; $i += 8) {
238 384         572 $c += $mm->[$i ]; $d += $mm->[$i+1];
  384         569  
239 384         531 $e += $mm->[$i+2]; $f += $mm->[$i+3];
  384         533  
240 384         535 $g += $mm->[$i+4]; $h += $mm->[$i+5];
  384         590  
241 384         533 $j += $mm->[$i+6]; $k += $mm->[$i+7];
  384         542  
242 384         519 $c ^= $d << 11; $f += $c; $d += $e;
  384         510  
  384         515  
243 384         528 $d ^= 0x3fffffff & ($e >> 2); $g += $d; $e += $f;
  384         518  
  384         514  
244 384         515 $e ^= $f << 8; $h += $e; $f += $g;
  384         495  
  384         504  
245 384         525 $f ^= 0x0000ffff & ($g >> 16); $j += $f; $g += $h;
  384         486  
  384         484  
246 384         536 $g ^= $h << 10; $k += $g; $h += $j;
  384         497  
  384         483  
247 384         559 $h ^= 0x0fffffff & ($j >> 4); $c += $h; $j += $k;
  384         501  
  384         490  
248 384         532 $j ^= $k << 8; $d += $j; $k += $c;
  384         505  
  384         518  
249 384         517 $k ^= 0x007fffff & ($c >> 9); $e += $k; $c += $d;
  384         560  
  384         511  
250 384         513 $mm->[$i ] = $c; $mm->[$i+1] = $d;
  384         584  
251 384         542 $mm->[$i+2] = $e; $mm->[$i+3] = $f;
  384         550  
252 384         528 $mm->[$i+4] = $g; $mm->[$i+5] = $h;
  384         584  
253 384         535 $mm->[$i+6] = $j; $mm->[$i+7] = $k;
  384         739  
254             }
255 12         66 $self->_isaac;
256 12         24 $self->[randcnt] = 256;
257 12         28 return;
258             }
259              
260             1;
261              
262             package Math::Random::ISAAC::Embedded;
263              
264 17     17   9766 use strict;
  17         36  
  17         483  
265 17     17   91 use warnings;
  17         44  
  17         844  
266              
267             our $VERSION = '1.007';
268 17     17   114 use constant _backend => 0;
  17         34  
  17         4687  
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   63 my ($class, @seed) = @_;
278             our $EMBEDDED_CSPRNG =
279             defined $EMBEDDED_CSPRNG ? $EMBEDDED_CSPRNG :
280 11 100       64 defined $ENV{'BRST_EMBEDDED_CSPRNG'} ? $ENV{'BRST_EMBEDDED_CSPRNG'} : 0;
    100          
281             my $DRIVER =
282             $EMBEDDED_CSPRNG ? $CSPRNG{'EM'} :
283 3         487 eval {require Math::Random::ISAAC::XS; 1} ? $CSPRNG{'XS'} :
  0         0  
284 3         415 eval {require Math::Random::ISAAC::PP; 1} ? $CSPRNG{'PP'} :
  0         0  
285 11 50       54 $CSPRNG{'EM'};
    50          
    100          
286 11         88 return bless [$DRIVER->new(@seed)], $class;
287             }
288              
289 31865     31865   52299 sub irand {shift->[_backend]->irand}
290              
291             1;
292              
293             package Bytes::Random::Secure::Tiny;
294              
295 17     17   154 use strict;
  17         63  
  17         487  
296 17     17   134 use warnings;
  17         57  
  17         789  
297 17     17   431 use 5.006000;
  17         82  
298 17     17   131 use Carp qw(croak);
  17         34  
  17         1293  
299 17     17   9941 use Hash::Util;
  17         52080  
  17         103  
300              
301             our $VERSION = '1.007';
302              
303             # See Math::Random::ISAAC https://rt.cpan.org/Public/Bug/Display.html?id=64324
304 17     17   1325 use constant SEED_SIZE => 256; # bits; eight 32-bit words.
  17         38  
  17         13752  
305              
306             sub new {
307 14     14 1 18283 my($self, $class, %args) = ({}, @_);
308 14         100 $args{lc $_} = delete $args{$_} for keys %args; # Convert args to lc names
309 14         56 my $bits = SEED_SIZE; # Default: eight 32bit words.
310 14 100       75 $bits = delete $args{bits} if exists $args{bits};
311 14 100 100     418 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         38 _rng => Math::Random::ISAAC::Embedded->new(do{
316 11 50       98 my $source = Crypt::Random::Seed::Embedded->new(%args)
317             or croak 'Could not get a seed source.';
318 11         84 $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         189  
324 31865     31865 1 108693 sub irand {shift->{'_rng'}->irand}
325 28     28 1 9878 sub bytes_hex {unpack 'H*', shift->bytes(shift)} # lc Hex digits only, no '0x'
326              
327             sub bytes {
328 2543     2543 1 15674 my($self, $bytes) = @_;
329 2543 100       4495 $bytes = defined $bytes ? int abs $bytes : 0; # Default 0, coerce to UINT.
330 2543         3631 my $str = q{};
331 2543         4708 while ($bytes >= 4) { # Utilize irand()'s 32 bits.
332 8348         13183 $str .= pack("L", $self->irand);
333 8348         17261 $bytes -= 4;
334             }
335 2543 100       4238 if ($bytes > 0) { # Handle 16b and 8b respectively.
336 2522 100       4217 $str .= pack("S", ($self->irand >> 8) & 0xFFFF) if $bytes >= 2;
337 2522 100       5374 $str .= pack("C", $self->irand & 0xFF) if $bytes % 2;
338             }
339 2543         7802 return $str;
340             }
341              
342             sub string_from {
343 276     276 1 5345 my($self, $bag, $bytes) = @_;
344 276 100       509 $bag = defined $bag ? $bag : q{};
345 276 100       492 $bytes = defined $bytes ? int abs $bytes : 0;
346 276         386 my $range = length $bag;
347 276 100       751 croak 'Bag size must be at least one character.' unless $range;
348 275         407 my $rand_bytes = q{}; # We need an empty, defined string.
349             $rand_bytes .= substr $bag, $_, 1
350 275         364 for @{$self->_ranged_randoms($range, $bytes)};
  275         487  
351 275         1146 return $rand_bytes;
352             }
353              
354             sub shuffle {
355 2     2 1 4864 my($self, $aref) = @_;
356 2 100       220 croak 'Argument must be an array reference.' unless 'ARRAY' eq ref $aref;
357 1 50       5 return $aref unless @$aref;
358 1         4 for (my $i = @$aref; --$i;) {
359 15         32 my $r = $self->_ranged_randoms($i+1, 1)->[0];
360 15         49 ($aref->[$i],$aref->[$r]) = ($aref->[$r], $aref->[$i]);
361             }
362 1         6 return $aref;
363             }
364              
365             sub _ranged_randoms {
366 10473     10473   42492 my ($self, $range, $count) = @_;
367 10473 100       28166 $_ = defined $_ ? $_ : 0 for $count, $range;
368 10473 100       20201 croak "$range exceeds irand max limit of 2^^32." if $range > 2**32;
369             # Find nearest factor of 2**32 >= $range.
370 10472         13781 my $divisor = do {
371 10472         16038 my ($n, $d) = (0,0);
372 10472   66     31425 while ($n <= 32 && $d < $range) {$d = 2 ** $n++}
  53282         138740  
373 10472         18255 $d;
374             };
375 10472         15335 my @randoms;
376 10472         23372 $#randoms = $count-1; @randoms = (); # Microoptimize: Preextend & purge.
  10472         16286  
377 10472         18361 for my $n (1 .. $count) { # re-roll if r-num is out of bag range (modbias)
378 10804         19235 my $rand = $self->irand % $divisor;
379 10804         23229 $rand = $self->irand % $divisor while $rand >= $range;
380 10804         21596 push @randoms, $rand;
381             }
382 10472         35956 return \@randoms;
383             }
384              
385             1; # POD contained in Bytes/Random/Secure/Tiny.pod
386