File Coverage

blib/lib/Crypt/Bcrypt.pm
Criterion Covered Total %
statement 41 45 91.1
branch 7 16 43.7
condition 3 6 50.0
subroutine 11 11 100.0
pod 4 4 100.0
total 66 82 80.4


line stmt bran cond sub pod time code
1             package Crypt::Bcrypt;
2             $Crypt::Bcrypt::VERSION = '0.009';
3 2     2   135995 use strict;
  2         14  
  2         59  
4 2     2   10 use warnings;
  2         17  
  2         59  
5              
6 2     2   10 use XSLoader;
  2         4  
  2         65  
7             XSLoader::load('Crypt::Bcrypt');
8              
9 2     2   9 use Exporter 5.57 'import';
  2         41  
  2         104  
10             our @EXPORT_OK = qw(bcrypt bcrypt_check bcrypt_hashed bcrypt_check_hashed bcrypt_needs_rehash);
11              
12 2     2   1046 use Digest::SHA 'hmac_sha256';
  2         6163  
  2         179  
13 2     2   940 use MIME::Base64 2.21 qw(encode_base64);
  2         1324  
  2         1347  
14              
15             sub bcrypt {
16 22     22 1 9421 my ($password, $subtype, $cost, $salt) = @_;
17 22 50       175 die "Unknown subtype $subtype" if $subtype !~ /^2[abxy]$/;
18 22 50 33     166 die "Invalid cost factor $cost" if $cost < 4 || $cost > 31;
19 22 50       72 die "Salt must be 16 bytes" if length $salt != 16;
20 22         94 my $encoded_salt = encode_base64($salt, "");
21 22         66 $encoded_salt =~ tr{A-Za-z0-9+/=}{./A-Za-z0-9}d;
22 22         8502658 return _bcrypt_hashpw($password, sprintf '$%s$%02d$%s', $subtype, $cost, $encoded_salt);
23             }
24              
25             my $subtype_qr = qr/2[abxy]/;
26             my $cost_qr = qr/\d{2}/;
27             my $salt_qr = qr{ [./A-Za-z0-9]{22} }x;
28              
29             sub bcrypt_hashed {
30 1     1 1 1647892 my ($password, $subtype, $cost, $salt, $hash_algorithm) = @_;
31 1 50       7 if ($hash_algorithm) {
32 1         11 (my $encoded_salt = encode_base64($salt, "")) =~ tr{A-Za-z0-9+/=}{./A-Za-z0-9}d;
33 1         20 my $hashed_password = encode_base64(hmac_sha256($password, $encoded_salt), "");
34 1         5 my $hash = bcrypt($hashed_password, $subtype, $cost, $salt);
35 1 50       112 $hash =~ s{ ^ \$ ($subtype_qr) \$ ($cost_qr) \$ ($salt_qr) }{\$bcrypt-sha256\$v=2,t=$1,r=$2\$$3\$}x or die $hash;
36 1         13 return $hash;
37             }
38             else {
39 0         0 bcrypt($password, $subtype, $cost, $salt);
40             }
41             }
42              
43             sub bcrypt_check_hashed {
44 3     3 1 1751 my ($password, $hash) = @_;
45 3 50       170 if ($hash =~ s/ ^ \$ bcrypt-sha256 \$ v=2,t=($subtype_qr),r=($cost_qr) \$ ($salt_qr) \$ /\$$1\$$2\$$3/x) {
46 3         10893183 return bcrypt_check(encode_base64(hmac_sha256($password, $3), ""), $hash);
47             }
48             else {
49 0         0 bcrypt_check($password, $hash);
50             }
51             }
52              
53             sub _get_parameters {
54 3     3   5 my ($hash) = @_;
55 3 50       80 if ($hash =~ / \A \$ ($subtype_qr) \$ ($cost_qr) \$ /x) {
    0          
56 3         18 return ($1, $2, '');
57             }
58             elsif ($hash =~ / ^ \$ bcrypt-sha256 \$ v=2,t=($subtype_qr),r=($cost_qr) \$ /x) {
59 0         0 return ($1, $2, 'sha256');
60             }
61 0         0 return ('', 0, '');
62             }
63              
64             sub bcrypt_needs_rehash {
65 3     3 1 12 my ($hash, $wanted_subtype, $wanted_cost, $wanted_hash) = @_;
66 3         8 my ($my_subtype, $my_cost, $my_hash) = _get_parameters($hash);
67 3   66     34 return $my_subtype ne $wanted_subtype || $my_cost != $wanted_cost || $my_hash ne ($wanted_hash || '');
68             }
69              
70             1;
71              
72             # ABSTRACT: A modern bcrypt implementation
73              
74             __END__