File Coverage

blib/lib/Authen/Passphrase/Argon2.pm
Criterion Covered Total %
statement 95 95 100.0
branch 42 42 100.0
condition 3 3 100.0
subroutine 27 27 100.0
pod 15 15 100.0
total 182 182 100.0


line stmt bran cond sub pod time code
1             package Authen::Passphrase::Argon2;
2              
3 6     6   414452 use 5.014;
  6         64  
4 6     6   31 use strict;
  6         10  
  6         162  
5 6     6   32 use warnings;
  6         12  
  6         255  
6 6     6   3018 use Crypt::Argon2 qw/argon2id_pass argon2id_verify/;
  6         3276  
  6         373  
7 6     6   2857 use MIME::Base64 qw(decode_base64 encode_base64);
  6         3870  
  6         407  
8 6     6   2872 use Data::GUID;
  6         107806  
  6         40  
9 6     6   1165 use Carp qw/croak/;
  6         15  
  6         307  
10 6     6   3160 use Syntax::Construct qw( ?<> /a );
  6         16432  
  6         39  
11              
12 6     6   3358 use parent 'Authen::Passphrase';
  6         1705  
  6         35  
13              
14             our $VERSION = '1.00';
15              
16             our (%salts, %hashes, @argons);
17             BEGIN {
18 6     6   42 @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 16 100       54 $_[0] = $salts{salt_random}() if "$_[0]" eq 'random';
28 16 100       1209 $_[0] =~ m#\A[\x00-\xff]{6}.*\z#
29             or croak sprintf("%s is not a valid raw salt", $_[0]);
30 13         45 $_[0];
31             },
32             salt_hex => sub {
33 2         5 $_[0] = $salts{salt}($_[0]);
34 2         7 unpack("H*", $_[0]);
35             },
36             salt_base64 => sub {
37 2         5 $_[0] = $salts{salt}($_[0]);
38 2         6 encode_base64($_[0]);
39             },
40             salt_random => sub {
41 2         14 Data::GUID->new->as_string;
42             },
43 6         58 );
44             %hashes = (
45             hash => sub {
46 15 100       596 $_[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 6     6   30321 ("$_[0]", %+);
  6         2334  
  6         796  
  11         196  
55             },
56             hash_base64 => sub {
57 5         16 ($_[0]) = $hashes{hash}($_[0]);
58 4         153 encode_base64($_[0]);
59             },
60             hash_hex => sub {
61 5         17 ($_[0]) = $hashes{hash}($_[0]);
62 4         40 unpack("H*", $_[0]);
63             }
64 6         6918 );
65             }
66              
67             sub new {
68 22 100   22 1 5102 my ($class, %args) = (shift, (scalar @_ == 1 ? %{$_[0]} : @_));
  1         7  
69              
70 22         123 my $self = bless({ algorithm => 'Argon2' }, $class);
71              
72 22 100       86 $args{crypt} = delete $args{passphrase} if $args{passphrase};
73              
74             exists $args{$_} ? ! $self->{salt}
75 6         27 ? do { $self->{salt} = $salts{$_}->($args{$_}) }
76             : croak sprintf "salt specified redundantly - %s", $_
77 22 100       169 : next foreach (keys %salts);
    100          
78              
79 21         68 foreach (qw/hash base64 hex/) {
80 63 100       195 if (exists $args{"stored_$_"}) {
81 1         8 ($args{"hash_$_"}) = delete $args{crypt};
82 1         5 last;
83             }
84             }
85              
86             $self->{$_->[0]} = $self->{$_->[0]}
87             || $args{$_->[0]}
88             || $_->[1]
89 21   100     440 foreach @argons;
90              
91 21 100       59 unless ($args{crypt}) {
92             exists $args{$_} ? ! $self->{crypt}
93             ? $self->$_($args{$_})
94             : croak sprintf "hash specified redundantly - %s", $_
95 16 100       82 : next foreach (keys %hashes);
    100          
96             } else {
97 5         27 $self->{crypt} = $self->_hash_of($args{crypt});
98             }
99              
100 20         137 return $self;
101             }
102              
103             sub match {
104 10     10 1 40 my ($self, $passphrase) = @_;
105              
106 10         2340472 return argon2id_verify($self->{crypt}, $passphrase);
107             }
108              
109             sub from_crypt {
110 3     3 1 693 my ($self, $crypt, $info) = (@_, {});
111 3         7 return $self->new(%{ $info }, 'passphrase', $hashes{hash}($crypt));
  3         20  
112             }
113              
114             sub from_rfc2307 {
115 2     2 1 11 my ( $class, $rfc2307, $info) = (@_, {});
116              
117 2 100       275 croak "invalid Argon2 RFC2307 format" unless $rfc2307 =~ m/^{ARGON2}(.*)$/;
118              
119 1         5 return $class->from_crypt($1, $info);
120             }
121              
122             sub as_crypt {
123 16     16 1 62 my ($self, $val) = @_;
124              
125 16 100       69 $self->{crypt} = $self->_hash_of($val) if defined $val;
126              
127 16         79 return $self->{crypt};
128             }
129              
130             sub as_hex {
131 1     1 1 6 goto &hash_hex;
132             }
133              
134             sub as_base64 {
135 1     1 1 8 goto &hash_base64;
136             }
137              
138             sub as_rfc2307 {
139 2     2 1 23 my ($self, $val) = @_;
140              
141 2         10 return '{ARGON2}' . $self->as_crypt($val);
142             }
143              
144             sub algorithm {
145 2     2 1 22 my($self) = @_;
146              
147 2         18 return $self->{algorithm};
148             }
149              
150             sub salt {
151 16     16 1 57 my($self, $val) = @_;
152 16 100       44 $self->{salt} = $salts{salt}($val) if $val;
153 13         51 $self->{salt};
154             }
155              
156             sub salt_hex {
157 3     3 1 15 my($self, $val) = @_;
158 3 100       15 $salts{salt_hex}($self->salt($val ? pack("H*", $val) : ()));
159             }
160              
161             sub salt_base64 {
162 3     3 1 15 my($self, $val) = @_;
163 3 100       13 $salts{salt_base64}($self->salt($val ? decode_base64($val) : ()));
164             }
165              
166             sub hash {
167 8     8 1 39 my($self, $val) = @_;
168 8 100       48 return $val ? $self->as_crypt($hashes{hash}($val)) : $self->{crypt};
169             }
170              
171             sub hash_hex {
172 5     5 1 25 my($self, $val) = @_;
173 5 100       38 return $hashes{hash_hex}($self->as_crypt($val ? pack("H*", $val) : ()));
174             }
175              
176             sub hash_base64 {
177 5     5 1 23 my($self, $val) = @_;
178 5 100       25 return $hashes{hash_base64}($self->as_crypt($val ? decode_base64($val) : ()));
179             }
180              
181             sub _hash_of {
182 13     13   36 my ($self, $pass, @params) = @_;
183 13 100       69 return $pass if ($pass =~ m/\$argon2/);
184 4 100       274 !$self->{$_->[0]} ? croak "$_->[0] not set" : push @params, $self->{$_->[0]} for @argons;
185 3         709192 return argon2id_pass($pass, @params);
186             }
187              
188             1;
189              
190             __END__