File Coverage

blib/lib/PHP/Functions/Password.pm
Criterion Covered Total %
statement 155 194 79.9
branch 80 132 60.6
condition 42 105 40.0
subroutine 22 22 100.0
pod 10 10 100.0
total 309 463 66.7


line stmt bran cond sub pod time code
1             package PHP::Functions::Password;
2 6     6   13379 use strict;
  6         14  
  6         222  
3 6     6   33 use warnings;
  6         13  
  6         183  
4 6     6   30 use Carp qw(carp croak);
  6         13  
  6         378  
5 6     6   2804 use Crypt::Bcrypt ();
  6         10524  
  6         189  
6 6     6   2629 use Crypt::OpenSSL::Random ();
  6         11463  
  6         200  
7 6     6   45 use MIME::Base64 qw(decode_base64);
  6         12  
  6         309  
8 6     6   3867 use Readonly qw(Readonly);
  6         24991  
  6         396  
9 6     6   49 use base qw(Exporter);
  6         12  
  6         1516  
10              
11             our @EXPORT;
12             our @EXPORT_OK = qw(
13             password_algos
14             password_get_info
15             password_hash
16             password_needs_rehash
17             password_verify
18             PASSWORD_BCRYPT
19             PASSWORD_ARGON2I
20             PASSWORD_ARGON2ID
21             PASSWORD_DEFAULT
22             );
23             our %EXPORT_TAGS = (
24             'all' => \@EXPORT_OK,
25             'default' => \@EXPORT,
26             'consts' => [ grep /^PASSWORD_/, @EXPORT_OK ],
27             'funcs' => [ grep /^password_/, @EXPORT_OK ],
28             );
29             our $VERSION = '1.13';
30              
31              
32             # Exported constants
33 6     6   61 use constant PASSWORD_BCRYPT => 1;
  6         14  
  6         604  
34 6     6   43 use constant PASSWORD_ARGON2I => 2; # exists in PHP since version 7.2
  6         13  
  6         432  
35 6     6   43 use constant PASSWORD_ARGON2ID => 3; # exists in PHP since version 7.3
  6         14  
  6         353  
36 6     6   44 use constant PASSWORD_DEFAULT => PASSWORD_BCRYPT;
  6         31  
  6         16972  
