File Coverage

blib/lib/File/KDBX/Cipher/Stream.pm
Criterion Covered Total %
statement 70 77 90.9
branch 19 26 73.0
condition 8 19 42.1
subroutine 18 22 81.8
pod 12 12 100.0
total 127 156 81.4


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   3571 use warnings;
  8         16  
  8         225  
5 8     8   37 use strict;
  8         11  
  8         207  
6              
7 8     8   378 use Crypt::Digest qw(digest_data);
  8         644  
  8         368  
8 8     8   49 use File::KDBX::Constants qw(:cipher :random_stream);
  8         15  
  8         916  
9 8     8   52 use File::KDBX::Error;
  8         42  
  8         340  
10 8     8   42 use File::KDBX::Util qw(:class);
  8         13  
  8         869  
11 8     8   47 use Scalar::Util qw(blessed);
  8         11  
  8         365  
12 8     8   41 use Module::Load;
  8         21  
  8         46  
13 8     8   371 use namespace::clean;
  8         13  
  8         48  
14              
15             extends 'File::KDBX::Cipher';
16              
17 226 50   226 1 724 our $VERSION = '0.904'; # VERSION
18 226 50   226 1 581  
19 226   66     762  
20 226   100     878 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         400 my %args = @_;
29              
30 132 100       535 if (my $uuid = $args{uuid}) {
    50          
31 31 100 66     180 if ($uuid eq CIPHER_UUID_CHACHA20 && length($args{iv}) == 16) {
    50          
32             # extract the counter
33 30         127 my $buf = substr($self->{iv}, 0, 4, '');
34 30         111 $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       323 my $key_ref = ref $args{key} ? $args{key} : \$args{key};
43 101 100       353 if ($id == STREAM_ID_CHACHA20) {
    50          
44 83         744 ($self->{key}, $self->{iv}) = unpack('a32 a12', digest_data('SHA512', $$key_ref));
45             }
46             elsif ($id == STREAM_ID_SALSA20) {
47 18         198 ($self->{key}, $self->{iv}) = (digest_data('SHA256', $$key_ref), STREAM_SALSA20_IV);
48             }
49             }
50              
51 132         1017 return $self;
52             }
53              
54              
55             sub crypt {
56 309     309 1 426 my $self = shift;
57 309         586 my $stream = $self->_stream;
58 309 100       604 return join('', map { $stream->crypt(ref $_ ? $$_ : $_) } grep { defined } @_);
  309         2370  
  309         734  
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 138 my $self = shift;
70 91         229 my $class = blessed($self);
71              
72 91         564 my $dup = bless {%$self, @_}, $class;
73 91         198 delete $dup->{stream};
74 91         176 return $dup;
75             }
76              
77             sub _stream {
78 309     309   369 my $self = shift;
79              
80 309   66     1154 $self->{stream} //= do {
81 226         309 my $s = eval {
82 226         574 my $pkg = 'Crypt::Stream::'.$self->algorithm;
83 226         451 my $counter = $self->counter;
84 226         301 my $pos = 0;
85 226 100       457 if (defined (my $offset = $self->offset)) {
86 91         246 $counter = int($offset / 64);
87 91         139 $pos = $offset % 64;
88             }
89 226         585 my $s = $pkg->new($self->key, $self->iv, $counter);
90             # seek to correct position within block
91 226 100       496 $s->keystream($pos) if $pos;
92 226         402 $s;
93             };
94 226 50       478 if (my $err = $@) {
95             throw 'Failed to initialize stream cipher library',
96             error => $err,
97             algorithm => $self->{algorithm},
98 0         0 key_length => length($self->key),
99             iv_length => length($self->iv),
100             iv => unpack('H*', $self->iv),
101             key => unpack('H*', $self->key);
102             }
103 226         627 $s;
104             };
105             }
106              
107 72     72 1 194 sub encrypt { goto &crypt }
108 18     18 1 70 sub decrypt { goto &crypt }
109              
110 218     218 1 676 sub finish { delete $_[0]->{stream}; '' }
  218         428  
111              
112             1;
113              
114             __END__