File Coverage

blib/lib/Crypt/Random/TESHA2.pm
Criterion Covered Total %
statement 75 77 97.4
branch 11 20 55.0
condition 4 8 50.0
subroutine 15 15 100.0
pod 5 5 100.0
total 110 125 88.0


line stmt bran cond sub pod time code
1             package Crypt::Random::TESHA2;
2 6     6   23443 use strict;
  6         13  
  6         210  
3 6     6   29 use warnings;
  6         12  
  6         190  
4 6     6   39 use Carp qw/croak confess carp/;
  6         11  
  6         515  
5 6     6   6463 use Time::HiRes qw/gettimeofday usleep/;
  6         11751  
  6         31  
6 6     6   9963 use Digest::SHA qw/sha256 sha512/;
  6         29492  
  6         560  
7 6     6   4146 use Crypt::Random::TESHA2::Config;
  6         15  
  6         511  
8              
9             BEGIN {
10 6     6   13 $Crypt::Random::TESHA2::AUTHORITY = 'cpan:DANAJ';
11 6         109 $Crypt::Random::TESHA2::VERSION = '0.01';
12             }
13              
14 6     6   34 use base qw( Exporter );
  6         11  
  6         7001  
15             our @EXPORT_OK = qw( random_bytes random_values irand rand is_strong );
16             our %EXPORT_TAGS = (all => [ @EXPORT_OK ]);
17             our @EXPORT = qw( ); # nothing by default
18              
19             my $_opt_need_strong = 0;
20             my $_opt_weak_ok = 0;
21              
22             my $_entropy_per_raw_byte = Crypt::Random::TESHA2::Config::entropy();
23             # Protect against possible abuse / misconfiguration
24             $_entropy_per_raw_byte = 1 if $_entropy_per_raw_byte < 1;
25             $_entropy_per_raw_byte = 7 if $_entropy_per_raw_byte > 7;
26             my $_hashalg = \&sha512;
27             my $_pool_size = 512;
28             my $_nehi = 0; # A 64-bit counter
29             my $_nelo = 0;
30             my $_pool;
31             my $_C; # bits of entropy that we think are in the pool
32              
33             # Make sure SHA512 is supported, back off to 256 if not.
34             if (!defined sha512("test")) {
35             $_pool_size = 256;
36             $_hashalg = \&sha256;
37             }
38              
39             sub import {
40 5     5   32 my @options;
41              
42 5         14 @options = grep { $_ !~ /^[-:]?weak$/i } @_;
  11         46  
43 5 50       20 $_opt_weak_ok = 1 if @options != @_;
44 5         15 @_ = @options;
45              
46 5         12 @options = grep { $_ !~ /^[-:]?strong$/i } @_;
  11         33  
47 5 50       19 $_opt_need_strong = 1 if @options != @_;
48 5         15 @_ = @options;
49              
50 5 50 33     21 croak "TESHA2 is not a strong randomness source on this platform"
51             if $_opt_need_strong && !is_strong();
52              
53 5         322 goto &Exporter::import;
54             }
55              
56             # Returns 1 if our installtion-time entropy measurements indicated we could
57             # get enough entropy to make this method work on this platform.
58             sub is_strong {
59 3 50   3 1 7166 return ($_entropy_per_raw_byte > 1.0) ? 1 : 0;
60             }
61              
62             # Return $n random 32-bit values
63             sub random_values {
64 1     1 1 756 return map { unpack("L", random_bytes(4)) } 1 .. shift;
  10         33  
65             }
66             # Note, only 32 bits.
67             # TODO: Figure out a portable non-64-bit way to make 52-bit doubles.
68             sub rand {
69 2   100 2 1 1766 return ($_[0]||1) * (unpack("L", random_bytes(4))/4294967296.0);
70             }
71             sub irand {
72 1     1 1 953 return unpack("L", random_bytes(4));
73             }
74              
75             # This uses an entropy pool (see Encyclopedia of Cryptography and Security,
76             # volume 2, "Entropy Sources"). We use SHA-512 to handle a 512-bit pool.
77             # One this this will do is ensure the input from any one byte is nicely
78             # spread out among the 64 bytes of the pool.
79              
80             sub random_bytes {
81 2523     2523 1 45210 my $n = shift;
82 2523 50       11668 return '' unless defined $n;
83 2523 100       8256 if (!defined $_pool) {
84             # Initialize pool with 64 bits of entropy.
85 2         3 $_C = 64;
86             # Get some extra bytes at the start.
87 2         9 my $nbytes = 4 + int($_C/$_entropy_per_raw_byte) + 1;
88 2         8 my $S = join("", map { _get_random_byte() } 1 .. $nbytes);
  28         61  
89 2         31 $_pool = $_hashalg->($S);
90              
91 2 50 33     33 carp "TESHA2 is not a strong randomness source on this platform"
92             if !$_opt_weak_ok && !is_strong();
93             }
94 2523         6417 my $X = '';
95 2523         9744 while (length($X) < $n) {
96 2522         11398 my $K = 8 * $n;
97 2522 50       8527 $K = $_pool_size if $K > $_pool_size;
98             # Add more entropy to pool if needed.
99 2522         7588 while ($_C < $K) {
100 2512         7415 my $nbytes = int( ($K - $_C) / $_entropy_per_raw_byte ) + 1;
101             #warn "want $K bits, pool has $_C bits. Adding $nbytes bytes\n";
102 2512         11505 my $S = join("", map { _get_random_byte() } 1 .. $nbytes);
  2960         12334  
103 2512         28752 $_pool = $_hashalg->($_pool . $S);
104 2512         7311 $_C += $_entropy_per_raw_byte * $nbytes;
105 2512 50       20272 $_C = $_pool_size if $_C > $_pool_size;
106             }
107             # Extract K bits from the pool.
108 2522         5471 $_C -= $K;
109 2522         31283 my $V = $_hashalg->( 'te' . pack("LL", $_nehi, $_nelo) . $_pool );
110 2522 50       10167 if ($_nelo < 4294967295) { $_nelo++; } else { $_nehi++; $_nelo = 0; }
  2522         6966  
  0         0  
  0         0  
111 2522         17220 $X .= substr($V, 0, int($K/8));
112             }
113 2523         36400 return $X;
114             }
115              
116             # The heart of the system, where we gather entropy from timer jitter
117             # (technically this is scheduler jitter). This is a similar idea to
118             # timer_entropyd, TrueRand, or the old Math::TrulyRandom module.
119             #
120             # *) Cryptographically, there are numerous issues here. This is, at best,
121             # one source to feed to a well designed entropy pool system.
122             #
123             # *) The output of this function passes ENT and TestU01 Rabbit on all systems
124             # I've run it on. timer_entropyd does not, even when von Neumann debiased.
125             # However, even a counter run through SHA256 will pass these tests, which
126             # just indicates the stream data is uncorrelated.
127             #
128             # *) The entropy tests mentioned above are only one part. If a system
129             # returned the same sequence every time, it may pass all the tests but
130             # still be a horrible generator. That's especially not what one wants
131             # from this module.
132             #
133             # *) I discovered Matt Blaze's truerand after I wrote this -- no ideas or
134             # code were used. However, I got the idea from timer_entropyd, which
135             # probably in turn got the idea from truerand. Version 2.1 (1996) of
136             # Matt Blaze's truerand is _far_ more conservative than the old design
137             # in Math::TrulyRandom. First he replaces the old-school byte mixing
138             # with a SHA (very similar to my solution here). Next, he does another
139             # mixing to generate the actual bytes, while the old code would use the
140             # raw results. I use a different method above, but the end result is
141             # somewhat similar -- we take these raw results and stir them further.
142             #
143             # *) My tests using the raw timer data are showing 1.5 - 4 bits per xor.
144             # The truerand 2.1 documentation indicates 0.67 to 1.33 bits per call,
145             # then conservatively halves the number.
146             #
147             # *) Expanding on the above, assume the worst and absolutely no entropy is
148             # gathered. Then each byte will be a sha256 related to the current hires
149             # time, where each byte is mixed in the pool. We get a fine PRNG, just
150             # not seeded well (from a crypto point of view, this is awful).
151             #
152             # *) For each bit, the two microsecond values are xor'd and packed into
153             # 32-bit words. Eight of these are concatenated and a SHA-256 hash is
154             # performed. As long as the sum of entropy gathered from all eight xors
155             # is at least 8, we're good. The sha256 takes care of shuffling bits so
156             # there aren't biases. This generates a much better result than using
157             # boolean differences like timer_entropyd (even if the differences are
158             # sent through von Neumann debiasing).
159             #
160             sub _get_random_byte {
161 2988     2988   12853 my ($start, $t1, $t2) = gettimeofday();
162             # This basically gives us a counter, so every call is unique. We can
163             # assume it adds no entropy.
164 2988         11608 my $str = pack("LL", $start, $t1);
165             # A hash we will use for a little bit of CPU processing inside the loop.
166 2988         6892 my %dummy;
167 2988         10415 foreach my $bit (1 .. 8) {
168             # Sleep some period of time. Differ the times so we don't hit a
169             # particular beat of the scheduler.
170 23904         4571861 usleep(2+3*$bit);
171 23904         145797 (undef, $t2) = gettimeofday();
172             # xor the two. The entropy is in the variation between what we got
173             # (t2 - t1) and what we expected (2+3*bit + c).
174 23904         115462 $str .= pack("L", $t1 ^ $t2);
175             # A little hash processing to further perturb the times.
176 23904         504730 $dummy{$str . $_}++ for 1..8;
177 23904         76664 $t1 = $t2;
178             }
179             # To truly return a byte: return substr( sha256($str), 0, 1 );
180             # Return the entire string -- let the pool figure it out.
181 2988         104921 return sha256($str);
182             }
183              
184             1;
185              
186             __END__