37              
38              
39             # Internal constants
40             Readonly my $PASSWORD_BCRYPT_DEFAULT_COST => 10; # no such PHP constant
41             Readonly my $PASSWORD_BCRYPT_MAX_PASSWORD_LEN => 72; # no such PHP constant
42             Readonly my $PASSWORD_ARGON2_DEFAULT_SALT_LENGTH => 16; # no such PHP constant
43             Readonly my $PASSWORD_ARGON2_DEFAULT_MEMORY_COST => 65536;
44             Readonly my $PASSWORD_ARGON2_DEFAULT_TIME_COST => 4;
45             Readonly my $PASSWORD_ARGON2_DEFAULT_THREADS => 1;
46             Readonly my $PASSWORD_ARGON2_DEFAULT_TAG_LENGTH => 32; # no such PHP constant
47              
48             Readonly my $SIG_BCRYPT => '2y'; # PHP default; equivalent of 2b in non-PHP implementations
49             Readonly my $SIG_ARGON2I => 'argon2i';
50             Readonly my $SIG_ARGON2ID => 'argon2id';
51              
52             Readonly my %SIG_TO_ALGO => ( # not used for bcrypt
53             $SIG_ARGON2I => PASSWORD_ARGON2I,
54             $SIG_ARGON2ID => PASSWORD_ARGON2ID,
55             );
56              
57             # https://en.wikipedia.org/wiki/Bcrypt
58             Readonly my $RE_BCRYPT_ALGO => qr#2[abxy]?#;
59             Readonly my $RE_BCRYPT_SALT => qr#[./A-Za-z0-9]{22}#; # fixed 16 byte salt (encoded as 22 bcrypt-custom-base64 chars)
60             Readonly my $RE_BCRYPT_COST => qr#[0-3]\d#;
61             Readonly my $RE_BCRYPT_HASH => qr#[./A-Za-z0-9]+#;
62             Readonly my $RE_BCRYPT_STRING => qr/^
63             \$
64             ($RE_BCRYPT_ALGO) # $1 type
65             \$
66             ($RE_BCRYPT_COST) # $2 cost
67             \$
68             ($RE_BCRYPT_SALT) # $3 salt
69             ($RE_BCRYPT_HASH) # $4 hash
70             $/x;
71              
72             # See https://www.alexedwards.net/blog/how-to-hash-and-verify-passwords-with-argon2-in-go
73             Readonly my $RE_ARGON2_ALGO => qr#argon2id?#;
74             Readonly my $RE_ARGON2_STRING => qr/^
75             \$
76             ($RE_ARGON2_ALGO) # $1 signature
77             \$
78             v=(\d{1,3}) # $2 version
79             \$
80             m=(\d{1,10}), # $3 memory_cost
81             t=(\d{1,3}), # $4 time_cost
82             p=(\d{1,3}) # $5 threads
83             \$
84             ([A-Za-z0-9+\/]+) # $6 salt
85             \$
86             ([A-Za-z0-9+\/]+) # $7 hash
87             $/x;
88              
89             =head1 NAME
90              
91             PHP::Functions::Password - Perl ports of PHP password functions
92              
93             =head1 DESCRIPTION
94              
95             This module provides ported PHP password functions.
96             This module supports the bcrypt, argon2i, and argon2id algorithms, as is the case with the equivalent PHP functions at the date of writing this.
97             All functions may also be called as class methods and support inheritance too.
98             See L for detailed usage instructions.
99              
100             =head1 SYNOPSIS
101              
102             use PHP::Functions::Password ();
103              
104             Functional interface, typical use:
105              
106             use PHP::Functions::Password qw(password_hash);
107             my $password = 'secret';
108             my $crypted_string = password_hash($password); # uses PASSWORD_BCRYPT algorithm
109              
110             Functional interface use, using options:
111              
112             use PHP::Functions::Password qw(:all);
113             my $password = 'secret';
114              
115             # Specify options (see PHP docs for which):
116             my $crypted_string = password_hash($password, PASSWORD_DEFAULT, cost => 11);
117              
118             # Use a different algorithm:
119             my $crypted_string = password_hash($password, PASSWORD_ARGON2ID);
120              
121             # Better practice using a 'pepper':
122             use Digest::SHA qw(hmac_sha256);
123             my $pepper = 'Abracadabra and Hocus pocus'; # retrieve this from a secrets config file for example (and don't loose it!)
124             my $peppered_password = hmac_sha256($password, $pepper);
125             my $crypted_string = password_hash($password, PASSWORD_ARGON2ID); # store this in your database
126             # ... and when verifying passwords, then you pepper then first too.
127              
128             Class method use, using options:
129              
130             use PHP::Functions::Password;
131             my $password = 'secret';
132             my $crypted_string = PHP::Functions::Password->hash($password, cost => 9);
133             # Note that the 2nd argument of password_hash() has been dropped here and may be specified
134             # as an option as should've been the case in the original password_hash() function IMHO.
135              
136             =head1 EXPORTS
137              
138             The following names can be imported into the calling namespace by request:
139              
140             password_algos
141             password_get_info
142             password_hash
143             password_needs_rehash
144             password_verify
145             PASSWORD_ARGON2I
146             PASSWORD_ARGON2ID
147             PASSWORD_BCRYPT
148             PASSWORD_DEFAULT
149             :all - what it says
150             :consts - the PASSWORD_* constants
151             :funcs - the password_* functions
152              
153             =head1 PHP COMPATIBLE AND EXPORTABLE FUNCTIONS
154              
155             =over
156              
157             =item password_algos()
158              
159             The same as L
160              
161             Returns an array of supported password algorithm signatures.
162              
163             =cut
164              
165             sub password_algos {
166 2     2 1 1266 my @result = ($SIG_BCRYPT);
167 2 50 33     21 if ($INC{'Crypt/Argon2.pm'} || eval { require Crypt::Argon2; }) {
  2         1012  
168 2         1885 push(@result, $SIG_ARGON2I, $SIG_ARGON2ID);
169             }
170 2         28 return @result;
171             }
172              
173              
174              
175              
176             =item password_get_info($crypted)
177              
178             The same as L
179             with the exception that it returns the following additional keys in the result:
180              
181             algoSig e.g. '2y'
182             salt (encoded)
183             hash (encoded)
184             version (only for argon2 algorithms)
185              
186             Returns a hash in array context, else a hashref.
187              
188             =cut
189              
190             sub password_get_info {
191 34 100 66 34 1 3856 my $proto = @_ && UNIVERSAL::isa($_[0],__PACKAGE__) ? shift : __PACKAGE__;
192 34         62 my $crypted = shift;
193 34 100       142 if ($crypted =~ $RE_BCRYPT_STRING) {
    100          
194 18         220 my $type = $1;
195 18         56 my $cost = int($2);
196 18         35 my $salt = $3;
197 18         37 my $hash = $4;
198 18         96 my %result = (
199             'algo' => PASSWORD_BCRYPT,
200             'algoName' => 'bcrypt',
201             'algoSig' => $type, # extra
202             'options' => {
203             'cost'=> $cost,
204             },
205             'salt' => $salt, # extra
206             'hash' => $hash, # extra
207             );
208 18 100       123 return wantarray ? %result : \%result;
209             }
210             elsif ($crypted =~ $RE_ARGON2_STRING) {
211 10         194 my $sig = $1;
212 10         30 my $version = int($2);
213 10         23 my $memory_cost = int($3);
214 10         20 my $time_cost = int($4);
215 10         19 my $threads = int($5);
216 10         22 my $salt = $6;
217 10         18 my $hash = $7;
218             #my $raw_salt = decode_base64($salt);
219             #my $raw_hash = decode_base64($hash);
220             my %result = (
221 10         51 'algo' => $SIG_TO_ALGO{$sig},
222             'algoName' => $sig,
223             'algoSig' => $sig,
224             'options' => {
225             'memory_cost' => $memory_cost,
226             'time_cost' => $time_cost,
227             'threads' => $threads,
228             },
229             'salt' => $salt,
230             'hash' => $hash,
231             'version' => $version,
232             );
233 10 100       188 return wantarray ? %result : \%result;
234             }
235              
236             # No matches:
237 6         93 my %result = (
238             'algo' => 0,
239             'algoName' => 'unknown',
240             'options' => {},
241             );
242 6 100       32 return wantarray ? %result : \%result;
243             }
244              
245              
246              
247              
248             =item password_hash($password, $algo, %options)
249              
250             Similar to L with the difference that the $algo argument is optional and defaults to PASSWORD_DEFAULT for your programming pleasure.
251              
252             Important notes about the 'salt' option which you shouldn't use in the first place:
253              
254             - The PASSWORD_BCRYPT 'salt' option is deprecated since PHP 7.0, but if you do pass it, then it must be 16 bytes long!
255             - For algorithms other than PASSWORD_BCRYPT, PHP doesn't support the 'salt' option, but if you do pass it, then it must be in raw bytes!
256              
257             =cut
258              
259             sub password_hash {
260 9 50 33 9 1 69 my $proto = @_ && UNIVERSAL::isa($_[0],__PACKAGE__) ? shift : __PACKAGE__;
261 9         22 my $password = shift;
262 9   50     32 my $algo = shift // PASSWORD_DEFAULT;
263 9 50 33     44 my %options = @_ && ref($_[0]) ? %{$_[0]} : @_;
  0         0  
264 9 50       62 unless ($algo =~ /^\d$/) {
265 0         0 croak("Invalid \$algo parameter ($algo) which should be one of the PASSWORD_* integer constants");
266             }
267 9 100 66     63 if ($algo == PASSWORD_BCRYPT) {
    50          
268 3         8 my $salt;
269 3 50 33     18 if (defined($options{'salt'}) && length($options{'salt'})) {
270             # Treat salt as a string of bytes
271 0         0 $salt = $options{'salt'};
272 0 0       0 utf8::is_utf8($salt) && utf8::encode($salt); # "\x{100}" becomes "\xc4\x80"; preferred equivalent of Encode::is_utf8($string) && Encode::_utf8_off($password);
273 0 0       0 if (length($salt) == 16) {
    0          
274             # raw bytes: OK
275             }
276             elsif ($salt =~ /^$RE_BCRYPT_SALT$/) { # bcrypt-custom-base64 encoded string of 22 characters; DEPRECATED
277 0         0 $salt =~ tr#./A-Za-z0-9#A-Za-z0-9+/#;
278 0         0 $salt .= '=' x (3 - (length($salt) + 3) % 4);
279 0         0 $salt = decode_base64($salt);
280             }
281             else {
282 0         0 croak('Bad syntax in given and deprecated salt option (' . $options{'salt'} . ')');
283             }
284             }
285             else {
286 3         62 $salt = Crypt::OpenSSL::Random::random_bytes(16);
287             }
288 3         33 my $cost = $PASSWORD_BCRYPT_DEFAULT_COST;
289 3 50       27 if ($options{'cost'}) {
290 0         0 my $min_cost = 5;
291 0         0 my $max_cost = 31;
292 0 0 0     0 unless (($options{'cost'} =~ /^\d{1,2}$/) && ($options{'cost'} >= $min_cost) && ($options{'cost'} <= $max_cost)) {
      0        
293 0         0 croak('Invalid cost option given (' . $options{'cost'} . ") which should be an integer in the range $min_cost to $max_cost");
294             }
295 0         0 $cost = int($options{'cost'});
296             }
297              
298             # Treat passwords as strings of bytes
299 3 100       17 utf8::is_utf8($password) && utf8::encode($password); # "\x{100}" becomes "\xc4\x80"; preferred equivalent of Encode::is_utf8($string) && Encode::_utf8_off($password);
300              
301             # Everything beyond the max password length in bytes for bcrypt is silently ignored.
302 3         955 require bytes;
303 3 100       34 if (bytes::length($password) > $PASSWORD_BCRYPT_MAX_PASSWORD_LEN) { # $password is already bytes, so the bytes:: prefix is redundant here
304 1         12 $password = substr($password, 0, $PASSWORD_BCRYPT_MAX_PASSWORD_LEN);
305             }
306              
307 3         2538 return Crypt::Bcrypt::bcrypt($password, $SIG_BCRYPT, $cost, $salt);
308             }
309             elsif (($algo == PASSWORD_ARGON2ID) || ($algo == PASSWORD_ARGON2I)) {
310 6 50 33     37 unless ($INC{'Crypt/Argon2.pm'} || eval { require Crypt::Argon2; }) {
  0         0  
311 0 0       0 my $algo_const_name = $algo == PASSWORD_ARGON2ID ? PASSWORD_ARGON2ID : PASSWORD_ARGON2I;
312 0         0 croak("Cannot use the $algo_const_name algorithm because the module Crypt::Argon2 is not installed");
313             }
314 6   33     75 my $salt = $options{'salt'} || Crypt::OpenSSL::Random::random_bytes($PASSWORD_ARGON2_DEFAULT_SALT_LENGTH); # undocumented; not a PHP option; raw!
315 6   33     2942 my $memory_cost = $options{'memory_cost'} || $PASSWORD_ARGON2_DEFAULT_MEMORY_COST;
316 6   33     91 my $time_cost = $options{'time_cost'} || $PASSWORD_ARGON2_DEFAULT_TIME_COST;
317 6   33     58 my $threads = $options{'threads'} || $PASSWORD_ARGON2_DEFAULT_THREADS;
318 6   33     55 my $tag_length = $options{'tag_length'} || $PASSWORD_ARGON2_DEFAULT_TAG_LENGTH; # undocumented; not a PHP option; 4 - 2^32 - 1
319              
320             # Treat passwords as strings of bytes
321 6 100       64 utf8::is_utf8($password) && utf8::encode($password); # "\x{100}" becomes "\xc4\x80"; preferred equivalent of Encode::is_utf8($string) && Encode::_utf8_off($password);
322              
323 6         29 my @args = ($password, $salt, $time_cost, $memory_cost . 'k', $threads, $tag_length);
324 6 100       19 if ($algo == PASSWORD_ARGON2ID) {
325 3         1899466 return Crypt::Argon2::argon2id_pass(@args);
326             }
327             else {
328 3         1953248 return Crypt::Argon2::argon2i_pass(@args);
329             }
330             }
331             else {
332 0         0 croak("Unimplemented algorithm $algo");
333             }
334             }
335              
336              
337              
338              
339             =item password_needs_rehash($crypted, $algo, %options)
340              
341             The same as L.
342              
343             =cut
344              
345             sub password_needs_rehash {
346 24 100 66 24 1 6946 my $proto = @_ && UNIVERSAL::isa($_[0],__PACKAGE__) ? shift : __PACKAGE__;
347 24         45 my $crypted = shift;
348 24   50     55 my $algo = shift(@_) // PASSWORD_DEFAULT;
349 24 50 33     88 my %options = @_ && ref($_[0]) ? %{$_[0]} : @_;
  24         88  
350 24         64 my %info = password_get_info($crypted);
351 24 100       74 unless ($info{'algo'} == $algo) {
352 2 50       7 $options{'debug'} && warn('Algorithms differ: ' . $info{'algo'} . "<>$algo");
353 2         9 return 1;
354             }
355 22 100 66     69 if ($algo == PASSWORD_BCRYPT) {
    100          
356             #unless (($info{'algoSig'} eq $SIG_BCRYPT) || ($info{'algoSig'} eq '2b')) { # also accept 2b as a non-PHP equivalent of 2y
357 12 100       35 unless ($info{'algoSig'} eq $SIG_BCRYPT) { # this emulates PHP's behaviour (it requires 2b to be rehashed as 2y).
358 4 50       30 $options{'debug'} && warn('Algorithm signatures differ: ' . $info{'algoSig'} . ' vs ' . $SIG_BCRYPT);
359 4         16 return 1;
360             }
361 8   66     58 my $cost = $options{'cost'} // $PASSWORD_BCRYPT_DEFAULT_COST;
362 8 100 66     63 unless (defined($info{'options'}->{'cost'}) && ($info{'options'}->{'cost'} == $cost)) {
363 4 50       12 $options{'debug'} && warn('Cost mismatch: ' . $info{'options'}->{'cost'} . "<>$cost");
364 4         16 return 1;
365             }
366             }
367             elsif (($algo == PASSWORD_ARGON2ID) || ($algo == PASSWORD_ARGON2I)) {
368 8   33     22 my $memory_cost = $options{'memory_cost'} // $PASSWORD_ARGON2_DEFAULT_MEMORY_COST;
369 8 100       20 if ($info{'options'}->{'memory_cost'} != $memory_cost) {
370 2 50       6 $options{'debug'} && warn('memory_cost mismatch: ' . $info{'options'}->{'memory_cost'} . "<>$memory_cost");
371 2         9 return 1;
372             }
373 6   33     16 my $time_cost = $options{'time_cost'} // $PASSWORD_ARGON2_DEFAULT_TIME_COST;
374 6 100       17 if ($info{'options'}->{'time_cost'} != $time_cost) {
375 2 50       7 $options{'debug'} && warn('time_cost mismatch: ' . $info{'options'}->{'time_cost'} . "<>$time_cost");
376 2         10 return 1;
377             }
378 4   33     11 my $threads = $options{'threads'} // $PASSWORD_ARGON2_DEFAULT_THREADS;
379 4 100       11 if ($info{'options'}->{'threads'} != $threads) {
380 2 50       7 $options{'debug'} && warn('threads mismatch: ' . $info{'options'}->{'threads'} . "<>$threads");
381 2         9 return 1;
382             }
383 2 50 33     15 my $wanted_salt_length = defined($options{'salt'}) && length($options{'salt'}) ? length($options{'salt'}) : $PASSWORD_ARGON2_DEFAULT_SALT_LENGTH;
384 2   33     17 my $wanted_tag_length = $options{'tag_length'} || $PASSWORD_ARGON2_DEFAULT_TAG_LENGTH; # undocumented; not a PHP option; 4 - 2^32 - 1
385              
386 2 50 66     21 if ($INC{'Crypt/Argon2.pm'} || eval { require Crypt::Argon2; }) {
  1         710  
387 2 50       1123 if (Crypt::Argon2->can('argon2_needs_rehash')) { # since version 0.008
388 2         12 return Crypt::Argon2::argon2_needs_rehash($crypted, $info{'algoSig'}, $time_cost, $memory_cost . 'k', $threads, $wanted_tag_length, $wanted_salt_length);
389             }
390             else { # as long as Crypt::Argon2 is not required for building, a minimum version requirement cannot be forced, and therefore the workaround below is needed
391 0 0       0 if ($info{'version'} < 19) {
392 0 0       0 $options{'debug'} && warn('Version mismatch: ' . $info{'version'} . '<19');
393 0         0 return 1;
394             }
395 0         0 my $salt_encoded = $info{'salt'};
396 0         0 my $salt = decode_base64($salt_encoded);
397 0 0       0 if (!defined($salt)) {
398 0 0       0 $options{'debug'} && warn("decode_base64('$salt_encoded') failed");
399 0         0 return 1;
400             }
401 0         0 my $actual_salt_length = length($salt);
402 0 0       0 if ($wanted_salt_length != $actual_salt_length) {
403 0 0       0 $options{'debug'} && warn("wanted salt length ($wanted_salt_length) != actual salt length ($actual_salt_length)");
404 0         0 return 1;
405             }
406 0         0 my $tag_encoded = $info{'hash'};
407 0         0 my $tag = decode_base64($tag_encoded);
408 0         0 my $actual_tag_length = length($tag);
409 0 0       0 if ($wanted_tag_length != $actual_tag_length) {
410 0 0       0 $options{'debug'} && warn("wanted tag length ($wanted_tag_length) != actual tag length ($actual_tag_length)");
411 0         0 return 1;
412             }
413             }
414             }
415             }
416             else {
417 2 50       6 $options{'debug'} && warn("Can't do anything with unknown algorithm: $algo");
418             }
419 6         26 return 0;
420             }
421              
422              
423              
424              
425             =item password_verify($password, $crypted)
426              
427             The same as L.
428              
429             =cut
430              
431             sub password_verify {
432 50 100 66 50 1 17393 my $proto = @_ && UNIVERSAL::isa($_[0],__PACKAGE__) ? shift : __PACKAGE__;
433 50         171 my ($password, $crypted) = @_;
434 50 100       509 if ($crypted =~ $RE_BCRYPT_STRING) {
    100          
435              
436             # Treat passwords as strings of bytes
437 32 100       740 utf8::is_utf8($password) && utf8::encode($password); # "\x{100}" becomes "\xc4\x80"; preferred equivalent of Encode::is_utf8($string) && Encode::_utf8_off($password);
438              
439             # Everything beyond the max password length in bytes for bcrypt is silently ignored.
440 32         1518 require bytes;
441 32 100       183 if (bytes::length($password) > $PASSWORD_BCRYPT_MAX_PASSWORD_LEN) { # $password is already bytes, so the bytes:: prefix is redundant here
442 10         112 $password = substr($password, 0, $PASSWORD_BCRYPT_MAX_PASSWORD_LEN);
443             }
444              
445 32         3157595 return Crypt::Bcrypt::bcrypt_check($password, $crypted);
446             }
447             elsif ($crypted =~ $RE_ARGON2_STRING) {
448 16 50 33     553 unless ($INC{'Crypt/Argon2.pm'} || eval { require Crypt::Argon2; }) {
  0         0  
449             #carp("Verifying the $sig algorithm requires the module Crypt::Argon2 to be installed");
450 0         0 return 0;
451             }
452 16         155 my $algo = $SIG_TO_ALGO{$1};
453              
454             # Treat passwords as strings of bytes
455 16 100       245 utf8::is_utf8($password) && utf8::encode($password); # "\x{100}" becomes "\xc4\x80"; preferred equivalent of Encode::is_utf8($string) && Encode::_utf8_off($password);
456              
457 16         56 my @args = ($crypted, $password);
458 16 100       58 if ($algo == PASSWORD_ARGON2ID) {
459 8         5256824 return Crypt::Argon2::argon2id_verify(@args);
460             }
461             else {
462 8         5187317 return Crypt::Argon2::argon2i_verify(@args);
463             }
464             }
465             #carp('Bad crypted argument');
466 2         37 return 0;
467             }
468              
469             =back
470              
471              
472              
473              
474             =head1 SHORTENED ALIAS METHODS
475              
476             =over
477              
478             =item algos()
479              
480             Alias of C.
481              
482             =cut
483              
484             sub algos {
485 1 50 33 1 1 19 my $proto = @_ && UNIVERSAL::isa($_[0],__PACKAGE__) ? shift : __PACKAGE__;
486 1         3 return $proto->password_algos(@_);
487             }
488              
489              
490              
491              
492              
493             =item get_info($crypted)
494              
495             Alias of C.
496              
497             =cut
498              
499             sub get_info {
500 5 50 33 5 1 4075 my $proto = @_ && UNIVERSAL::isa($_[0],__PACKAGE__) ? shift : __PACKAGE__;
501 5         16 return $proto->password_get_info(@_);
502             }
503              
504              
505              
506              
507             =item hash($password, %options)
508              
509             Proxy method for C.
510             The difference is that this method does have an $algo argument,
511             but instead allows the algorithm to be specified with the 'algo' option (in %options).
512              
513             =cut
514              
515             sub hash {
516 9 50 33 9 1 127 my $proto = @_ && UNIVERSAL::isa($_[0],__PACKAGE__) ? shift : __PACKAGE__;
517 9         28 my $password = shift;
518 9 50 33     72 my %options = @_ && ref($_[0]) ? %{$_[0]} : @_;
  0         0  
519 9   50     36 my $algo = $options{'algo'} || PASSWORD_DEFAULT;
520 9         25 delete($options{'algo'});
521 9         53 return $proto->password_hash($password, $algo, %options);
522             }
523              
524              
525              
526              
527             =item needs_rehash($crypted, $algo, %options)
528              
529             Alias of C.
530              
531             =cut
532              
533             sub needs_rehash {
534 12 50 33 12 1 7698 my $proto = @_ && UNIVERSAL::isa($_[0],__PACKAGE__) ? shift : __PACKAGE__;
535 12         40 return $proto->password_needs_rehash(@_);
536             }
537              
538              
539              
540              
541             =item verify($password, $crypted)
542              
543             Alias of C.
544              
545             =cut
546              
547             sub verify {
548 25 50 33 25 1 343963 my $proto = @_ && UNIVERSAL::isa($_[0],__PACKAGE__) ? shift : __PACKAGE__;
549 25         139 return $proto->password_verify(@_);
550             }
551              
552             =back
553              
554             =cut
555              
556              
557              
558             1;
559              
560             __END__