File Coverage

blib/lib/File/KDBX/Cipher/Stream.pm
Criterion Covered Total %
statement 69 78 88.4
branch 18 26 69.2
condition 7 19 36.8
subroutine 18 22 81.8
pod 12 12 100.0
total 124 157 78.9


line stmt bran cond sub pod time code
1             package File::KDBX::Cipher::Stream;
2             # ABSTRACT: A cipher stream encrypter/decrypter
3              
4 9     9   4057 use warnings;
  9         19  
  9         258  
5 9     9   40 use strict;
  9         14  
  9         226  
6              
7 9     9   408 use Crypt::Digest qw(digest_data);
  9         634  
  9         346  
8 9     9   49 use File::KDBX::Constants qw(:cipher :random_stream);
  9         16  
  9         1064  
9 9     9   65 use File::KDBX::Error;
  9         47  
  9         410  
10 9     9   48 use File::KDBX::Util qw(:class);
  9         14  
  9         790  
11 9     9   56 use Scalar::Util qw(blessed);
  9         14  
  9         386  
12 9     9   49 use Module::Load;
  9         22  
  9         88  
13 9     9   405 use namespace::clean;
  9         21  
  9         43  
14              
15             extends 'File::KDBX::Cipher';
16              
17 198 50   198 1 612 our $VERSION = '0.906'; # VERSION
18 198 50   198 1 506  
19 198   66     617  
20 198   100     651 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 103     103 1 169 my $self = shift;
28 103         279 my %args = @_;
29              
30 103 100       389 if (my $uuid = $args{uuid}) {
    50          
31 1 50 33     15 if ($uuid eq CIPHER_UUID_CHACHA20 && length($args{iv}) == 16) {
    50          
32             # extract the counter
33 0         0 my $buf = substr($self->{iv}, 0, 4, '');
34 0         0 $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 102 50       244 my $key_ref = ref $args{key} ? $args{key} : \$args{key};
43 102 100       216 if ($id == STREAM_ID_CHACHA20) {
    50          
44 84         693 ($self->{key}, $self->{iv}) = unpack('a32 a12', digest_data('SHA512', $$key_ref));
45             }
46             elsif ($id == STREAM_ID_SALSA20) {
47 18         184 ($self->{key}, $self->{iv}) = (digest_data('SHA256', $$key_ref), STREAM_SALSA20_IV);
48             }
49             }
50              
51 103         802 return $self;
52             }
53              
54              
55             sub crypt {
56 229     229 1 360 my $self = shift;
57 229         393 my $stream = $self->_stream;
58 229 100       399 return join('', map { $stream->crypt(ref $_ ? $$_ : $_) } grep { defined } @_);
  229         1613  
  229         525  
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 134 my $self = shift;
70 91         213 my $class = blessed($self);
71              
72 91         552 my $dup = bless {%$self, @_}, $class;
73 91         178 delete $dup->{stream};
74 91         164 return $dup;
75             }
76              
77             sub _stream {
78 229     229   274 my $self = shift;
79              
80 229   66     535 $self->{stream} //= do {
81 198         260 my $s = eval {
82 198         457 my $pkg = 'Crypt::Stream::'.$self->algorithm;
83 198         406 my $counter = $self->counter;
84 198         328 my $pos = 0;
85 198 100       323 if (defined (my $offset = $self->offset)) {
86 91         262 $counter = int($offset / 64);
87 91         133 $pos = $offset % 64;
88             }
89 198         535 load $pkg;
90 198         17025 my $s = $pkg->new($self->key, $self->iv, $counter);
91             # seek to correct position within block
92 198 100       416 $s->keystream($pos) if $pos;
93 198         319 $s;
94             };
95 198 50       373 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 198         612 $s;
105             };
106             }
107              
108 2     2 1 5 sub encrypt { goto &crypt }
109 2     2 1 8 sub decrypt { goto &crypt }
110              
111 200     200 1 503 sub finish { delete $_[0]->{stream}; '' }
  200         345  
112              
113             1;
114              
115             __END__