File Coverage

blib/lib/Crypt/ScryptKDF.pm
Criterion Covered Total %
statement 71 90 78.8
branch 37 68 54.4
condition 24 53 45.2
subroutine 13 13 100.0
pod 6 6 100.0
total 151 230 65.6


line stmt bran cond sub pod time code
1             package Crypt::ScryptKDF;
2              
3 4     4   139646 use strict;
  4         7  
  4         149  
4 4     4   21 use warnings ;
  4         20  
  4         442  
5              
6             our $VERSION = '0.011';
7              
8 4     4   2289 use MIME::Base64 qw(decode_base64 encode_base64);
  4         3440  
  4         307  
9 4     4   29 use Exporter 'import';
  4         127  
  4         6482  
10             our %EXPORT_TAGS = ( all => [qw(scrypt_raw scrypt_hex scrypt_b64 scrypt_hash scrypt_hash_verify random_bytes)] );
11             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
12             our @EXPORT = qw();
13              
14             require XSLoader;
15             XSLoader::load('Crypt::ScryptKDF', $VERSION);
16              
17             sub random_bytes {
18 7   50 7 1 27 my $length = shift || 32;
19 7         13 my $rv;
20              
21 7 50       14 if (eval {require Crypt::PRNG}) {
  7 50       1156  
    50          
    0          
    0          
22 0         0 $rv = Crypt::PRNG::random_bytes($length);
23             }
24 7         751 elsif (eval {require Crypt::OpenSSL::Random}) {
25 0 0       0 if (Crypt::OpenSSL::Random::random_status()) {
26 0         0 $rv = Crypt::OpenSSL::Random::random_bytes($length);
27             }
28             }
29 7         52 elsif (eval {require Net::SSLeay}) {
30 7 50       3847 if (Net::SSLeay::RAND_status() == 1) {
31 7 50       265 if (Net::SSLeay::RAND_bytes($rv, $length) != 1) {
32 0         0 $rv = undef;
33             }
34             }
35             }
36 0         0 elsif (eval {require Crypt::Random}) {
37 0         0 $rv = Crypt::Random::makerandom_octet(Length=>$length);
38             }
39 0         0 elsif (eval {require Bytes::Random::Secure}) {
40 0         0 $rv = Bytes::Random::Secure::random_bytes(32);
41             }
42              
43 7 50       34 if (!defined $rv) {
44 0         0 die "FATAL: Cannot generate random bytes. Install one of: Crypt::PRNG, Crypt::OpenSSL::Random, Net::SSLeay, Crypt::Random, or Bytes::Random::Secure\n";
45             }
46              
47 7         30 return $rv
48             }
49              
50             sub scrypt_raw {
51 2 50 0 2 1 13 warn "scrypt_raw: 2 or 6 parameters required\n" and return undef unless @_ == 2 || @_ == 6;
      66        
52 2         4 my $key = _scrypt_extra(@_);
53 2         14 return $key;
54             }
55              
56             sub scrypt_b64 {
57 3 50 0 3 1 284612 warn "scrypt_b64: 2 or 6 parameters required\n" and return undef unless @_ == 2 || @_ == 6;
      66        
58 3         14 my $key = _scrypt_extra(@_);
59 3 50       10 return undef unless defined $key;
60 3         31 return encode_base64($key, '');
61             }
62              
63             sub scrypt_hex {
64 5 50 0 5 1 324769 warn "scrypt_hex: 2 or 6 parameters required\n" and return undef unless @_ == 2 || @_ == 6;
      66        
65 5         39 my $key = _scrypt_extra(@_);
66 5 50       23 return undef unless defined $key;
67 5         59 return unpack("H*", $key);
68             }
69              
70             sub scrypt_hash {
71 9     9 1 42 my ($key, $salt, $N, $r, $p) = _scrypt_extra(@_);
72 9 50 33     197 return undef unless defined $key && defined $salt && defined $N && defined $r && defined $p;
      33        
      33        
      33        
73 9         269 return "SCRYPT:$N:$r:$p:" . MIME::Base64::encode($salt, "") . ":" . MIME::Base64::encode($key, "");
74             }
75              
76             sub scrypt_hash_verify {
77 1     1 1 10 my ($passwd, $string) = @_;
78 1 50       6 return 0 unless $string;
79 1 50       5 return 0 unless defined $passwd;
80 1         14 my ($alg, $N, $r, $p, $salt, $hash) = ($string =~ /^(SCRYPT):(\d+):(\d+):(\d+):([^\:]+):([^\:]+)$/);
81 1 50 33     9 return 0 unless defined $salt && defined $hash;
82 1         6 $salt = MIME::Base64::decode($salt);
83 1         4 $hash = MIME::Base64::decode($hash);
84 1 50 33     9 return 0 unless defined $salt && defined $hash;
85 1 50       4 return 0 unless length($hash) > 0;
86 1 50 33     16 return 0 unless $N > 0 && $r >= 0 && $p >= 0;
      33        
87             #XXX-TODO utf8::encode($passwd) if utf8::is_utf8($passwd);
88 1         76201 my $key = _scrypt($passwd, $salt, $N, $r, $p, length($hash));
89 1 50       8 return 0 unless defined $key;
90 1 50       8 return 0 unless _slow_eq($key, $hash);
91 1         9 return 1;
92             }
93              
94             sub _get_scrypt_defaults {
95             # (N=2^14, r=8, p=1, len=32)
96 10     10   36 return (16384, 8, 1, 32);
97             }
98              
99             sub _scrypt_extra {
100 19     19   44 my $salt;
101             my @args;
102 19 100       88 if (@_ == 1) { # ... ($passwd)
    100          
    100          
    50          
103 2         9 ($salt, @args) = (random_bytes(32), _get_scrypt_defaults);
104             }
105             elsif (@_ == 2) { # ... ($passwd, $salt)
106 8         31 ($salt, @args) = ($_[1], _get_scrypt_defaults);
107             }
108             elsif (@_ == 5) { # ... ($passwd, $N, $r, $p, $dklen)
109 1         4 ($salt, @args) = (random_bytes(32), $_[1], $_[2], $_[3], $_[4]);
110             }
111             elsif (@_ == 6) { # ... ($passwd, $salt, $N, $r, $p, $dklen)
112 8         17 (undef, $salt, @args) = @_;
113             }
114             else {
115 0         0 warn "ERROR: scrypt() invalid number of arguments\n";
116 0         0 return;
117             }
118             #check @args
119 19         37 my $N = $args[0];
120 19 50 33     114 if ( ($N <= 0) || (($N&($N-1)) != 0) ) { warn "ERROR: invalid 'N'\n"; return }
  0         0  
  0         0  
121 19 50       48 if ($args[1] < 1) { warn "ERROR: invalid 'r'\n"; return }
  0         0  
  0         0  
122 19 50       68 if ($args[2] < 1) { warn "ERROR: invalid 'p'\n"; return }
  0         0  
  0         0  
123 19 50       47 if ($args[3] < 1) { warn "ERROR: invalid 'len'\n"; return }
  0         0  
  0         0  
124             #XXX-TODO utf8::encode($_[0]) if utf8::is_utf8($_[0]);
125 19 100 66     101 $salt = random_bytes($$salt) if ref $salt eq 'SCALAR' && $$salt =~ /^\d+$/;
126 19         1275688 my $key = _scrypt($_[0], $salt, @args);
127 19 100       259 return wantarray ? ($key, $salt, $args[0], $args[1], $args[2]) : $key;
128             }
129              
130             sub _slow_eq {
131 19     19   361152 my ($a, $b) = @_;
132 19 100 100     143 return unless defined $a && defined $b;
133 14         34 my $diff = length $a ^ length $b;
134 14   100     74 for(my $i = 0; $i < length $a && $i < length $b; $i++) {
135 125         419 $diff |= ord(substr $a, $i) ^ ord(substr $b, $i);
136             }
137 14         83 return $diff == 0;
138             }
139              
140             1;
141              
142             __END__