File Coverage

blib/lib/Crypt/ScryptKDF.pm
Criterion Covered Total %
statement 73 91 80.2
branch 37 68 54.4
condition 24 53 45.2
subroutine 13 13 100.0
pod 6 6 100.0
total 153 231 66.2


line stmt bran cond sub pod time code
1             package Crypt::ScryptKDF;
2              
3 4     4   22948 use strict;
  4         8  
  4         105  
4 4     4   20 use warnings ;
  4         5  
  4         187  
5              
6             our $VERSION = '0.010';
7              
8 4     4   2969 use MIME::Base64 qw(decode_base64 encode_base64);
  4         5117  
  4         477  
9 4     4   36 use Exporter 'import';
  4         9  
  4         6317  
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 23 my $length = shift || 32;
19 7         10 my $rv;
20              
21 7 50       14 if (eval {require Crypt::PRNG}) {
  7 50       2787  
    50          
    50          
    50          
22 0         0 $rv = Crypt::PRNG::random_bytes($length);
23             }
24 7         2287 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         40919 elsif (eval {require Net::SSLeay}) {
30 0 0       0 if (Net::SSLeay::RAND_status() == 1) {
31 0 0       0 if (Net::SSLeay::RAND_bytes($rv, $length) != 1) {
32 0         0 $rv = undef;
33             }
34             }
35             }
36 7         2473 elsif (eval {require Crypt::Random}) {
37 0         0 $rv = Crypt::Random::makerandom_octet(Length=>$length);
38             }
39 7         2507 elsif (eval {require Bytes::Random::Secure}) {
40 0         0 $rv = Bytes::Random::Secure::random_bytes(32);
41             }
42              
43 7 50       73 if (!defined $rv) {
44 7         474 warn "WARNING: Generating random bytes via insecure rand()\n";
45 7         261 $rv = pack('C*', map(int(rand(256)), 1..$length));
46             }
47              
48 7         46 return $rv
49             }
50              
51             sub scrypt_raw {
52 2 50 0 2 1 15 warn "scrypt_raw: 2 or 6 parameters required\n" and return undef unless @_ == 2 || @_ == 6;
      66        
53 2         8 my $key = _scrypt_extra(@_);
54 2         21 return $key;
55             }
56              
57             sub scrypt_b64 {
58 3 50 0 3 1 29 warn "scrypt_b64: 2 or 6 parameters required\n" and return undef unless @_ == 2 || @_ == 6;
      66        
59 3         9 my $key = _scrypt_extra(@_);
60 3 50       13 return undef unless defined $key;
61 3         81 return encode_base64($key, '');
62             }
63              
64             sub scrypt_hex {
65 5 50 0 5 1 47 warn "scrypt_hex: 2 or 6 parameters required\n" and return undef unless @_ == 2 || @_ == 6;
      66        
66 5         34 my $key = _scrypt_extra(@_);
67 5 50       30 return undef unless defined $key;
68 5         75 return unpack("H*", $key);
69             }
70              
71             sub scrypt_hash {
72 9     9 1 37 my ($key, $salt, $N, $r, $p) = _scrypt_extra(@_);
73 9 50 33     179 return undef unless defined $key && defined $salt && defined $N && defined $r && defined $p;
      33        
      33        
      33        
74 9         250 return "SCRYPT:$N:$r:$p:" . MIME::Base64::encode($salt, "") . ":" . MIME::Base64::encode($key, "");
75             }
76              
77             sub scrypt_hash_verify {
78 1     1 1 9 my ($passwd, $string) = @_;
79 1 50       8 return 0 unless $string;
80 1 50       5 return 0 unless defined $passwd;
81 1         19 my ($alg, $N, $r, $p, $salt, $hash) = ($string =~ /^(SCRYPT):(\d+):(\d+):(\d+):([^\:]+):([^\:]+)$/);
82 1 50 33     10 return 0 unless defined $salt && defined $hash;
83 1         6 $salt = MIME::Base64::decode($salt);
84 1         4 $hash = MIME::Base64::decode($hash);
85 1 50 33     9 return 0 unless defined $salt && defined $hash;
86 1 50       4 return 0 unless length($hash) > 0;
87 1 50 33     14 return 0 unless $N > 0 && $r >= 0 && $p >= 0;
      33        
88             #XXX-TODO utf8::encode($passwd) if utf8::is_utf8($passwd);
89 1         176604 my $key = _scrypt($passwd, $salt, $N, $r, $p, length($hash));
90 1 50       13 return 0 unless defined $key;
91 1 50       6 return 0 unless _slow_eq($key, $hash);
92 1         11 return 1;
93             }
94              
95             sub _get_scrypt_defaults {
96             # (N=2^14, r=8, p=1, len=32)
97 10     10   45 return (16384, 8, 1, 32);
98             }
99              
100             sub _scrypt_extra {
101 19     19   35 my $salt;
102             my @args;
103 19 100       99 if (@_ == 1) { # ... ($passwd)
    100          
    100          
    50          
104 2         10 ($salt, @args) = (random_bytes(32), _get_scrypt_defaults);
105             }
106             elsif (@_ == 2) { # ... ($passwd, $salt)
107 8         27 ($salt, @args) = ($_[1], _get_scrypt_defaults);
108             }
109             elsif (@_ == 5) { # ... ($passwd, $N, $r, $p, $dklen)
110 1         5 ($salt, @args) = (random_bytes(32), $_[1], $_[2], $_[3], $_[4]);
111             }
112             elsif (@_ == 6) { # ... ($passwd, $salt, $N, $r, $p, $dklen)
113 8         26 (undef, $salt, @args) = @_;
114             }
115             else {
116 0         0 warn "ERROR: scrypt() invalid number of arguments\n";
117 0         0 return;
118             }
119             #check @args
120 19         99 my $N = $args[0];
121 19 50 33     174 if ( ($N <= 0) || (($N&($N-1)) != 0) ) { warn "ERROR: invalid 'N'\n"; return }
  0         0  
  0         0  
122 19 50       58 if ($args[1] < 1) { warn "ERROR: invalid 'r'\n"; return }
  0         0  
  0         0  
123 19 50       47 if ($args[2] < 1) { warn "ERROR: invalid 'p'\n"; return }
  0         0  
  0         0  
124 19 50       49 if ($args[3] < 1) { warn "ERROR: invalid 'len'\n"; return }
  0         0  
  0         0  
125             #XXX-TODO utf8::encode($_[0]) if utf8::is_utf8($_[0]);
126 19 100 66     106 $salt = random_bytes($$salt) if ref $salt eq 'SCALAR' && $$salt =~ /^\d+$/;
127 19         2223015 my $key = _scrypt($_[0], $salt, @args);
128 19 100       257 return wantarray ? ($key, $salt, $args[0], $args[1], $args[2]) : $key;
129             }
130              
131             sub _slow_eq {
132 19     19   48 my ($a, $b) = @_;
133 19 100 100     117 return unless defined $a && defined $b;
134 14         27 my $diff = length $a ^ length $b;
135 14   100     80 for(my $i = 0; $i < length $a && $i < length $b; $i++) {
136 125         583 $diff |= ord(substr $a, $i) ^ ord(substr $b, $i);
137             }
138 14         70 return $diff == 0;
139             }
140              
141             1;
142              
143             __END__