Revision history for File-KDBX.
{{$NEXT}}
- * Fixed transform_rounds method to work with Argon KDF. Thanks HIGHTOWE.
- * Fixed bug preventing memory protection on new databases. Thanks HIGHTOWE.
+ * Fixed bug where dumping a fresh database could write wrong-sized encryption IV, making the resulting
+ serialization unreadable by some KeePass implementations. Thanks HIGHTOWE.
+ * Fixed bugs preventing the use of memory protection with fresh databases. Thanks HIGHTOWE.
+ * Fixed the transform_rounds method to work with Argon KDF; this now maps to the Argon iterations value if
+ the current KDF is Argon. Thanks HIGHTOWE.
0.905 2022-08-06 12:12:42-0600
* Declared Time::Local 1.19 as a required dependency.
* Added support for 32-bit perls.
* API change: Rename iterator accessors on group to all_*.
* Declared perl 5.10.0 prerequisite. I have no intention of supporting 5.8 or earlier.
- * Fixed more other broken tests -- thanks CPAN testers.
+ * Fixed more other broken tests. Thanks CPAN testers.
0.901 2022-05-02 01:18:13-0600
# copy constructor
return $_[0]->clone if @_ == 1 && blessed $_[0] && $_[0]->isa($class);
- my $self = bless {}, $class;
+ my $data;
+ $data = shift if is_plain_hashref($_[0]);
+
+ my $self = bless $data // {}, $class;
$self->init(@_);
- $self->_set_nonlazy_attributes if empty $self;
+ $self->_set_nonlazy_attributes if !$data;
return $self;
}
# HEADERS
has 'headers.comment' => '', coerce => \&to_string;
-has 'headers.cipher_id' => CIPHER_UUID_CHACHA20, coerce => \&to_uuid;
+has 'headers.cipher_id' => sub { $_[0]->version < KDBX_VERSION_4_0 ? CIPHER_UUID_AES256 : CIPHER_UUID_CHACHA20 },
+ coerce => \&to_uuid;
has 'headers.compression_flags' => COMPRESSION_GZIP, coerce => \&to_compression_constant;
has 'headers.master_seed' => sub { random_bytes(32) }, coerce => \&to_string;
-has 'headers.encryption_iv' => sub { random_bytes(16) }, coerce => \&to_string;
+has 'headers.encryption_iv' => sub { random_bytes($_[0]->version < KDBX_VERSION_4_0 ? 16 : 12) },
+ coerce => \&to_string;
has 'headers.stream_start_bytes' => sub { random_bytes(32) }, coerce => \&to_string;
has 'headers.kdf_parameters' => sub {
+{
sub randomize_seeds {
my $self = shift;
- $self->encryption_iv(random_bytes(16));
+ my $iv_size = 16;
+ $iv_size = $self->cipher(key => "\0" x 32)->iv_size if KDBX_VERSION_4_0 <= $self->version;
+ $self->encryption_iv(random_bytes($iv_size));
$self->inner_random_stream_key(random_bytes(64));
$self->master_seed(random_bytes(32));
$self->stream_start_bytes(random_bytes(32));
my $self = shift;
my %args = @_;
- $args{uuid} //= $self->headers->{+HEADER_CIPHER_ID};
- $args{iv} //= $self->headers->{+HEADER_ENCRYPTION_IV};
+ $args{uuid} //= $self->cipher_id;
+ $args{iv} //= $self->encryption_iv;
require File::KDBX::Cipher;
return File::KDBX::Cipher->new(%args);
local $headers->{+HEADER_TRANSFORM_SEED} = $kdbx->transform_seed;
local $headers->{+HEADER_TRANSFORM_ROUNDS} = $kdbx->transform_rounds;
+ my $got_iv_size = length($headers->{+HEADER_ENCRYPTION_IV});
+ alert 'Encryption IV should be exactly 16 bytes long',
+ got => $got_iv_size,
+ expected => 16 if $got_iv_size != 16;
+
if (nonempty (my $comment = $headers->{+HEADER_COMMENT})) {
$buf .= $self->_write_header($fh, HEADER_COMMENT, $comment);
}
my $cipher = $kdbx->cipher(key => $final_key);
$fh = File::KDBX::IO::Crypt->new($fh, cipher => $cipher);
+ my $got_iv_size = length($kdbx->headers->{+HEADER_ENCRYPTION_IV});
+ my $iv_size = $cipher->iv_size;
+ alert "Encryption IV should be $iv_size bytes long",
+ got => $got_iv_size,
+ expected => $iv_size if $got_iv_size != $iv_size;
+
my $compress = $kdbx->headers->{+HEADER_COMPRESSION_FLAGS};
if ($compress == COMPRESSION_GZIP) {
load_optional('IO::Compress::Gzip');
sub _commit { die 'Not implemented' }
# Get a reference to an object that represents an object's committed state. If there is no pending
-# transaction, this is just $self. If there is a transaction, this is the snapshot take before the transaction
-# began. This method is private because it provides direct access to the actual snapshot. It is important that
-# the snapshot not be changed or a rollback would roll back to an altered state.
+# transaction, this is just $self. If there is a transaction, this is the snapshot taken immediately before
+# the transaction began. This method is private because it provides direct access to the actual snapshot. It
+# is important that the snapshot not be changed or a rollback would roll back to an altered state.
# This is used by File::KDBX::Dumper::XML so as to not dump uncommitted changes.
sub _committed {
my $self = shift;
use lib "$Bin/lib";
use TestCommon;
+use File::KDBX::Constants qw(:cipher :version);
use File::KDBX;
use File::Temp qw(tempfile);
use Test::Deep;
$entry->remove;
ok $kdbx->_has_implicit_root, 'Removing group makes the root group implicit again';
+
+ cmp_ok $kdbx->version, '==', KDBX_VERSION_3_1, 'Default KDBX file version is 3.1';
+ is $kdbx->cipher_id, CIPHER_UUID_AES256, 'Cipher of new database is AES256';
+ cmp_ok length($kdbx->encryption_iv), '==', 16, 'Encryption IV of new databse is 16 bytes';
+
+ my $kdbx2 = File::KDBX->new(version => KDBX_VERSION_4_0);
+ is $kdbx2->cipher_id, CIPHER_UUID_CHACHA20, 'Cipher of new v4 database is ChaCha20';
+ cmp_ok length($kdbx2->encryption_iv), '==', 12, 'Encryption IV of new databse is 12 bytes';
};
subtest 'Clone' => sub {