1 package File
::KDBX
::IO
::HashBlock
;
2 # ABSTRACT: Hash block stream IO handle
7 use Crypt
::Digest
qw(digest_data);
10 use File
::KDBX
::Util
qw(:io);
14 use parent
'File::KDBX::IO';
16 our $VERSION = '999.999'; # VERSION
17 our $ALGORITHM = 'SHA256';
18 our $BLOCK_SIZE = 1048576; # 1MiB
23 $fh = File
::KDBX
::IO
::HashBlock-
>new(%attributes);
24 $fh = File
::KDBX
::IO
::HashBlock-
>new($fh, %attributes);
26 Construct a new hash-block stream IO handle
.
32 my %args = @_ % 2 == 1 ? (fh
=> shift, @_) : @_;
33 my $self = $class->SUPER::new
;
34 $self->_fh($args{fh
}) or throw
'IO handle required';
35 $self->algorithm($args{algorithm
});
36 $self->block_size($args{block_size
});
43 Digest algorithm
in hash-blocking the stream
(default: C
<SHA-256
>)
47 Desired block size
when writing
(default: C
<$File::KDBX
::IO
::HashBlock
::BLOCK_SIZE
> or 1,048,576 bytes
)
53 _buffer
=> \
(my $buf = ''),
55 algorithm
=> sub { $ALGORITHM },
56 block_size
=> sub { $BLOCK_SIZE },
58 while (my ($attr, $default) = each %ATTRS) {
59 no strict
'refs'; ## no critic (ProhibitNoStrict)
62 *$self->{$attr} = shift if @_;
63 *$self->{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default;
70 $ENV{DEBUG_STREAM
} and print STDERR
"FILL\t$self\n";
71 return if $self->_finished;
73 my $block = eval { $self->_read_hash_block($fh) };
75 $self->_set_error($err);
78 return $$block if defined $block;
82 my ($self, $buf, $fh) = @_;
84 $ENV{DEBUG_STREAM
} and print STDERR
"WRITE\t$self\n";
85 return 0 if $self->_finished;
87 ${$self->_buffer} .= $buf;
97 $ENV{DEBUG_STREAM
} and print STDERR
"POPPED\t$self\n";
98 return if $self->_mode ne 'w';
102 $self->_write_next_hash_block($fh); # partial block with remaining content
103 $self->_write_final_hash_block($fh); # terminating block
105 $self->_set_error($@) if $@;
109 my ($self, $fh) = @_;
111 $ENV{DEBUG_STREAM
} and print STDERR
"FLUSH\t$self\n";
112 return if $self->_mode ne 'w';
115 while ($self->block_size <= length(${*$self->{_buffer
}})) {
116 $self->_write_next_hash_block($fh);
120 $self->_set_error($err);
127 ##############################################################################
129 sub _read_hash_block
{
133 read_all
$fh, my $buf, 4 or throw
'Failed to read hash block index';
134 my ($index) = unpack('L<', $buf);
136 $index == $self->_block_index or throw
'Invalid block index', index => $index;
138 read_all
$fh, my $hash, 32 or throw
'Failed to read hash';
140 read_all
$fh, $buf, 4 or throw
'Failed to read hash block size';
141 my ($size) = unpack('L<', $buf);
144 $hash eq ("\0" x
32) or throw
'Invalid final block hash', hash
=> $hash;
149 read_all
$fh, my $block, $size or throw
'Failed to read hash block', index => $index, size
=> $size;
151 my $got_hash = digest_data
($self->algorithm, $block);
153 or throw
'Hash mismatch', index => $index, size
=> $size, got
=> $got_hash, expected
=> $hash;
155 *$self->{_block_index
}++;
159 sub _write_next_hash_block
{
163 my $size = length(${$self->_buffer});
164 $size = $self->block_size if $self->block_size < $size;
165 return 0 if $size == 0;
167 my $block = substr(${$self->_buffer}, 0, $size, '');
169 my $buf = pack('L<', $self->_block_index);
170 print $fh $buf or throw
'Failed to write hash block index';
172 my $hash = digest_data
($self->algorithm, $block);
173 print $fh $hash or throw
'Failed to write hash';
175 $buf = pack('L<', length($block));
176 print $fh $buf or throw
'Failed to write hash block size';
178 # $fh->write($block, $size) or throw 'Failed to hash write block';
179 print $fh $block or throw
'Failed to hash write block';
181 *$self->{_block_index
}++;
185 sub _write_final_hash_block
{
189 my $buf = pack('L<', $self->_block_index);
190 print $fh $buf or throw
'Failed to write hash block index';
192 my $hash = "\0" x
32;
193 print $fh $hash or throw
'Failed to write hash';
195 $buf = pack('L<', 0);
196 print $fh $buf or throw
'Failed to write hash block size';
204 $ENV{DEBUG_STREAM
} and print STDERR
"err\t$self\n";
205 if (exists &Errno
::EPROTO
) {
208 elsif (exists &Errno
::EIO
) {
211 $self->_error($ERROR = error
(@_));
219 Writing to a hash-block handle will transform the data into a series of blocks. Each block is hashed, and the
220 hash is included with the block in the stream.
222 Reading from a handle, each hash block will be verified as the blocks are disassembled back into a data
225 This format helps ensure data integrity of KDBX3 files.
227 Each block is encoded thusly:
230 * Block index - Little-endian unsigned 32-bit integer, increments starting with 0
232 * Block size - Little-endian unsigned 32-bit (counting only the data)
233 * Data - String of bytes
235 The terminating block is an empty block where hash is 32 null bytes, block size is 0 and there is no data.