1 package File
::KDBX
::Cipher
;
2 # ABSTRACT: A block cipher mode or cipher stream
7 use Devel
::GlobalDestruction
;
8 use File
::KDBX
::Constants
qw(:cipher :random_stream);
10 use File
::KDBX
::Util
qw(:class erase format_uuid);
12 use Scalar
::Util
qw(looks_like_number);
15 our $VERSION = '0.903'; # VERSION
20 has 'uuid', is => 'ro';
21 has 'stream_id', is => 'ro';
22 has 'key', is => 'ro';
27 sub algorithm
{ $_[0]->{algorithm
} or throw
'Block cipher algorithm is not set' }
34 return $class->new_from_uuid(delete $args{uuid
}, %args) if defined $args{uuid
};
35 return $class->new_from_stream_id(delete $args{stream_id
}, %args) if defined $args{stream_id
};
37 throw
'Must pass uuid or stream_id';
45 $args{key
} or throw
'Missing encryption key';
46 $args{iv
} or throw
'Missing encryption IV';
48 my $formatted_uuid = format_uuid
($uuid);
50 my $cipher = $CIPHERS{$uuid} or throw
"Unsupported cipher ($formatted_uuid)", uuid
=> $uuid;
51 ($class, my %registration_args) = @$cipher;
53 my @args = (%args, %registration_args, uuid
=> $uuid);
55 my $self = bless {@args}, $class;
56 return $self->init(@args);
59 sub new_from_stream_id
{
64 $args{key
} or throw
'Missing encryption key';
66 my $cipher = $CIPHERS{$id} or throw
"Unsupported stream cipher ($id)", id
=> $id;
67 ($class, my %registration_args) = @$cipher;
69 my @args = (%args, %registration_args, stream_id
=> $id);
71 my $self = bless {@args}, $class;
72 return $self->init(@args);
78 sub DESTROY
{ !in_global_destruction
and erase \
$_[0]->{key
} }
81 sub encrypt
{ die 'Not implemented' }
84 sub decrypt
{ die 'Not implemented' }
92 my $out = $self->encrypt(@_);
93 $out .= $self->finish;
100 my $out = $self->decrypt(@_);
101 $out .= $self->finish;
112 my $formatted_id = looks_like_number
($id) ? $id : format_uuid
($id);
113 $package = "${class}::${package}" if $package !~ s/^\+// && $package !~ /^\Q${class}::\E/;
115 my %blacklist = map { (looks_like_number
($_) ? $_ : File
::KDBX
::Util
::uuid
($_)) => 1 }
116 split(/,/, $ENV{FILE_KDBX_CIPHER_BLACKLIST
} // '');
117 if ($blacklist{$id} || $blacklist{$package}) {
118 alert
"Ignoring blacklisted cipher ($formatted_id)", id
=> $id, package => $package;
122 if (defined $CIPHERS{$id}) {
123 alert
"Overriding already-registered cipher ($formatted_id) with package $package",
128 $CIPHERS{$id} = [$package, @args];
133 delete $CIPHERS{$_} for @_;
137 __PACKAGE__-
>register(CIPHER_UUID_AES128
, 'CBC', algorithm
=> 'AES', key_size
=> 16);
138 __PACKAGE__-
>register(CIPHER_UUID_AES256
, 'CBC', algorithm
=> 'AES', key_size
=> 32);
139 __PACKAGE__-
>register(CIPHER_UUID_SERPENT
, 'CBC', algorithm
=> 'Serpent', key_size
=> 32);
140 __PACKAGE__-
>register(CIPHER_UUID_TWOFISH
, 'CBC', algorithm
=> 'Twofish', key_size
=> 32);
141 __PACKAGE__-
>register(CIPHER_UUID_CHACHA20
, 'Stream', algorithm
=> 'ChaCha');
142 __PACKAGE__-
>register(CIPHER_UUID_SALSA20
, 'Stream', algorithm
=> 'Salsa20');
143 __PACKAGE__-
>register(STREAM_ID_CHACHA20
, 'Stream', algorithm
=> 'ChaCha');
144 __PACKAGE__-
>register(STREAM_ID_SALSA20
, 'Stream', algorithm
=> 'Salsa20');
157 File::KDBX::Cipher - A block cipher mode or cipher stream
165 use File::KDBX::Cipher;
167 my $cipher = File::KDBX::Cipher->new(uuid => $uuid, key => $key, iv => $iv);
169 my $ciphertext = $cipher->encrypt('plaintext');
170 $ciphertext .= $cipher->encrypt('more plaintext');
171 $ciphertext .= $cipher->finish;
173 my $plaintext = $cipher->decrypt('ciphertext');
174 $plaintext .= $cipher->decrypt('more ciphertext');
175 $plaintext .= $cipher->finish;
179 A cipher is used to encrypt and decrypt KDBX files. The L<File::KDBX> distribution comes with several
180 pre-registered ciphers ready to go:
186 C<61AB05A1-9464-41C3-8D74-3A563DF8DD35> - AES128 (legacy)
190 C<31C1F2E6-BF71-4350-BE58-05216AFC5AFF> - AES256
194 C<D6038A2B-8B6F-4CB5-A524-339A31DBB59A> - ChaCha20
198 C<716E1C8A-EE17-4BDC-93AE-A977B882833A> - Salsa20
202 C<098563FF-DDF7-4F98-8619-8079F6DB897A> - Serpent
206 C<AD68F29F-576F-4BB9-A36A-D47AF965346C> - Twofish
210 B<NOTE:> If you want your KDBX file to be readable by other KeePass implementations, you must use a UUID and
211 algorithm that they support. From the list above, AES256 and ChaCha20 are well-supported. You should avoid
212 AES128 for new databases.
214 You can also L</register> your own cipher. Here is a skeleton:
216 package File::KDBX::Cipher::MyCipher;
218 use parent 'File::KDBX::Cipher';
220 File::KDBX::Cipher->register(
221 # $uuid, $package, %args
222 "\x12\x34\x56\x78\x9a\xbc\xde\xfg\x12\x34\x56\x78\x9a\xbc\xde\xfg" => __PACKAGE__,
225 sub init { ... } # optional
233 sub block_size { ... }
239 $uuid = $cipher->uuid;
241 Get the UUID if the cipher was constructed with one.
245 $stream_id = $cipher->stream_id;
247 Get the stream ID if the cipher was constructed with one.
253 Get the raw encryption key.
259 Get the initialization vector.
263 $size = $cipher->iv_size;
265 Get the expected size of the initialization vector, in bytes.
269 $size = $cipher->key_size;
271 Get the size the mode or stream expects the key to be, in bytes.
275 $size = $cipher->block_size;
277 Get the block size, in bytes.
281 Get the symmetric cipher algorithm.
289 =head2 new_from_stream_id
291 $cipher = File::KDBX::Cipher->new(uuid => $uuid, key => $key, iv => $iv);
293 $cipher = File::KDBX::Cipher->new_from_uuid($uuid, key => $key, iv => $iv);
295 $cipher = File::KDBX::Cipher->new(stream_id => $id, key => $key);
297 $cipher = File::KDBX::Cipher->new_from_stream_id($id, key => $key);
299 Construct a new L<File::KDBX::Cipher>.
301 This is a factory method which returns a subclass.
307 Initialize the cipher. Called by </new>.
311 $ciphertext = $cipher->encrypt($plaintext, ...);
317 $plaintext = $cipher->decrypt($ciphertext, ...);
323 $ciphertext .= $cipher->finish; # if encrypting
324 $plaintext .= $cipher->finish; # if decrypting
328 =head2 encrypt_finish
330 $ciphertext = $cipher->encrypt_finish($plaintext, ...);
332 Encrypt and finish a stream in one call.
334 =head2 decrypt_finish
336 $plaintext = $cipher->decrypt_finish($ciphertext, ...);
338 Decrypt and finish a stream in one call.
342 File::KDBX::Cipher->register($uuid => $package, %args);
344 Register a cipher. Registered ciphers can be used to encrypt and decrypt KDBX databases. A cipher's UUID
345 B<must> be unique and B<musn't change>. A cipher UUID is written into each KDBX file and the associated cipher
346 must be registered with the same UUID in order to decrypt the KDBX file.
348 C<$package> should be a Perl package relative to C<File::KDBX::Cipher::> or prefixed with a C<+> if it is
349 a fully-qualified package. C<%args> are passed as-is to the cipher's L</init> method.
353 File::KDBX::Cipher->unregister($uuid);
355 Unregister a cipher. Unregistered ciphers can no longer be used to encrypt and decrypt KDBX databases, until
356 reregistered (see L</register>).
360 Please report any bugs or feature requests on the bugtracker website
361 L<https://github.com/chazmcgarvey/File-KDBX/issues>
363 When submitting a bug or request, please include a test-file or a
364 patch to an existing test-file that illustrates the bug or desired
369 Charles McGarvey <ccm@cpan.org>
371 =head1 COPYRIGHT AND LICENSE
373 This software is copyright (c) 2022 by Charles McGarvey.
375 This is free software; you can redistribute it and/or modify it under
376 the same terms as the Perl 5 programming language system itself.