File Coverage

blib/lib/Crypt/ScryptKDF.pm
Criterion Covered Total %
statement 71 91 78.0
branch 37 68 54.4
condition 24 53 45.2
subroutine 13 13 100.0
pod 6 6 100.0
total 151 231 65.3


line stmt bran cond sub pod time code
1             package Crypt::ScryptKDF;
2              
3 4     4   24460 use strict;
  4         7  
  4         147  
4 4     4   19 use warnings ;
  4         8  
  4         274  
5              
6             our $VERSION = '0.009';
7              
8 4     4   2248 use MIME::Base64 qw(decode_base64 encode_base64);
  4         2591  
  4         309  
9 4     4   27 use Exporter 'import';
  4         5  
  4         4502  
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 24 my $length = shift || 32;
19 7         10 my $rv;
20              
21 7 50       10 if (eval {require Crypt::PRNG}) {
  7 50       1540  
  7 50       1045  
    0          
    0          
22 0         0 $rv = Crypt::PRNG::random_bytes($length);
23             }
24 7         5550 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 0         0 elsif (eval {require Net::SSLeay}) {
30 7 50       15297 if (Net::SSLeay::RAND_status() == 1) {
31 7 50       172 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             elsif (eval {require Bytes::Random::Secure}) {
40 0         0 $rv = Bytes::Random::Secure::random_bytes(32);
41             }
42              
43 7 50       45 if (!defined $rv) {
44 0         0 warn "WARNING: Generating random bytes via insecure rand()\n";
45 0         0 $rv = pack('C*', map(int(rand(256)), 1..$length));
46             }
47              
48 7         24 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         7 my $key = _scrypt_extra(@_);
54 2         18 return $key;
55             }
56              
57             sub scrypt_b64 {
58 3 50 0 3 1 28 warn "scrypt_b64: 2 or 6 parameters required\n" and return undef unless @_ == 2 || @_ == 6;
      66        
59 3         10 my $key = _scrypt_extra(@_);
60 3 50       12 return undef unless defined $key;
61 3         35 return encode_base64($key, '');
62             }
63              
64             sub scrypt_hex {
65 5 50 0 5 1 46 warn "scrypt_hex: 2 or 6 parameters required\n" and return undef unless @_ == 2 || @_ == 6;
      66        
66 5         18 my $key = _scrypt_extra(@_);
67 5 50       24 return undef unless defined $key;
68 5         60 return unpack("H*", $key);
69             }
70              
71             sub scrypt_hash {
72 9     9 1 34 my ($key, $salt, $N, $r, $p) = _scrypt_extra(@_);
73 9 50 33     154 return undef unless defined $key && defined $salt && defined $N && defined $r && defined $p;
      33        
      33        
      33        
74 9         185 return "SCRYPT:$N:$r:$p:" . MIME::Base64::encode($salt, "") . ":" . MIME::Base64::encode($key, "");
75             }
76              
77             sub scrypt_hash_verify {
78 1     1 1 7 my ($passwd, $string) = @_;
79 1 50       5 return 0 unless $string;
80 1 50       4 return 0 unless defined $passwd;
81 1         15 my ($alg, $N, $r, $p, $salt, $hash) = ($string =~ /^(SCRYPT):(\d+):(\d+):(\d+):([^\:]+):([^\:]+)$/);
82 1 50 33     6 return 0 unless defined $salt && defined $hash;
83 1         4 $salt = MIME::Base64::decode($salt);
84 1         2 $hash = MIME::Base64::decode($hash);
85 1 50 33     5 return 0 unless defined $salt && defined $hash;
86 1 50       3 return 0 unless length($hash) > 0;
87 1 50 33     10 return 0 unless $N > 0 && $r >= 0 && $p >= 0;
      33        
88             #XXX-TODO utf8::encode($passwd) if utf8::is_utf8($passwd);
89 1         119978 my $key = _scrypt($passwd, $salt, $N, $r, $p, length($hash));
90 1 50       9 return 0 unless defined $key;
91 1 50       4 return 0 unless _slow_eq($key, $hash);
92 1         7 return 1;
93             }
94              
95             sub _get_scrypt_defaults {
96             # (N=2^14, r=8, p=1, len=32)
97 10     10   39 return (16384, 8, 1, 32);
98             }
99              
100             sub _scrypt_extra {
101 19     19   66 my $salt;
102             my @args;
103 19 100       101 if (@_ == 1) { # ... ($passwd)
    100          
    100          
    50          
104 2         6 ($salt, @args) = (random_bytes(32), _get_scrypt_defaults);
105             }
106             elsif (@_ == 2) { # ... ($passwd, $salt)
107 8         23 ($salt, @args) = ($_[1], _get_scrypt_defaults);
108             }
109             elsif (@_ == 5) { # ... ($passwd, $N, $r, $p, $dklen)
110 1         494 ($salt, @args) = (random_bytes(32), $_[1], $_[2], $_[3], $_[4]);
111             }
112             elsif (@_ == 6) { # ... ($passwd, $salt, $N, $r, $p, $dklen)
113 8         28 (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         37 my $N = $args[0];
121 19 50 33     173 if ( ($N <= 0) || (($N&($N-1)) != 0) ) { warn "ERROR: invalid 'N'\n"; return }
  0         0  
  0         0  
122 19 50       45 if ($args[1] < 1) { warn "ERROR: invalid 'r'\n"; return }
  0         0  
  0         0  
123 19 50       49 if ($args[2] < 1) { warn "ERROR: invalid 'p'\n"; return }
  0         0  
  0         0  
124 19 50       48 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     119 $salt = random_bytes($$salt) if ref $salt eq 'SCALAR' && $$salt =~ /^\d+$/;
127 19         1553108 my $key = _scrypt($_[0], $salt, @args);
128 19 100       175 return wantarray ? ($key, $salt, $args[0], $args[1], $args[2]) : $key;
129             }
130              
131             sub _slow_eq {
132 19     19   39 my ($a, $b) = @_;
133 19 100 100     93 return unless defined $a && defined $b;
134 14         20 my $diff = length $a ^ length $b;
135 14   100     56 for(my $i = 0; $i < length $a && $i < length $b; $i++) {
136 125         360 $diff |= ord(substr $a, $i) ^ ord(substr $b, $i);
137             }
138 14         48 return $diff == 0;
139             }
140              
141             1;
142              
143             __END__