Skip Menu |

This queue is for tickets about the File-KeePass CPAN distribution.

Report information
The Basics
Id: 87109
Status: new
Priority: 0/
Queue: File-KeePass

People
Owner: Nobody in particular
Requestors: cjm [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: (no value)



Subject: [PATCH] Use Crypt::Salsa20
As reported in RT#82582, File::KeePass's Salsa20 implementation is buggy. And anyway, Salsa20 should be a separate CPAN module; File::KeePass shouldn't have its own private implementation. Accordingly, I've just released Crypt::Salsa20 to CPAN. It works properly on both 32 and 64 bit Perls, and includes the complete test vectors from ECRYPT to ensure that. Here's a patch to make File::KeePass use Crypt::Salsa20. This fixes RT#82582.
Subject: Crypt-Salsa20.patch
--- lib/File/KeePass.pm 2012-09-15 17:09:42.000000000 -0500 +++ lib/File/KeePass.pm 2013-07-20 12:20:36.548187805 -0500 @@ -9,6 +9,7 @@ use strict; use warnings; use Crypt::Rijndael; +use Crypt::Salsa20; use Digest::SHA qw(sha256); use constant DB_HEADSIZE_V1 => 124; @@ -237,7 +238,7 @@ # parse the XML - use our own parser since XML::Simple does not do event based actions my $tri = sub { return !defined($_[0]) ? undef : ('true' eq lc $_[0]) ? 1 : ('false' eq lc $_[0]) ? 0 : undef }; - my $s20_stream = $self->salsa20_stream({key => sha256($head->{'protected_stream_key'}), iv => $salsa20_iv, rounds => 20}); + my $s20_stream = Crypt::Salsa20->new(key => sha256($head->{'protected_stream_key'}), iv => $salsa20_iv)->cryptor; my %BIN; my $META; my @GROUPS; @@ -962,7 +963,7 @@ } } - my $s20_stream = $self->salsa20_stream({key => sha256($head->{'protected_stream_key'}), iv => $salsa20_iv, rounds => 20}); + my $s20_stream = Crypt::Salsa20->new(key => sha256($head->{'protected_stream_key'}), iv => $salsa20_iv)->cryptor; for my $ref (@PROTECT_BIN, @PROTECT_STR) { $$ref = $self->encode_base64($s20_stream->($$ref)); } @@ -1490,102 +1491,6 @@ } ###----------------------------------------------------------------### - -sub salsa20_stream { - my ($self, $args) = @_; - delete $args->{'data'}; - my $salsa20 = $self->salsa20($args); - my $buffer = ''; - return sub { - my $enc = shift; - $buffer .= $salsa20->("\0" x 64) while length($buffer) < length($enc); - my $data = join '', map {chr(ord(substr $enc, $_, 1) ^ ord(substr $buffer, $_, 1))} 0 .. length($enc)-1; - substr $buffer, 0, length($enc), ''; - return $data; - }; -} - - -sub salsa20 { # http://cr.yp.to/snuffle/salsa20/regs/salsa20.c - my ($self, $args) = @_; - my ($key, $iv, $rounds) = @$args{qw(key iv rounds)}; - $rounds ||= 20; - - my (@k, @c); - if (32 == length $key) { - @k = unpack 'L8', $key; - @c = (0x61707865, 0x3320646e, 0x79622d32, 0x6b206574); # SIGMA - } elsif (16 == length $key) { - @k = unpack 'L8', $key x 2; - @c = (0x61707865, 0x3120646e, 0x79622d36, 0x6b206574); # TAU - } else { - die "Salsa20 key length must be 16 or 32\n"; - } - die "Salsa20 IV length must be 8\n" if length($iv) != 8; - die "Salsa20 rounds must be 8, 12, or 20.\n" if !grep {$rounds != $_} 8, 12, 20; - my @v = unpack('L2', $iv); - - # 0 5 6 7 10 # 15 - my @state = ($c[0], $k[0], $k[1], $k[2], $k[3], $c[1], $v[0], $v[1], 0, 0, $c[2], $k[4], $k[5], $k[6], $k[7], $c[3]); - - my $rotl32 = sub { return (($_[0] << $_[1]) | ($_[0] >> (32 - $_[1]))) & 0xffffffff }; - my $word_to_byte = sub { - my @x = @state; - for (1 .. $rounds/2) { - $x[ 4] ^= $rotl32->(($x[ 0] + $x[12]) & 0xffffffff, 7); - $x[ 8] ^= $rotl32->(($x[ 4] + $x[ 0]) & 0xffffffff, 9); - $x[12] ^= $rotl32->(($x[ 8] + $x[ 4]) & 0xffffffff, 13); - $x[ 0] ^= $rotl32->(($x[12] + $x[ 8]) & 0xffffffff, 18); - $x[ 9] ^= $rotl32->(($x[ 5] + $x[ 1]) & 0xffffffff, 7); - $x[13] ^= $rotl32->(($x[ 9] + $x[ 5]) & 0xffffffff, 9); - $x[ 1] ^= $rotl32->(($x[13] + $x[ 9]) & 0xffffffff, 13); - $x[ 5] ^= $rotl32->(($x[ 1] + $x[13]) & 0xffffffff, 18); - $x[14] ^= $rotl32->(($x[10] + $x[ 6]) & 0xffffffff, 7); - $x[ 2] ^= $rotl32->(($x[14] + $x[10]) & 0xffffffff, 9); - $x[ 6] ^= $rotl32->(($x[ 2] + $x[14]) & 0xffffffff, 13); - $x[10] ^= $rotl32->(($x[ 6] + $x[ 2]) & 0xffffffff, 18); - $x[ 3] ^= $rotl32->(($x[15] + $x[11]) & 0xffffffff, 7); - $x[ 7] ^= $rotl32->(($x[ 3] + $x[15]) & 0xffffffff, 9); - $x[11] ^= $rotl32->(($x[ 7] + $x[ 3]) & 0xffffffff, 13); - $x[15] ^= $rotl32->(($x[11] + $x[ 7]) & 0xffffffff, 18); - - $x[ 1] ^= $rotl32->(($x[ 0] + $x[ 3]) & 0xffffffff, 7); - $x[ 2] ^= $rotl32->(($x[ 1] + $x[ 0]) & 0xffffffff, 9); - $x[ 3] ^= $rotl32->(($x[ 2] + $x[ 1]) & 0xffffffff, 13); - $x[ 0] ^= $rotl32->(($x[ 3] + $x[ 2]) & 0xffffffff, 18); - $x[ 6] ^= $rotl32->(($x[ 5] + $x[ 4]) & 0xffffffff, 7); - $x[ 7] ^= $rotl32->(($x[ 6] + $x[ 5]) & 0xffffffff, 9); - $x[ 4] ^= $rotl32->(($x[ 7] + $x[ 6]) & 0xffffffff, 13); - $x[ 5] ^= $rotl32->(($x[ 4] + $x[ 7]) & 0xffffffff, 18); - $x[11] ^= $rotl32->(($x[10] + $x[ 9]) & 0xffffffff, 7); - $x[ 8] ^= $rotl32->(($x[11] + $x[10]) & 0xffffffff, 9); - $x[ 9] ^= $rotl32->(($x[ 8] + $x[11]) & 0xffffffff, 13); - $x[10] ^= $rotl32->(($x[ 9] + $x[ 8]) & 0xffffffff, 18); - $x[12] ^= $rotl32->(($x[15] + $x[14]) & 0xffffffff, 7); - $x[13] ^= $rotl32->(($x[12] + $x[15]) & 0xffffffff, 9); - $x[14] ^= $rotl32->(($x[13] + $x[12]) & 0xffffffff, 13); - $x[15] ^= $rotl32->(($x[14] + $x[13]) & 0xffffffff, 18); - } - return pack 'L16', map {($x[$_] + $state[$_]) & 0xffffffff} 0 .. 15; - }; - - my $encoder = sub { - my $enc = shift; - my $out = ''; - while (length $enc) { - my $stream = $word_to_byte->(); - $state[8] = ($state[8] + 1) & 0xffffffff; - $state[9] = ($state[9] + 1) & 0xffffffff if $state[8] == 0; - my $chunk = substr $enc, 0, 64, ''; - $out .= join '', map {chr(ord(substr $stream, $_, 1) ^ ord(substr $chunk, $_, 1))} 0 .. length($chunk)-1; - } - return $out; - }; - return $encoder if !exists $args->{'data'}; - return $encoder->(defined($args->{'data'}) ? $args->{'data'} : ''); -} - -###----------------------------------------------------------------### 1;
The first patch neglected to remove the documentation relating to Salsa20. This second patch is just for that.
Subject: Crypt-Salsa20-docs.patch
--- lib/File/KeePass.pm 2012-09-15 17:09:42.000000000 -0500 +++ lib/File/KeePass.pm 2013-07-20 12:40:09.213266655 -0500 @@ -2158,38 +2063,6 @@ to order the tags of a node and __attr__ can be used to indicate which items of a node are attributes. -=item salsa20 - -Takes a hashref containing a salsa20 key string (length 32 or 16), a -salsa20 iv string (length 8), number of salsa20 rounds (8, 12, or 20 - -default 20), and an optional data string. The key and iv are used to -initialize the salsa20 encryption. - -If a data string is passed, the string is salsa20 encrypted and -returned. - -If no data string is passed a salsa20 encrypting coderef is returned. - - my $encoded = $self->salsa20({key => $key, iv => $iv, data => $data}); - my $uncoded = $self->salsa20({key => $key, iv => $iv, data => $encoded}); - # $data eq $uncoded - - my $encoder = $self->salsa20({key => $key, iv => $Iv}); # no data - my $encoded = $encoder->($data); - my $part2 = $encoder->($more_data); # continues from previous state - -=item salsa20_stream - -Takes a hashref that will be passed to salsa20. Uses the resulting -encoder to generate a more continuous encoded stream. The salsa20 -method encodes in chunks of 64 bytes. If a string is not a multiple -of 64, then some of the xor bytes are unused. The salsa20_stream -method maintains a buffer of xor bytes to ensure that none are wasted. - - my $encoder = $self->salsa20_stream({key => $key, iv => $Iv}); # no data - my $encoded = $encoder->("1234"); # calls salsa20->() - my $part2 = $encoder->("1234"); # uses the same pad until 64 bytes are used - =back =head1 OTHER METHODS @@ -2296,10 +2169,6 @@ Copyright: 2010-2012, Felix Geyer <debfx@fobos.de> 2011-2012, Florian Geyer <blueice@fobos.de> -The salsa20 algorithm is based on -http://cr.yp.to/snuffle/salsa20/regs/salsa20.c which is listed as -Public domain (D. J. Bernstein). - The ordering and layering of encryption/decryption algorithms of File::KeePass are of derivative nature from KeePassX and could not have been created without this insight - though the perl code is from