File Coverage

blib/lib/Crypt/Passphrase/Argon2/Encrypted.pm
Criterion Covered Total %
statement 69 73 94.5
branch 15 20 75.0
condition 4 11 36.3
subroutine 14 15 93.3
pod 6 6 100.0
total 108 125 86.4


line stmt bran cond sub pod time code
1             package Crypt::Passphrase::Argon2::Encrypted;
2             $Crypt::Passphrase::Argon2::Encrypted::VERSION = '0.008';
3 1     1   69310 use strict;
  1         12  
  1         27  
4 1     1   6 use warnings;
  1         2  
  1         46  
5              
6 1     1   452 use Crypt::Passphrase 0.010 -encoder;
  1         14775  
  1         6  
7 1     1   7661 use Crypt::Passphrase::Argon2;
  1         3  
  1         34  
8              
9 1     1   7 use Carp 'croak';
  1         3  
  1         51  
10 1     1   6 use Crypt::Argon2 0.017 qw/argon2_raw argon2_verify argon2_types/;
  1         17  
  1         49  
11 1     1   446 use MIME::Base64 qw/encode_base64 decode_base64/;
  1         604  
  1         1091  
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 128 my ($class, %args) = @_;
21 2   50     17 $args{output_size} //= 32;
22 2         14 my $self = bless Crypt::Passphrase::Argon2::_settings_for(%args), $class;
23 2         29 $self->{memory_cost} =~ s/ \A (\d+) ([kMG]) \z / $1 * $multiplier{$2} /xe;
  2         17  
24 2         6 $self->{cipher} = $args{cipher};
25 2         8 $self->{active} = $args{active};
26 2         9 return $self;
27             }
28              
29             my $format = '$%s-encrypted$v=1,cipher=%s,id=%s$v=19$m=%d,t=%d,p=%d$%s$%s';
30              
31             sub _pack_hash {
32 7     7   40 my ($subtype, $cipher, $id, $m_cost, $t_cost, $parallel, $salt, $hash) = @_;
33 7         62 my $encoded_salt = encode_base64($salt, '') =~ tr/=//dr;
34 7         29 my $encoded_hash = encode_base64($hash, '') =~ tr/=//dr;
35 7         109 return sprintf $format, $subtype, $cipher, $id, $m_cost / 1024, $t_cost, $parallel, $encoded_salt, $encoded_hash;
36             }
37              
38             my $regex = qr/ ^ \$ ($Crypt::Argon2::type_regex)-encrypted \$ v=1, cipher=([^\$,]+) , id=([^\$,]+) \$ v=(\d+) \$ m=(\d+), t=(\d+), p=(\d+) \$ ([^\$]+) \$ (.*) $ /x;
39              
40             sub _unpack_hash {
41 12     12   31 my ($pwhash) = @_;
42 12 100       386 my ($subtype, $alg, $id, $version, $m_cost, $t_cost, $parallel, $encoded_salt, $encoded_hash) = $pwhash =~ $regex or return;
43 9         52 my $salt = decode_base64($encoded_salt);
44 9         27 my $hash = decode_base64($encoded_hash);
45 9         87 return ($subtype, $alg, $id, $version, $m_cost * 1024, $t_cost, $parallel, $salt, $hash);
46             }
47              
48             my $unencrypted_regex = qr/ ^ \$ ($Crypt::Argon2::type_regex) \$ v=(\d+) \$ m=(\d+), t=(\d+), p=(\d+) \$ ([^\$]+) \$ (.*) $ /x;
49             sub recrypt_hash {
50 2     2 1 10 my ($self, $input, $to) = @_;
51 2   33     20 $to //= $self->{active};
52 2 100       6 if (my ($subtype, $alg, $id, $version, $m_cost, $t_cost, $parallel, $salt, $hash) = _unpack_hash($input)) {
    50          
53 1 50 33     8 return $input if $id eq $to and $alg eq $self->{cipher};
54 1         5 my $decrypted = $self->decrypt_hash($alg, $id, $salt, $hash);
55 1         81 my $encrypted = $self->encrypt_hash($self->{cipher}, $to, $salt, $decrypted);
56 1         78 return _pack_hash($subtype, $self->{cipher}, $to, $m_cost, $t_cost, $parallel, $salt, $encrypted);
57             }
58             elsif (($subtype, $version, $m_cost, $t_cost, $parallel, my $encoded_salt, my $encoded_hash) = $input =~ $unencrypted_regex) {
59 1         8 my $salt = decode_base64($encoded_salt);
60 1         3 my $hash = decode_base64($encoded_hash);
61 1         9 my $encrypted = $self->encrypt_hash($self->{cipher}, $to, $salt, $hash);
62 1         73 return _pack_hash($subtype, $self->{cipher}, $to, $m_cost * 1024, $t_cost, $parallel, $salt, $encrypted);
63             }
64             else {
65 0         0 return $input;
66             }
67             }
68              
69             sub hash_password {
70 1     1 1 14 my ($self, $password) = @_;
71              
72 1         10 my $salt = $self->random_bytes($self->{salt_size});
73 1         10447 my $raw = argon2_raw($self->{subtype}, $password, $salt, @{$self}{qw/time_cost memory_cost parallelism output_size/});
  1         275684  
74 1         29 my $encrypted = $self->encrypt_hash($self->{cipher}, $self->{active}, $salt, $raw);
75              
76 1         92 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 2484 my ($self, $pwhash) = @_;
81 5 100       25 my ($subtype, $alg, $id, $version, $m_cost, $t_cost, $parallel, $salt, $hash) = _unpack_hash($pwhash) or return 1;
82 4 100       18 return 1 if $pwhash ne _pack_hash(@{$self}{qw/subtype cipher active memory_cost time_cost parallelism/}, $salt, $hash);
  4         24  
83 3   33     39 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 return map { ("$_-encrypted", $_) } argon2_types;
  0         0  
89             }
90              
91             sub verify_password {
92 5     5 1 57 my ($self, $password, $pwhash) = @_;
93 5 100       16 if (my ($subtype, $alg, $id, $version, $m_got, $t_got, $parallel_got, $salt, $hash) = _unpack_hash($pwhash)) {
    50          
94 4 50       10 my $raw = eval { argon2_raw($subtype, $password, $salt, $t_got, $m_got, $parallel_got, length $hash) } or return !!0;
  4         1113302  
95 4 50       39 my $decrypted = eval { $self->decrypt_hash($alg, $id, $salt, $hash) } or return !!0;
  4         57  
96              
97 4         485 return $self->secure_compare($decrypted, $raw);
98             }
99             elsif ($pwhash =~ $unencrypted_regex) {
100 1         267317 return argon2_verify($pwhash, $password);
101             }
102             }
103              
104             #ABSTRACT: A base-class for encrypting/peppered Argon2 encoders for Crypt::Passphrase
105              
106             __END__