1 package File
::KDBX
::KDF
;
2 # ABSTRACT: A key derivation function
7 use Crypt
::PRNG
qw(random_bytes);
8 use File
::KDBX
::Constants
qw(:version :kdf);
10 use File
::KDBX
::Util
qw(format_uuid);
12 use Scalar
::Util
qw(blessed);
15 our $VERSION = '0.906'; # VERSION
20 KDF_UUID_ARGON2D
() => {p
=> KDF_PARAM_ARGON2_ITERATIONS
, d
=> KDF_DEFAULT_ARGON2_ITERATIONS
},
21 KDF_UUID_ARGON2ID
() => {p
=> KDF_PARAM_ARGON2_ITERATIONS
, d
=> KDF_DEFAULT_ARGON2_ITERATIONS
},
23 our $DEFAULT_ROUNDS_INFO = {
24 p
=> KDF_PARAM_AES_ROUNDS
,
25 d
=> KDF_DEFAULT_AES_ROUNDS
,
33 my $uuid = $args{+KDF_PARAM_UUID
} //= delete $args{uuid
} or throw
'Missing KDF UUID', args
=> \
%args;
34 my $formatted_uuid = format_uuid
($uuid);
36 my $kdf = $KDFS{$uuid} or throw
"Unsupported KDF ($formatted_uuid)", uuid
=> $uuid;
37 ($class, my %registration_args) = @$kdf;
40 my $self = bless {KDF_PARAM_UUID
() => $uuid}, $class;
41 return $self->init(%args, %registration_args);
49 @$self{keys %args} = values %args;
55 sub uuid
{ $_[0]->{+KDF_PARAM_UUID
} }
58 sub seed
{ die 'Not implemented' }
65 if (blessed
$key && $key->can('raw_key')) {
66 return $self->_transform($key->raw_key) if $self->uuid eq KDF_UUID_AES
;
67 return $self->_transform($key->raw_key($self->seed, @_));
70 return $self->_transform($key);
73 sub _transform
{ die 'Not implemented' }
78 $self->{+KDF_PARAM_AES_SEED
} = random_bytes
(length($self->seed));
88 my $formatted_id = format_uuid
($id);
89 $package = "${class}::${package}" if $package !~ s/^\+// && $package !~ /^\Q${class}::\E/;
91 my %blacklist = map { File
::KDBX
::Util
::uuid
($_) => 1 } split(/,/, $ENV{FILE_KDBX_KDF_BLACKLIST
} // '');
92 if ($blacklist{$id} || $blacklist{$package}) {
93 alert
"Ignoring blacklisted KDF ($formatted_id)", id
=> $id, package => $package;
97 if (defined $KDFS{$id}) {
98 alert
"Overriding already-registered KDF ($formatted_id) with package $package",
103 $KDFS{$id} = [$package, @args];
108 delete $KDFS{$_} for @_;
112 __PACKAGE__-
>register(KDF_UUID_AES
, 'AES');
113 __PACKAGE__-
>register(KDF_UUID_AES_CHALLENGE_RESPONSE
, 'AES');
114 __PACKAGE__-
>register(KDF_UUID_ARGON2D
, 'Argon2');
115 __PACKAGE__-
>register(KDF_UUID_ARGON2ID
, 'Argon2');
128 File::KDBX::KDF - A key derivation function
136 A KDF (key derivation function) is used in the transformation of a master key (i.e. one or more component
137 keys) to produce the final encryption key protecting a KDBX database. The L<File::KDBX> distribution comes
138 with several pre-registered KDFs ready to go:
144 C<C9D9F39A-628A-4460-BF74-0D08C18A4FEA> - AES
148 C<7C02BB82-79A7-4AC0-927D-114A00648238> - AES (challenge-response variant)
152 C<EF636DDF-8C29-444B-91F7-A9A403E30A0C> - Argon2d
156 C<9E298B19-56DB-4773-B23D-FC3EC6F0A1E6> - Argon2id
160 B<NOTE:> If you want your KDBX file to be readable by other KeePass implementations, you must use a UUID and
161 algorithm that they support. From the list above, all are well-supported except the AES challenge-response
162 variant which is kind of a pseudo KDF and isn't usually written into files. All of these are good. AES has
163 a longer track record, but Argon2 has better ASIC resistance.
165 You can also L</register> your own KDF. Here is a skeleton:
167 package File::KDBX::KDF::MyKDF;
169 use parent 'File::KDBX::KDF';
171 File::KDBX::KDF->register(
172 # $uuid, $package, %args
173 "\x12\x34\x56\x78\x9a\xbc\xde\xfg\x12\x34\x56\x78\x9a\xbc\xde\xfg" => __PACKAGE__,
176 sub init { ... } # optional
178 sub _transform { my ($key) = @_; ... }
186 Get the UUID used to determine which function to use.
192 Get the seed (or salt, depending on the function).
198 $kdf = File::KDBX::KDF->new(parameters => \%params);
204 $kdf = $kdf->init(%attributes);
206 Called by L</new> to set attributes. You normally shouldn't call this. Returns itself to allow method
211 $transformed_key = $kdf->transform($key);
212 $transformed_key = $kdf->transform($key, $challenge);
214 Transform a key. The input key can be either a L<File::KDBX::Key> or a raw binary key, and the
215 transformed key will be a raw key.
217 This can take awhile, depending on the KDF parameters.
219 If a challenge is provided (and the KDF is AES except for the KeePassXC variant), it will be passed to the key
220 so challenge-response keys can produce raw keys. See L<File::KDBX::Key/raw_key>.
222 =head2 randomize_seed
224 $kdf->randomize_seed;
226 Generate and set a new random seed/salt.
230 File::KDBX::KDF->register($uuid => $package, %args);
232 Register a KDF. Registered KDFs can be used to encrypt and decrypt KDBX databases. A KDF's UUID B<must> be
233 unique and B<musn't change>. A KDF UUID is written into each KDBX file and the associated KDF must be
234 registered with the same UUID in order to decrypt the KDBX file.
236 C<$package> should be a Perl package relative to C<File::KDBX::KDF::> or prefixed with a C<+> if it is
237 a fully-qualified package. C<%args> are passed as-is to the KDF's L</init> method.
241 File::KDBX::KDF->unregister($uuid);
243 Unregister a KDF. Unregistered KDFs can no longer be used to encrypt and decrypt KDBX databases, until
244 reregistered (see L</register>).
248 Please report any bugs or feature requests on the bugtracker website
249 L<https://github.com/chazmcgarvey/File-KDBX/issues>
251 When submitting a bug or request, please include a test-file or a
252 patch to an existing test-file that illustrates the bug or desired
257 Charles McGarvey <ccm@cpan.org>
259 =head1 COPYRIGHT AND LICENSE
261 This software is copyright (c) 2022 by Charles McGarvey.
263 This is free software; you can redistribute it and/or modify it under
264 the same terms as the Perl 5 programming language system itself.