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;