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   467296 use 5.006;
  6         55  
4 6     6   29 use strict;
  6         9  
  6         108  
5 6     6   33 use warnings;
  6         9  
  6         212  
6 6     6   11279 use Crypt::Argon2 qw/argon2id_pass argon2id_verify/;
  6         2933  
  6         353  
7 6     6   2276 use MIME::Base64 qw(decode_base64 encode_base64);
  6         3379  
  6         296  
8 6     6   2215 use Data::GUID;
  6         104023  
  6         34  
9 6     6   1151 use Carp qw/croak/;
  6         13  
  6         284  
10 6     6   2693 use Syntax::Construct qw( ?<> /a );
  6         7497  
  6         32  
11              
12 6     6   2800 use parent 'Authen::Passphrase';
  6         1393  
  6         121  
13              
14             our $VERSION = '0.06';
15              
16             our (%salts, %hashes, @argons);
17             BEGIN {
18 6     6   35 @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       47 $_[0] = $salts{salt_random}() if "$_[0]" eq 'random';
28 17 100       1120 $_[0] =~ m#\A[\x00-\xff]{6}.*\z#
29             or croak sprintf("%s is not a valid raw salt", $_[0]);
30 14         135 $_[0];
31             },
32             salt_hex => sub {
33 2         5 $_[0] = $salts{salt}($_[0]);
34 2         6 unpack("H*", $_[0]);
35             },
36             salt_base64 => sub {
37 2         4 $_[0] = $salts{salt}($_[0]);
38 2         6 encode_base64($_[0]);
39             },
40             salt_random => sub {
41 1         11 Data::GUID->new->as_string;
42             },
43 6         45 );
44             %hashes = (
45             hash => sub {
46 15 100       519 $_[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   24321 ("$_[0]", %+);
  6         2155  
  6         704  
  11         165  
55             },
56             hash_base64 => sub {
57 5         14 ($_[0]) = $hashes{hash}($_[0]);
58 4         128 encode_base64($_[0]);
59             },
60             hash_hex => sub {
61 5         15 ($_[0]) = $hashes{hash}($_[0]);
62 4         32 unpack("H*", $_[0]);
63             }
64 6         5585 );
65             }
66              
67             sub new {
68 22 100   22 1 5201 my ($class, %args) = (shift, (scalar @_ == 1 ? %{$_[0]} : @_));
  1         6  
69              
70 22         103 my $self = bless({ algorithm => 'Argon2' }, $class);
71              
72 22 100       71 $args{crypt} = delete $args{passphrase} if $args{passphrase};
73              
74             exists $args{$_} ? ! $self->{salt}
75 6         18 ? do { $self->{salt} = $salts{$_}->($args{$_}) }
76             : croak sprintf "salt specified redundantly - %s", $_
77 22 100       141 : next foreach (keys %salts);
    100          
78              
79 21         51 foreach (qw/hash base64 hex/) {
80 63 100       136 if (exists $args{"stored_$_"}) {
81 1         4 ($args{"hash_$_"}) = delete $args{crypt};
82 1         2 last;
83             }
84             }
85              
86             $self->{$_->[0]} = $self->{$_->[0]}
87             || $args{$_->[0]}
88             || $_->[1]
89 21   100     370 foreach @argons;
90              
91 21 100       51 unless ($args{crypt}) {
92             exists $args{$_} ? ! $self->{crypt}
93             ? $self->$_($args{$_})
94             : croak sprintf "hash specified redundantly - %s", $_
95 16 100       71 : next foreach (keys %hashes);
    100          
96             } else {
97 5         19 $self->{crypt} = $self->_hash_of($args{crypt});
98             }
99              
100 20         92 return $self;
101             }
102              
103             sub match {
104 10     10 1 33 my ($self, $passphrase) = @_;
105              
106 10         2009201 return argon2id_verify($self->{crypt}, $passphrase);
107             }
108              
109             sub from_crypt {
110 3     3 1 620 my ($self, $crypt, $info) = (@_, {});
111 3         8 return $self->new(%{ $info }, 'passphrase', $hashes{hash}($crypt));
  3         15  
112             }
113              
114             sub from_rfc2307 {
115 2     2 1 9 my ( $class, $rfc2307, $info) = (@_, {});
116              
117 2 100       194 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 48 my ($self, $val) = @_;
124              
125 16 100       58 $self->{crypt} = $self->_hash_of($val) if defined $val;
126              
127 16         63 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 5 goto &hash_base64;
136             }
137              
138             sub as_rfc2307 {
139 2     2 1 14 my ($self, $val) = @_;
140              
141 2         9 return '{ARGON2}' . $self->as_crypt($val);
142             }
143              
144             sub algorithm {
145 2     2 1 18 my($self) = @_;
146              
147 2         14 return $self->{algorithm};
148             }
149              
150             sub salt {
151 16     16 1 50 my($self, $val) = @_;
152 16 100       36 $self->{salt} = $salts{salt}($val) if $val;
153 13         50 $self->{salt};
154             }
155              
156             sub salt_hex {
157 3     3 1 12 my($self, $val) = @_;
158 3 100       14 $salts{salt_hex}($self->salt($val ? pack("H*", $val) : ()));
159             }
160              
161             sub salt_base64 {
162 3     3 1 12 my($self, $val) = @_;
163 3 100       12 $salts{salt_base64}($self->salt($val ? decode_base64($val) : ()));
164             }
165              
166             sub hash {
167 8     8 1 33 my($self, $val) = @_;
168 8 100       82 return $val ? $self->as_crypt($hashes{hash}($val)) : $self->{crypt};
169             }
170              
171             sub hash_hex {
172 5     5 1 24 my($self, $val) = @_;
173 5 100       33 return $hashes{hash_hex}($self->as_crypt($val ? pack("H*", $val) : ()));
174             }
175              
176             sub hash_base64 {
177 5     5 1 19 my($self, $val) = @_;
178 5 100       24 return $hashes{hash_base64}($self->as_crypt($val ? decode_base64($val) : ()));
179             }
180              
181             sub _hash_of {
182 13     13   30 my ($self, $pass, @params) = @_;
183 13 100       56 return $pass if ($pass =~ m/\$argon2/);
184 4 100       237 !$self->{$_->[0]} ? croak "$_->[0] not set" : push @params, $self->{$_->[0]} for @argons;
185 3         573813 return argon2id_pass($pass, @params);
186             }
187              
188             1;
189              
190             __END__