File Coverage

blib/lib/File/KDBX/KDF/AES.pm
Criterion Covered Total %
statement 52 68 76.4
branch 7 18 38.8
condition 5 12 41.6
subroutine 15 15 100.0
pod 3 3 100.0
total 82 116 70.6


line stmt bran cond sub pod time code
1             package File::KDBX::KDF::AES;
2             # ABSTRACT: Using the AES cipher as a key derivation function
3              
4 11     11   5283 use warnings;
  11         43  
  11         337  
5 11     11   255 use strict;
  11         28  
  11         231  
6              
7 11     11   2856 use Crypt::Cipher;
  11         1704  
  11         293  
8 11     11   713 use Crypt::Digest qw(digest_data);
  11         1229  
  11         477  
9 11     11   56 use File::KDBX::Constants qw(:bool :kdf);
  11         25  
  11         1911  
10 11     11   65 use File::KDBX::Error;
  11         16  
  11         474  
11 11     11   54 use File::KDBX::Util qw(:class :load can_fork);
  11         18  
  11         1249  
12 11     11   69 use namespace::clean;
  11         33  
  11         61  
13              
14             extends 'File::KDBX::KDF';
15              
16             our $VERSION = '0.905'; # VERSION
17              
18             # Rounds higher than this are eligible for forking:
19             my $FORK_OPTIMIZATION_THRESHOLD = 100_000;
20              
21             BEGIN {
22 11   33 11   5646 my $use_fork = $ENV{NO_FORK} || !can_fork;
23 11 50       5922 *_USE_FORK = $use_fork ? \&TRUE : \&FALSE;
24             }
25              
26              
27 42 50   42 1 116 sub rounds { $_[0]->{+KDF_PARAM_AES_ROUNDS} || KDF_DEFAULT_AES_ROUNDS }
28 56     56 1 115 sub seed { $_[0]->{+KDF_PARAM_AES_SEED} }
29              
30             sub init {
31 158     158 1 231 my $self = shift;
32 158         378 my %args = @_;
33             return $self->SUPER::init(
34             KDF_PARAM_AES_ROUNDS() => $args{+KDF_PARAM_AES_ROUNDS} // $args{rounds},
35             KDF_PARAM_AES_SEED() => $args{+KDF_PARAM_AES_SEED} // $args{seed},
36 158   66     747 );
      66        
37             }
38              
39             sub _transform {
40 42     42   76 my $self = shift;
41 42         65 my $key = shift;
42              
43 42         110 my $seed = $self->seed;
44 42         94 my $rounds = $self->rounds;
45              
46 42 100       108 length($key) == 32 or throw 'Raw key must be 32 bytes', size => length($key);
47 40 50       78 length($seed) == 32 or throw 'Invalid seed length', size => length($seed);
48              
49 40         176 my ($key_l, $key_r) = unpack('(a16)2', $key);
50              
51 40         98 goto NO_FORK if !_USE_FORK || $rounds < $FORK_OPTIMIZATION_THRESHOLD;
52             {
53 0   0     0 my $pid = open(my $read, '-|') // do { alert "fork failed: $!"; goto NO_FORK };
  0         0  
  0         0  
  0         0  
54 0 0       0 if ($pid == 0) { # child
55 0         0 my $l = _transform_half($seed, $key_l, $rounds);
56 0         0 require POSIX;
57 0 0       0 print $l or POSIX::_exit(1);
58 0         0 POSIX::_exit(0);
59             }
60 0         0 my $r = _transform_half($seed, $key_r, $rounds);
61 0 0       0 read($read, my $l, length($key_l)) == length($key_l) or do { alert "read failed: $!", goto NO_FORK };
  0         0  
62 0 0       0 close($read) or do { alert "worker thread exited abnormally", status => $?; goto NO_FORK };
  0         0  
  0         0  
63 0         0 return digest_data('SHA256', $l, $r);
64             }
65              
66             # FIXME: This used to work but now it crashes frequently. Threads are now discouraged anyway, but it might
67             # be nice if this was available for no-fork platforms.
68             # if ($ENV{THREADS} && eval 'use threads; 1') {
69             # my $l = threads->create(\&_transform_half, $key_l, $seed, $rounds);
70             # my $r = _transform_half($key_r, $seed, $rounds);
71             # return digest_data('SHA256', $l->join, $r);
72             # }
73              
74             NO_FORK:
75 40         90236 my $l = _transform_half($seed, $key_l, $rounds);
76 40         89512 my $r = _transform_half($seed, $key_r, $rounds);
77 40         357 return digest_data('SHA256', $l, $r);
78             }
79              
80             sub _transform_half_pp {
81 2     2   3 my $seed = shift;
82 2         3 my $key = shift;
83 2         2 my $rounds = shift;
84              
85 2         38 my $c = Crypt::Cipher->new('AES', $seed);
86              
87 2         5 my $result = $key;
88 2         5 for (my $i = 0; $i < $rounds; ++$i) {
89 20         55 $result = $c->encrypt($result);
90             }
91              
92 2         19 return $result;
93             }
94              
95             BEGIN {
96 11     11   69 my $use_xs = load_xs;
97 11 100       311 *_transform_half = $use_xs ? \&File::KDBX::XS::kdf_aes_transform_half : \&_transform_half_pp;
98             }
99              
100             1;
101              
102             __END__