File Coverage

blib/lib/Crypt/Passphrase/Argon2/Encrypted.pm
Criterion Covered Total %
statement 68 76 89.4
branch 15 20 75.0
condition 3 9 33.3
subroutine 14 15 93.3
pod 6 6 100.0
total 106 126 84.1


line stmt bran cond sub pod time code
1             package Crypt::Passphrase::Argon2::Encrypted;
2             $Crypt::Passphrase::Argon2::Encrypted::VERSION = '0.009';
3 1     1   70966 use strict;
  1         11  
  1         27  
4 1     1   17 use warnings;
  1         3  
  1         41  
5              
6 1     1   466 use Crypt::Passphrase 0.010 -encoder;
  1         15124  
  1         7  
7 1     1   7838 use Crypt::Passphrase::Argon2;
  1         4  
  1         33  
8              
9 1     1   7 use Carp 'croak';
  1         2  
  1         52  
10 1     1   5 use Crypt::Argon2 0.017 qw/argon2_raw argon2_verify argon2_types/;
  1         18  
  1         49  
11 1     1   439 use MIME::Base64 qw/encode_base64 decode_base64/;
  1         658  
  1         1092  
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 123 my ($class, %args) = @_;
21 2         14 my $self = bless Crypt::Passphrase::Argon2::_settings_for(%args), $class;
22 2         35 $self->{memory_cost} =~ s/ \A (\d+) ([kMG]) \z / $1 * $multiplier{$2} /xe;
  2         18  
23 2         7 $self->{cipher} = $args{cipher};
24 2         6 $self->{active} = $args{active};
25 2         10 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   37 my ($subtype, $cipher, $id, $m_cost, $t_cost, $parallel, $salt, $hash) = @_;
32 7         47 my $encoded_salt = encode_base64($salt, '') =~ tr/=//dr;
33 7         31 my $encoded_hash = encode_base64($hash, '') =~ tr/=//dr;
34 7         117 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   35 my ($pwhash) = @_;
41 14 100       365 my ($subtype, $alg, $m_cost, $t_cost, $parallel, $id, $encoded_salt, $encoded_hash) = $pwhash =~ $regex or return;
42 11         51 my $salt = decode_base64($encoded_salt);
43 11         39 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 9 my ($self, $input, $to) = @_;
50 2   33     18 $to //= $self->{active};
51 2 100       6 if (my ($subtype, $alg, $id, $m_cost, $t_cost, $parallel, $salt, $hash) = _unpack_hash($input)) {
    50          
52 1 50 33     7 return $input if $id eq $to and $alg eq $self->{cipher};
53 1         6 my $decrypted = $self->decrypt_hash($alg, $id, $salt, $hash);
54 1         63 my $encrypted = $self->encrypt_hash($self->{cipher}, $to, $salt, $decrypted);
55 1         87 return _pack_hash($subtype, $self->{cipher}, $to, $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         7 my $salt = decode_base64($encoded_salt);
59 1         4 my $hash = decode_base64($encoded_hash);
60 1         7 my $encrypted = $self->encrypt_hash($self->{cipher}, $to, $salt, $hash);
61 1         70 return _pack_hash($subtype, $self->{cipher}, $to, $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 12 my ($self, $password) = @_;
70              
71 1         9 my $salt = $self->random_bytes($self->{salt_size});
72 1         10431 my $raw = argon2_raw($self->{subtype}, $password, $salt, @{$self}{qw/time_cost memory_cost parallelism output_size/});
  1         261911  
73 1         29 my $encrypted = $self->encrypt_hash($self->{cipher}, $self->{active}, $salt, $raw);
74              
75 1         90 return _pack_hash(@{$self}{qw/subtype cipher active memory_cost time_cost parallelism/}, $salt, $encrypted);
  1         8  
76             }
77              
78             sub needs_rehash {
79 5     5 1 2467 my ($self, $pwhash) = @_;
80 5 100       26 my ($subtype, $alg, $id, $m_cost, $t_cost, $parallel, $salt, $hash) = _unpack_hash($pwhash) or return 1;
81 4 100       19 return 1 if $pwhash ne _pack_hash(@{$self}{qw/subtype cipher active memory_cost time_cost parallelism/}, $salt, $hash);
  4         22  
82 3   33     41 return length $salt != $self->{salt_size} || length $hash != $self->{output_size};
83             }
84              
85             sub crypt_subtypes {
86 0     0 1 0 my $self = shift;
87 0         0 my @result;
88 0         0 my @supported = $self->supported_ciphers;
89 0         0 for my $argon2 (argon2_types) {
90 0         0 push @result, $argon2, map { "$argon2-encrypted-$_" } @supported
  0         0  
91             }
92 0         0 return @result;
93             }
94              
95             sub verify_password {
96 7     7 1 725 my ($self, $password, $pwhash) = @_;
97 7 100       43 if (my ($subtype, $alg, $id, $m_got, $t_got, $parallel_got, $salt, $hash) = _unpack_hash($pwhash)) {
    50          
98 6 50       14 my $raw = eval { argon2_raw($subtype, $password, $salt, $t_got, $m_got, $parallel_got, length $hash) } or return !!0;
  6         1600234  
99 6 50       66 my $decrypted = eval { $self->decrypt_hash($alg, $id, $salt, $hash) } or return !!0;
  6         74  
100              
101 6         700 return $self->secure_compare($decrypted, $raw);
102             }
103             elsif ($pwhash =~ $unencrypted_regex) {
104 1         266932 return argon2_verify($pwhash, $password);
105             }
106             }
107              
108             #ABSTRACT: A base-class for encrypting/peppered Argon2 encoders for Crypt::Passphrase
109              
110             __END__