File Coverage

blib/lib/File/KDBX/Cipher/Stream.pm
Criterion Covered Total %
statement 71 78 91.0
branch 19 26 73.0
condition 8 19 42.1
subroutine 18 22 81.8
pod 12 12 100.0
total 128 157 81.5


line stmt bran cond sub pod time code
1             package File::KDBX::Cipher::Stream;
2             # ABSTRACT: A cipher stream encrypter/decrypter
3              
4 8     8   3753 use warnings;
  8         14  
  8         255  
5 8     8   43 use strict;
  8         13  
  8         206  
6              
7 8     8   410 use Crypt::Digest qw(digest_data);
  8         704  
  8         309  
8 8     8   41 use File::KDBX::Constants qw(:cipher :random_stream);
  8         15  
  8         1052  
9 8     8   47 use File::KDBX::Error;
  8         43  
  8         339  
10 8     8   48 use File::KDBX::Util qw(:class);
  8         15  
  8         700  
11 8     8   48 use Scalar::Util qw(blessed);
  8         11  
  8         370  
12 8     8   43 use Module::Load;
  8         13  
  8         77  
13 8     8   341 use namespace::clean;
  8         28  
  8         49  
14              
15             extends 'File::KDBX::Cipher';
16              
17 226 50   226 1 635 our $VERSION = '0.905'; # VERSION
18 226 50   226 1 514  
19 226   66     661  
20 226   100     740 has 'counter', is => 'ro', default => 0;
21             has 'offset', is => 'ro';
22 0   0 0 1 0 sub key_size { { Salsa20 => 32, ChaCha => 32 }->{$_[0]->{algorithm} || ''} // 0 }
      0        
23 0   0 0 1 0 sub iv_size { { Salsa20 => 8, ChaCha => 12 }->{$_[0]->{algorithm} || ''} // -1 }
      0        
24 0     0 1 0 sub block_size { 1 }
25              
26             sub init {
27 132     132 1 217 my $self = shift;
28 132         332 my %args = @_;
29              
30 132 100       438 if (my $uuid = $args{uuid}) {
    50          
31 31 100 66     155 if ($uuid eq CIPHER_UUID_CHACHA20 && length($args{iv}) == 16) {
    50          
32             # extract the counter
33 30         89 my $buf = substr($self->{iv}, 0, 4, '');
34 30         106 $self->{counter} = unpack('L<', $buf);
35             }
36             elsif ($uuid eq CIPHER_UUID_SALSA20) {
37             # only need eight bytes...
38 0         0 $self->{iv} = substr($args{iv}, 8);
39             }
40             }
41             elsif (my $id = $args{stream_id}) {
42 101 50       227 my $key_ref = ref $args{key} ? $args{key} : \$args{key};
43 101 100       287 if ($id == STREAM_ID_CHACHA20) {
    50          
44 83         720 ($self->{key}, $self->{iv}) = unpack('a32 a12', digest_data('SHA512', $$key_ref));
45             }
46             elsif ($id == STREAM_ID_SALSA20) {
47 18         174 ($self->{key}, $self->{iv}) = (digest_data('SHA256', $$key_ref), STREAM_SALSA20_IV);
48             }
49             }
50              
51 132         953 return $self;
52             }
53              
54              
55             sub crypt {
56 309     309 1 407 my $self = shift;
57 309         575 my $stream = $self->_stream;
58 309 100       590 return join('', map { $stream->crypt(ref $_ ? $$_ : $_) } grep { defined } @_);
  309         2249  
  309         698  
59             }
60              
61              
62             sub keystream {
63 0     0 1 0 my $self = shift;
64 0         0 return $self->_stream->keystream(@_);
65             }
66              
67              
68             sub dup {
69 91     91 1 131 my $self = shift;
70 91         220 my $class = blessed($self);
71              
72 91         540 my $dup = bless {%$self, @_}, $class;
73 91         173 delete $dup->{stream};
74 91         180 return $dup;
75             }
76              
77             sub _stream {
78 309     309   379 my $self = shift;
79              
80 309   66     641 $self->{stream} //= do {
81 226         293 my $s = eval {
82 226         490 my $pkg = 'Crypt::Stream::'.$self->algorithm;
83 226         478 my $counter = $self->counter;
84 226         306 my $pos = 0;
85 226 100       408 if (defined (my $offset = $self->offset)) {
86 91         226 $counter = int($offset / 64);
87 91         138 $pos = $offset % 64;
88             }
89 226         673 load $pkg;
90 226         18089 my $s = $pkg->new($self->key, $self->iv, $counter);
91             # seek to correct position within block
92 226 100       520 $s->keystream($pos) if $pos;
93 226         358 $s;
94             };
95 226 50       442 if (my $err = $@) {
96             throw 'Failed to initialize stream cipher library',
97             error => $err,
98             algorithm => $self->{algorithm},
99 0         0 key_length => length($self->key),
100             iv_length => length($self->iv),
101             iv => unpack('H*', $self->iv),
102             key => unpack('H*', $self->key);
103             }
104 226         667 $s;
105             };
106             }
107              
108 72     72 1 178 sub encrypt { goto &crypt }
109 18     18 1 55 sub decrypt { goto &crypt }
110              
111 218     218 1 597 sub finish { delete $_[0]->{stream}; '' }
  218         457  
112              
113             1;
114              
115             __END__