File Coverage

blib/lib/Crypt/Passphrase/Argon2/Encrypted.pm
Criterion Covered Total %
statement 70 78 89.7
branch 16 22 72.7
condition 2 6 33.3
subroutine 14 15 93.3
pod 6 6 100.0
total 108 127 85.0


line stmt bran cond sub pod time code
1             package Crypt::Passphrase::Argon2::Encrypted;
2             $Crypt::Passphrase::Argon2::Encrypted::VERSION = '0.010';
3 2     2   276462 use strict;
  2         5  
  2         83  
4 2     2   8 use warnings;
  2         4  
  2         127  
5              
6 2     2   10 use parent 'Crypt::Passphrase::Encoder';
  2         4  
  2         28  
7 2     2   10700 use Crypt::Passphrase::Argon2;
  2         5  
  2         59  
8              
9 2     2   10 use Carp 'croak';
  2         2  
  2         94  
10 2     2   7 use Crypt::Argon2 0.017 qw/argon2_raw argon2_verify argon2_types/;
  2         21  
  2         83  
11 2     2   845 use MIME::Base64 qw/encode_base64 decode_base64/;
  2         1173  
  2         1947  
12              
13             my %multiplier = (
14             k => 1024,
15             M => 1024 * 1024,
16             G => 1024 * 1024 * 1024,
17             );
18              
19             sub new {
20 2     2 1 170346 my ($class, %args) = @_;
21 2         13 my $self = bless Crypt::Passphrase::Argon2::_settings_for(%args), $class;
22 2         25 $self->{memory_cost} =~ s/ \A (\d+) ([kMG]) \z / $1 * $multiplier{$2} /xe;
  2         14  
23 2         8 $self->{cipher} = $args{cipher};
24 2         6 $self->{active} = $args{active};
25 2         8 return $self;
26             }
27              
28             my $format = '$%s-encrypted-%s$v=19$m=%d,t=%d,p=%d,keyid=%s$%s$%s';
29              
30             sub _pack_hash {
31 7     7   36 my ($subtype, $cipher, $id, $m_cost, $t_cost, $parallel, $salt, $hash) = @_;
32 7         44 my $encoded_salt = encode_base64($salt, '') =~ tr/=//dr;
33 7         31 my $encoded_hash = encode_base64($hash, '') =~ tr/=//dr;
34 7         126 return sprintf $format, $subtype, $cipher, $m_cost / 1024, $t_cost, $parallel, $id, $encoded_salt, $encoded_hash;
35             }
36              
37             my $regex = qr/ ^ \$ ($Crypt::Argon2::type_regex)-encrypted-([^\$]+) \$ v=19 \$ m=(\d+), t=(\d+), p=(\d+), keyid=([^\$,]+) \$ ([^\$]+) \$ (.*) $ /x;
38              
39             sub _unpack_hash {
40 14     14   41 my ($pwhash) = @_;
41 14 100       328 my ($subtype, $alg, $m_cost, $t_cost, $parallel, $id, $encoded_salt, $encoded_hash) = $pwhash =~ $regex or return;
42 11         55 my $salt = decode_base64($encoded_salt);
43 11         29 my $hash = decode_base64($encoded_hash);
44 11         106 return ($subtype, $alg, $id, $m_cost * 1024, $t_cost, $parallel, $salt, $hash);
45             }
46              
47             my $unencrypted_regex = qr/ ^ \$ ($Crypt::Argon2::type_regex) \$ v=19 \$ m=(\d+), t=(\d+), p=(\d+) \$ ([^\$]+) \$ (.*) $ /x;
48             sub recode_hash {
49 2     2 1 7 my ($self, $input) = @_;
50 2         17 local $SIG{__DIE__} = \&Carp::croak;
51 2 100       7 if (my ($subtype, $alg, $id, $m_cost, $t_cost, $parallel, $salt, $hash) = _unpack_hash($input)) {
    50          
52 1 50 33     8 return $input if $id eq $self->{active} and $alg eq $self->{cipher};
53 1 50       2 my $decrypted = eval { $self->decrypt_hash($alg, $id, $salt, $hash) } or return $input;
  1         5  
54 1         90 my $encrypted = $self->encrypt_hash($self->{cipher}, $self->{active}, $salt, $decrypted);
55 1         77 return _pack_hash($subtype, $self->{cipher}, $self->{active}, $m_cost, $t_cost, $parallel, $salt, $encrypted);
56             }
57             elsif (($subtype, $m_cost, $t_cost, $parallel, my $encoded_salt, my $encoded_hash) = $input =~ $unencrypted_regex) {
58 1         6 my $salt = decode_base64($encoded_salt);
59 1         5 my $hash = decode_base64($encoded_hash);
60 1         10 my $encrypted = $self->encrypt_hash($self->{cipher}, $self->{active}, $salt, $hash);
61 1         88 return _pack_hash($subtype, $self->{cipher}, $self->{active}, $m_cost * 1024, $t_cost, $parallel, $salt, $encrypted);
62             }
63             else {
64 0         0 return $input;
65             }
66             }
67              
68             sub hash_password {
69 1     1 1 9 my ($self, $password) = @_;
70              
71 1         6 my $salt = $self->random_bytes($self->{salt_size});
72 1         16 local $SIG{__DIE__} = \&Carp::croak;
73 1         3 my $raw = argon2_raw($self->{subtype}, $password, $salt, @{$self}{qw/time_cost memory_cost parallelism output_size/});
  1         217139  
74 1         20 my $encrypted = $self->encrypt_hash($self->{cipher}, $self->{active}, $salt, $raw);
75              
76 1         101 return _pack_hash(@{$self}{qw/subtype cipher active memory_cost time_cost parallelism/}, $salt, $encrypted);
  1         10  
77             }
78              
79             sub needs_rehash {
80 5     5 1 2333 my ($self, $pwhash) = @_;
81 5 100       21 my ($subtype, $alg, $id, $m_cost, $t_cost, $parallel, $salt, $hash) = _unpack_hash($pwhash) or return 1;
82 4 100       12 return 1 if $pwhash ne _pack_hash(@{$self}{qw/subtype cipher active memory_cost time_cost parallelism/}, $salt, $hash);
  4         29  
83 3   33     29 return length $salt != $self->{salt_size} || length $hash != $self->{output_size};
84             }
85              
86             sub crypt_subtypes {
87 0     0 1 0 my $self = shift;
88 0         0 my @result;
89 0         0 my @supported = $self->supported_ciphers;
90 0         0 for my $argon2 (argon2_types) {
91 0         0 push @result, $argon2, map { "$argon2-encrypted-$_" } @supported
  0         0  
92             }
93 0         0 return @result;
94             }
95              
96             sub verify_password {
97 7     7 1 787 my ($self, $password, $pwhash) = @_;
98 7 100       46 if (my ($subtype, $alg, $id, $m_got, $t_got, $parallel_got, $salt, $hash) = _unpack_hash($pwhash)) {
    50          
99 6 50       15 my $raw = eval { argon2_raw($subtype, $password, $salt, $t_got, $m_got, $parallel_got, length $hash) } or return !!0;
  6         1373271  
100 6 50       34 my $decrypted = eval { $self->decrypt_hash($alg, $id, $salt, $hash) } or return !!0;
  6         66  
101              
102 6         602 return $self->secure_compare($decrypted, $raw);
103             }
104             elsif ($pwhash =~ $unencrypted_regex) {
105 1         199978 return argon2_verify($pwhash, $password);
106             }
107             }
108              
109             #ABSTRACT: A base-class for encrypting/peppered Argon2 encoders for Crypt::Passphrase
110              
111             __END__