File Coverage

blib/lib/Authen/Passphrase/Argon2.pm
Criterion Covered Total %
statement 92 92 100.0
branch 42 42 100.0
condition 3 3 100.0
subroutine 26 26 100.0
pod 15 15 100.0
total 178 178 100.0


line stmt bran cond sub pod time code
1             package Authen::Passphrase::Argon2;
2              
3 6     6   756461 use 5.014;
  6         24  
4 6     6   34 use strict;
  6         12  
  6         204  
5 6     6   30 use warnings;
  6         33  
  6         509  
6 6     6   3402 use Crypt::Argon2 qw/argon2id_pass argon2id_verify/;
  6         7687  
  6         660  
7 6     6   3177 use MIME::Base64 qw(decode_base64 encode_base64);
  6         4827  
  6         498  
8 6     6   3385 use Data::GUID;
  6         154847  
  6         48  
9 6     6   1589 use Carp qw/croak/;
  6         18  
  6         466  
10 6     6   3937 use Syntax::Construct qw( ?<> /a );
  6         35862  
  6         47  
11              
12 6     6   758 use parent 'Authen::Passphrase';
  6         12  
  6         35  
13              
14             our $VERSION = '1.01';
15              
16             our (%salts, %hashes, @argons);
17             BEGIN {
18 6     6   33303 @argons = (
19             ['salt'],
20             [qw/cost 3/],
21             [qw/factor 32M/],
22             [qw/parallelism 1/],
23             [qw/size 16/]
24             );
25             %salts = (
26             salt => sub {
27 17 100       66 $_[0] = $salts{salt_random}() if "$_[0]" eq 'random';
28 17 100       1980 $_[0] =~ m#\A[\x00-\xff]{6}.*\z#
29             or croak sprintf("%s is not a valid raw salt", $_[0]);
30 14         217 $_[0];
31             },
32             salt_hex => sub {
33 2         7 $_[0] = $salts{salt}($_[0]);
34 2         8 unpack("H*", $_[0]);
35             },
36             salt_base64 => sub {
37 2         7 $_[0] = $salts{salt}($_[0]);
38 2         9 encode_base64($_[0]);
39             },
40             salt_random => sub {
41 1         15 Data::GUID->new->as_string;
42             },
43 6         74 );
44             %hashes = (
45             hash => sub {
46 15 100       926 $_[0] =~ m/^
47             \$argon2id
48             \$v=\d+
49             \$m=(?\d+),
50             t=(?\d+),
51             p=(?\d+)
52             \$
53             /ax or croak sprintf "not a valid raw hash - %s", $_[0];
54 11         231 ("$_[0]", %+);
55             },
56             hash_base64 => sub {
57 5         16 ($_[0]) = $hashes{hash}($_[0]);
58 4         223 encode_base64($_[0]);
59             },
60             hash_hex => sub {
61 5         19 ($_[0]) = $hashes{hash}($_[0]);
62 4         38 unpack("H*", $_[0]);
63             }
64 6         8036 );
65             }
66              
67             sub new {
68 22 100   22 1 999912 my ($class, %args) = (shift, (scalar @_ == 1 ? %{$_[0]} : @_));
  1         8  
69              
70 22         101 my $self = bless({ algorithm => 'Argon2' }, $class);
71              
72 22 100       109 $args{crypt} = delete $args{passphrase} if $args{passphrase};
73              
74             exists $args{$_} ? ! $self->{salt}
75 6         30 ? do { $self->{salt} = $salts{$_}->($args{$_}) }
76             : croak sprintf "salt specified redundantly - %s", $_
77 22 100       186 : next foreach (keys %salts);
    100          
78              
79 21         62 foreach (qw/hash base64 hex/) {
80 63 100       197 if (exists $args{"stored_$_"}) {
81 1         7 ($args{"hash_$_"}) = delete $args{crypt};
82 1         4 last;
83             }
84             }
85              
86             $self->{$_->[0]} = $self->{$_->[0]}
87             || $args{$_->[0]}
88             || $_->[1]
89 21   100     556 foreach @argons;
90              
91 21 100       84 unless ($args{crypt}) {
92             exists $args{$_} ? ! $self->{crypt}
93             ? $self->$_($args{$_})
94             : croak sprintf "hash specified redundantly - %s", $_
95 16 100       99 : next foreach (keys %hashes);
    100          
96             } else {
97 5         35 $self->{crypt} = $self->_hash_of($args{crypt});
98             }
99              
100 20         131 return $self;
101             }
102              
103             sub match {
104 10     10 1 35 my ($self, $passphrase) = @_;
105              
106 10         1395516 return argon2id_verify($self->{crypt}, $passphrase);
107             }
108              
109             sub from_crypt {
110 3     3 1 887 my ($self, $crypt, $info) = (@_, {});
111 3         8 return $self->new(%{ $info }, 'passphrase', $hashes{hash}($crypt));
  3         26  
112             }
113              
114             sub from_rfc2307 {
115 2     2 1 13 my ( $class, $rfc2307, $info) = (@_, {});
116              
117 2 100       230 croak "invalid Argon2 RFC2307 format" unless $rfc2307 =~ m/^{ARGON2}(.*)$/;
118              
119 1         6 return $class->from_crypt($1, $info);
120             }
121              
122             sub as_crypt {
123 16     16 1 78 my ($self, $val) = @_;
124              
125 16 100       68 $self->{crypt} = $self->_hash_of($val) if defined $val;
126              
127 16         121 return $self->{crypt};
128             }
129              
130             sub as_hex {
131 1     1 1 7 goto &hash_hex;
132             }
133              
134             sub as_base64 {
135 1     1 1 6 goto &hash_base64;
136             }
137              
138             sub as_rfc2307 {
139 2     2 1 19 my ($self, $val) = @_;
140              
141 2         12 return '{ARGON2}' . $self->as_crypt($val);
142             }
143              
144             sub algorithm {
145 2     2 1 23 my($self) = @_;
146              
147 2         19 return $self->{algorithm};
148             }
149              
150             sub salt {
151 16     16 1 73 my($self, $val) = @_;
152 16 100       54 $self->{salt} = $salts{salt}($val) if $val;
153 13         87 $self->{salt};
154             }
155              
156             sub salt_hex {
157 3     3 1 21 my($self, $val) = @_;
158 3 100       16 $salts{salt_hex}($self->salt($val ? pack("H*", $val) : ()));
159             }
160              
161             sub salt_base64 {
162 3     3 1 21 my($self, $val) = @_;
163 3 100       19 $salts{salt_base64}($self->salt($val ? decode_base64($val) : ()));
164             }
165              
166             sub hash {
167 8     8 1 45 my($self, $val) = @_;
168 8 100       88 return $val ? $self->as_crypt($hashes{hash}($val)) : $self->{crypt};
169             }
170              
171             sub hash_hex {
172 5     5 1 30 my($self, $val) = @_;
173 5 100       42 return $hashes{hash_hex}($self->as_crypt($val ? pack("H*", $val) : ()));
174             }
175              
176             sub hash_base64 {
177 5     5 1 27 my($self, $val) = @_;
178 5 100       31 return $hashes{hash_base64}($self->as_crypt($val ? decode_base64($val) : ()));
179             }
180              
181             sub _hash_of {
182 13     13   40 my ($self, $pass, @params) = @_;
183 13 100       87 return $pass if ($pass =~ m/\$argon2/);
184 4 100       334 !$self->{$_->[0]} ? croak "$_->[0] not set" : push @params, $self->{$_->[0]} for @argons;
185 3         436536 return argon2id_pass($pass, @params);
186             }
187              
188             1;
189              
190             __END__