Skip Menu |

This queue is for tickets about the Crypt-OpenPGP CPAN distribution.

Report information
The Basics
Id: 82314
Status: patched
Priority: 0/
Queue: Crypt-OpenPGP

People
Owner: Nobody in particular
Requestors: RIVY [...] cpan.org
sandals [...] crustytoothpaste.net
Cc:
AdminCc:

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



Subject: [PATCH] Avoid dependencies on Math::Pari
Math::Pari is hard for most Linux distributions to build because it requires its own copy of libpari and won't build with a system copy. (This has been a persistent problem for Debian.) Since Crypt::RSA is probably going to be moving away from Math::Pari, the only dependency of Crypt::OpenPGP left that uses it is Crypt::Random. I'm attaching a patch that will allow the use of Bytes::Random::Secure if Crypt::Random is not available. I have added two functions to Crypt::OpenPGP::Util that abstract the differences between the two modules. The testsuite continues to pass for me on Debian amd64/sid. If you would rather, you can pull from git://github.com/bk2204/Crypt- OpenPGP.git non-pari.
Subject: non-pari.patch
diff --git a/lib/Crypt/OpenPGP.pm b/lib/Crypt/OpenPGP.pm index cbf40d4..75fb644 100644 --- a/lib/Crypt/OpenPGP.pm +++ b/lib/Crypt/OpenPGP.pm @@ -11,6 +11,7 @@ use Crypt::OpenPGP::Plaintext; use Crypt::OpenPGP::Message; use Crypt::OpenPGP::PacketFactory; use Crypt::OpenPGP::Config; +use Crypt::OpenPGP::Util; use Crypt::OpenPGP::ErrorHandler; use base qw( Crypt::OpenPGP::ErrorHandler ); @@ -455,8 +456,7 @@ sub encrypt { Crypt::OpenPGP::Compressed->errstr); $ptdata = Crypt::OpenPGP::PacketFactory->save($cdata); } - require Crypt::Random; - my $key_data = Crypt::Random::makerandom_octet( Length => 32 ); + my $key_data = Crypt::OpenPGP::Util::get_random_bytes(32); my $sym_alg = $param{Cipher} ? Crypt::OpenPGP::Cipher->alg_id($param{Cipher}) : DEFAULT_CIPHER; my(@sym_keys); diff --git a/lib/Crypt/OpenPGP/Certificate.pm b/lib/Crypt/OpenPGP/Certificate.pm index 72d344b..b5d7092 100644 --- a/lib/Crypt/OpenPGP/Certificate.pm +++ b/lib/Crypt/OpenPGP/Certificate.pm @@ -329,8 +329,7 @@ sub lock { my($passphrase) = @_; my $cipher = Crypt::OpenPGP::Cipher->new($cert->{cipher}); my $sym_key = $cert->{s2k}->generate($passphrase, $cipher->keysize); - require Crypt::Random; - $cert->{iv} = Crypt::Random::makerandom_octet( Length => 8 ); + $cert->{iv} = Crypt::OpenPGP::Util::get_random_bytes(8); $cipher->init($sym_key, $cert->{iv}); my @sec = $cert->{key}->secret_props; if ($cert->{version} < 4) { diff --git a/lib/Crypt/OpenPGP/Ciphertext.pm b/lib/Crypt/OpenPGP/Ciphertext.pm index 4b76e0a..27bcffc 100644 --- a/lib/Crypt/OpenPGP/Ciphertext.pm +++ b/lib/Crypt/OpenPGP/Ciphertext.pm @@ -1,6 +1,7 @@ package Crypt::OpenPGP::Ciphertext; use strict; +use Crypt::OpenPGP::Util; use Crypt::OpenPGP::Cipher; use Crypt::OpenPGP::Constants qw( DEFAULT_CIPHER PGP_PKT_ENCRYPTED @@ -24,11 +25,10 @@ sub init { if ((my $key = $param{SymKey}) && (my $data = $param{Data})) { $enc->{is_mdc} = $param{MDC} || 0; $enc->{version} = 1; - require Crypt::Random; my $alg = $param{Cipher} || DEFAULT_CIPHER; my $cipher = Crypt::OpenPGP::Cipher->new($alg, $key); my $bs = $cipher->blocksize; - my $pad = Crypt::Random::makerandom_octet( Length => $bs ); + my $pad = Crypt::OpenPGP::Util::get_random_bytes($bs); $pad .= substr $pad, -2, 2; $enc->{ciphertext} = $cipher->encrypt($pad); $cipher->sync unless $enc->{is_mdc}; diff --git a/lib/Crypt/OpenPGP/Key/Public/ElGamal.pm b/lib/Crypt/OpenPGP/Key/Public/ElGamal.pm index d848f90..a00cfeb 100644 --- a/lib/Crypt/OpenPGP/Key/Public/ElGamal.pm +++ b/lib/Crypt/OpenPGP/Key/Public/ElGamal.pm @@ -55,10 +55,7 @@ sub gen_k { my $bits = 198; my $p_minus1 = $p - 1; - require Crypt::Random; - my $k = Crypt::Random::makerandom( Size => $bits, Strength => 0 ); - # We get back a Math::Pari object, but need a Math::BigInt - $k = Math::BigInt->new($k); + my $k = Crypt::OpenPGP::Util::get_random_bigint($bits); while (1) { last if Math::BigInt::bgcd($k, $p_minus1) == 1; $k++; diff --git a/lib/Crypt/OpenPGP/S2k.pm b/lib/Crypt/OpenPGP/S2k.pm index 0ce398c..bd246f3 100644 --- a/lib/Crypt/OpenPGP/S2k.pm +++ b/lib/Crypt/OpenPGP/S2k.pm @@ -4,6 +4,7 @@ use strict; use Crypt::OpenPGP::Buffer; use Crypt::OpenPGP::Digest; use Crypt::OpenPGP::ErrorHandler; +use Crypt::OpenPGP::Util; use base qw( Crypt::OpenPGP::ErrorHandler ); use vars qw( %TYPES ); @@ -95,8 +96,7 @@ sub init { } else { $s2k->{hash_alg} = DEFAULT_DIGEST; - require Crypt::Random; - $s2k->{salt} = Crypt::Random::makerandom_octet( Length => 8 ); + $s2k->{salt} = Crypt::OpenPGP::Util::get_random_bytes(8); } if ($s2k->{hash_alg}) { $s2k->{hash} = Crypt::OpenPGP::Digest->new($s2k->{hash_alg}); @@ -130,8 +130,7 @@ sub init { } else { $s2k->{hash_alg} = DEFAULT_DIGEST; - require Crypt::Random; - $s2k->{salt} = Crypt::Random::makerandom_octet( Length => 8 ); + $s2k->{salt} = Crypt::OpenPGP::Util::get_random_bytes(8); $s2k->{count} = 96; } if ($s2k->{hash_alg}) { diff --git a/lib/Crypt/OpenPGP/SessionKey.pm b/lib/Crypt/OpenPGP/SessionKey.pm index a4bbb8f..4c86d42 100644 --- a/lib/Crypt/OpenPGP/SessionKey.pm +++ b/lib/Crypt/OpenPGP/SessionKey.pm @@ -88,11 +88,12 @@ sub decrypt { sub _encode { my $class = shift; - require Crypt::Random; my($sym_key, $sym_alg, $size) = @_; my $padlen = "$size" - length($sym_key) - 2 - 2 - 2; - my $pad = Crypt::Random::makerandom_octet( Length => $padlen, - Skip => chr(0) ); + my $pad = "\0"; + while ($pad =~ tr/\0//) { + $pad = Crypt::OpenPGP::Util::get_random_bytes($padlen); + } bin2mp(pack 'na*na*n', 2, $pad, $sym_alg, $sym_key, unpack('%16C*', $sym_key)); } diff --git a/lib/Crypt/OpenPGP/Util.pm b/lib/Crypt/OpenPGP/Util.pm index 3398c35..579d80a 100644 --- a/lib/Crypt/OpenPGP/Util.pm +++ b/lib/Crypt/OpenPGP/Util.pm @@ -99,6 +99,34 @@ sub _ensure_bigint { return $num; } +sub get_random_bytes { + my $length = shift; + if (eval 'require Crypt::Random; 1;') { + return Crypt::Random::makerandom_octet( Length => $length); + } + elsif (eval 'require Bytes::Random::Secure; 1;') { + return Bytes::Random::Secure::random_bytes($length); + } + else { + die "No random source available!"; + } +} + +sub get_random_bigint { + my $bits = shift; + if (eval 'require Crypt::Random; 1;') { + my $pari = Crypt::Random::makerandom( Size => $bits, Strength => 0 ); + return Math::BigInt->new($pari); + } + elsif (eval 'require Bytes::Random::Secure; 1;') { + my $hex = Bytes::Random::Secure::random_bytes_hex($bits / 8); + return Math::BigInt->new("0x$hex"); + } + else { + die "No random source available!"; + } +} + 1; __END__
In get_random_bigint() I think there may be an issue. We're trying to make a $bits random number, which is what Crypt::Random::makerandom() does by default (it needs Uniform=>1 to do otherwise). (1) By calling random_bytes_hex we end up generating a uniform number -- that is, we could generate leading zeros and end up with a smaller number. A lousy hacky untested solution would be something like: my $result = Math::BigInt->new("0x$hex"); $result->bior( Math::BigInt->bone->lsft($bits-1) ); return $result; (2) This method also assumes bits is divisible by 8. In ElGamal.pm it looks like $bits is set to 198, so seemingly we'd hand 24.75 to random_bytes_hex. Both could be easily done if we had a function to return bits, something like 'return Math::BigInt->new("0b1" . random_bit_string($bits-1));' Alternately someone will come up with a small clever solution using just random_bytes_hex. Though I have nothing to do with this module, I like the idea, and I think Bytes::Random::Secure is a good solution.
From: sandals [...] crustytoothpaste.net
On Fri Dec 28 22:48:03 2012, DANAJ wrote: Show quoted text
> In get_random_bigint() I think there may be an issue. We're trying to > make a $bits random number, which is what Crypt::Random::makerandom() > does by default (it needs Uniform=>1 to do otherwise). > > (1) By calling random_bytes_hex we end up generating a uniform number
-- Show quoted text
> that is, we could generate leading zeros and end up with a smaller > number. A lousy hacky untested solution would be something like: > my $result = Math::BigInt->new("0x$hex"); > $result->bior( Math::BigInt->bone->lsft($bits-1) ); > return $result;
I've fixed this with the attached patch. Show quoted text
> (2) This method also assumes bits is divisible by 8. In ElGamal.pm it > looks like $bits is set to 198, so seemingly we'd hand 24.75 to > random_bytes_hex.
And this. These now have tests to go along with them to be sure that the behavior is as expected. Show quoted text
> Both could be easily done if we had a function to return bits,
something Show quoted text
> like 'return Math::BigInt->new("0b1" . random_bit_string($bits-1));' > Alternately someone will come up with a small clever solution using
just Show quoted text
> random_bytes_hex.
I don't know about clever, but I believe my solution works. Show quoted text
> Though I have nothing to do with this module, I like the idea, and I > think Bytes::Random::Secure is a good solution.
I picked it because Crypt::OpenPGP depends on Crypt::RSA, and your proposed implementation uses it. My interest is getting Crypt::OpenPGP into Debian, and for that, we need to get rid of Math::Pari, and therefore Crypt::Random.
Subject: non-pari.patch
diff --git a/lib/Crypt/OpenPGP.pm b/lib/Crypt/OpenPGP.pm index cbf40d4..75fb644 100644 --- a/lib/Crypt/OpenPGP.pm +++ b/lib/Crypt/OpenPGP.pm @@ -11,6 +11,7 @@ use Crypt::OpenPGP::Plaintext; use Crypt::OpenPGP::Message; use Crypt::OpenPGP::PacketFactory; use Crypt::OpenPGP::Config; +use Crypt::OpenPGP::Util; use Crypt::OpenPGP::ErrorHandler; use base qw( Crypt::OpenPGP::ErrorHandler ); @@ -455,8 +456,7 @@ sub encrypt { Crypt::OpenPGP::Compressed->errstr); $ptdata = Crypt::OpenPGP::PacketFactory->save($cdata); } - require Crypt::Random; - my $key_data = Crypt::Random::makerandom_octet( Length => 32 ); + my $key_data = Crypt::OpenPGP::Util::get_random_bytes(32); my $sym_alg = $param{Cipher} ? Crypt::OpenPGP::Cipher->alg_id($param{Cipher}) : DEFAULT_CIPHER; my(@sym_keys); diff --git a/lib/Crypt/OpenPGP/Certificate.pm b/lib/Crypt/OpenPGP/Certificate.pm index 72d344b..b5d7092 100644 --- a/lib/Crypt/OpenPGP/Certificate.pm +++ b/lib/Crypt/OpenPGP/Certificate.pm @@ -329,8 +329,7 @@ sub lock { my($passphrase) = @_; my $cipher = Crypt::OpenPGP::Cipher->new($cert->{cipher}); my $sym_key = $cert->{s2k}->generate($passphrase, $cipher->keysize); - require Crypt::Random; - $cert->{iv} = Crypt::Random::makerandom_octet( Length => 8 ); + $cert->{iv} = Crypt::OpenPGP::Util::get_random_bytes(8); $cipher->init($sym_key, $cert->{iv}); my @sec = $cert->{key}->secret_props; if ($cert->{version} < 4) { diff --git a/lib/Crypt/OpenPGP/Ciphertext.pm b/lib/Crypt/OpenPGP/Ciphertext.pm index 4b76e0a..27bcffc 100644 --- a/lib/Crypt/OpenPGP/Ciphertext.pm +++ b/lib/Crypt/OpenPGP/Ciphertext.pm @@ -1,6 +1,7 @@ package Crypt::OpenPGP::Ciphertext; use strict; +use Crypt::OpenPGP::Util; use Crypt::OpenPGP::Cipher; use Crypt::OpenPGP::Constants qw( DEFAULT_CIPHER PGP_PKT_ENCRYPTED @@ -24,11 +25,10 @@ sub init { if ((my $key = $param{SymKey}) && (my $data = $param{Data})) { $enc->{is_mdc} = $param{MDC} || 0; $enc->{version} = 1; - require Crypt::Random; my $alg = $param{Cipher} || DEFAULT_CIPHER; my $cipher = Crypt::OpenPGP::Cipher->new($alg, $key); my $bs = $cipher->blocksize; - my $pad = Crypt::Random::makerandom_octet( Length => $bs ); + my $pad = Crypt::OpenPGP::Util::get_random_bytes($bs); $pad .= substr $pad, -2, 2; $enc->{ciphertext} = $cipher->encrypt($pad); $cipher->sync unless $enc->{is_mdc}; diff --git a/lib/Crypt/OpenPGP/Key/Public/ElGamal.pm b/lib/Crypt/OpenPGP/Key/Public/ElGamal.pm index d848f90..a00cfeb 100644 --- a/lib/Crypt/OpenPGP/Key/Public/ElGamal.pm +++ b/lib/Crypt/OpenPGP/Key/Public/ElGamal.pm @@ -55,10 +55,7 @@ sub gen_k { my $bits = 198; my $p_minus1 = $p - 1; - require Crypt::Random; - my $k = Crypt::Random::makerandom( Size => $bits, Strength => 0 ); - # We get back a Math::Pari object, but need a Math::BigInt - $k = Math::BigInt->new($k); + my $k = Crypt::OpenPGP::Util::get_random_bigint($bits); while (1) { last if Math::BigInt::bgcd($k, $p_minus1) == 1; $k++; diff --git a/lib/Crypt/OpenPGP/S2k.pm b/lib/Crypt/OpenPGP/S2k.pm index 0ce398c..bd246f3 100644 --- a/lib/Crypt/OpenPGP/S2k.pm +++ b/lib/Crypt/OpenPGP/S2k.pm @@ -4,6 +4,7 @@ use strict; use Crypt::OpenPGP::Buffer; use Crypt::OpenPGP::Digest; use Crypt::OpenPGP::ErrorHandler; +use Crypt::OpenPGP::Util; use base qw( Crypt::OpenPGP::ErrorHandler ); use vars qw( %TYPES ); @@ -95,8 +96,7 @@ sub init { } else { $s2k->{hash_alg} = DEFAULT_DIGEST; - require Crypt::Random; - $s2k->{salt} = Crypt::Random::makerandom_octet( Length => 8 ); + $s2k->{salt} = Crypt::OpenPGP::Util::get_random_bytes(8); } if ($s2k->{hash_alg}) { $s2k->{hash} = Crypt::OpenPGP::Digest->new($s2k->{hash_alg}); @@ -130,8 +130,7 @@ sub init { } else { $s2k->{hash_alg} = DEFAULT_DIGEST; - require Crypt::Random; - $s2k->{salt} = Crypt::Random::makerandom_octet( Length => 8 ); + $s2k->{salt} = Crypt::OpenPGP::Util::get_random_bytes(8); $s2k->{count} = 96; } if ($s2k->{hash_alg}) { diff --git a/lib/Crypt/OpenPGP/SessionKey.pm b/lib/Crypt/OpenPGP/SessionKey.pm index a4bbb8f..4c86d42 100644 --- a/lib/Crypt/OpenPGP/SessionKey.pm +++ b/lib/Crypt/OpenPGP/SessionKey.pm @@ -88,11 +88,12 @@ sub decrypt { sub _encode { my $class = shift; - require Crypt::Random; my($sym_key, $sym_alg, $size) = @_; my $padlen = "$size" - length($sym_key) - 2 - 2 - 2; - my $pad = Crypt::Random::makerandom_octet( Length => $padlen, - Skip => chr(0) ); + my $pad = "\0"; + while ($pad =~ tr/\0//) { + $pad = Crypt::OpenPGP::Util::get_random_bytes($padlen); + } bin2mp(pack 'na*na*n', 2, $pad, $sym_alg, $sym_key, unpack('%16C*', $sym_key)); } diff --git a/lib/Crypt/OpenPGP/Util.pm b/lib/Crypt/OpenPGP/Util.pm index 3398c35..2c195c6 100644 --- a/lib/Crypt/OpenPGP/Util.pm +++ b/lib/Crypt/OpenPGP/Util.pm @@ -99,6 +99,39 @@ sub _ensure_bigint { return $num; } +sub get_random_bytes { + my $length = shift; + if (eval 'require Crypt::Random; 1;') { + return Crypt::Random::makerandom_octet( Length => $length); + } + elsif (eval 'require Bytes::Random::Secure; 1;') { + return Bytes::Random::Secure::random_bytes($length); + } + else { + die "No random source available!"; + } +} + +sub get_random_bigint { + my $bits = shift; + if (eval 'require Crypt::Random; 1;') { + my $pari = Crypt::Random::makerandom( Size => $bits, Strength => 0 ); + return Math::BigInt->new($pari); + } + elsif (eval 'require Bytes::Random::Secure; 1;') { + my $hex = Bytes::Random::Secure::random_bytes_hex(($bits + 7) / 8); + my $val = Math::BigInt->new("0x$hex"); + # Get exactly the correct number of bits. + $val->brsft(8 - ($bits & 7)) if ($bits & 7); + # Make sure the top bit is set. + $val->bior(Math::BigInt->bone->blsft($bits-1)); + return $val; + } + else { + die "No random source available!"; + } +} + 1; __END__ diff --git a/t/01-util.t b/t/01-util.t index fa8a361..3657a03 100644 --- a/t/01-util.t +++ b/t/01-util.t @@ -1,5 +1,5 @@ use strict; -use Test::More tests => 41; +use Test::More tests => 63; use Math::BigInt; use Crypt::OpenPGP::Util qw( bin2bigint bigint2bin bitsize mod_exp mod_inverse ); @@ -59,4 +59,13 @@ is $num, $n4, 'mod_exp is correct'; ("34093840983", "23509283509", "7281956166"); $num = mod_inverse( $n1, $n2 ); is $num, $n3, 'mod_inverse gives expected result'; -is 1, ( $n1 * $num ) % $n2, 'mod_inverse verified'; \ No newline at end of file +is 1, ( $n1 * $num ) % $n2, 'mod_inverse verified'; + +for my $bits (190..200) { + my $val = Crypt::OpenPGP::Util::get_random_bigint($bits); + my $topbit = Math::BigInt->new("0b1" . ("0" x ($bits-1))); + $topbit->band($val); + ok !$topbit->is_zero, "top bit is set for $bits-bit number"; + $val->brsft($bits); + ok $val->is_zero, "number is exactly $bits bits"; +}
I just wanted to comment on Bytes::Random::Secure as a means of eliminating the pari dependency: Dana Jacobsen has done a lot of work on Bytes::Random::Seed, which is now used to seed Bytes::Random::Secure (previously I was using Crypt::Random::Source, which had a really heavy dependency chain). Now that Bytes::Random::Secure is using Cript::Random::Seed for its seeding, its non-Core dependencies are only the following: Crypt::Random::Seed (which uses Crypt::Random::TESHA2, which has no non-core deps). Math::Random::ISAAC (which has no non-core deps). That's it. Ok, the test suite uses Test::Warn, so there's one other. But that's a lot better than the 40+ non-Core deps we had when we were using Crypt::Random::Source. Furthermore, we've extended backward compatibility all the way to Perl 5.6.x, whereas previously our dependencies prevented B::R::S from being used on Perl's older than 5.8.x. A lot of work has also been done on allowing Bytes::Random::Secure users to configure the seeding as well. At the same time, the default behavior is actually less likely to fall back to weaker seed sources than before. Bytes::Random::Secure v0.20 or newer should be a good fit.
On Sat Jan 26 19:30:41 2013, DAVIDO wrote: Show quoted text
> Dana Jacobsen has done a lot of work on Bytes::Random::Seed, which is > now used to seed Bytes::Random::Secure (previously I was using > Crypt::Random::Source, which had a really heavy dependency chain).
Woops, that should say "Crypt::Random::Seed". :)
In your patch there should be IMO:

- my $hex = Bytes::Random::Secure::random_bytes_hex(($bits + 7) / 8);
+ my $hex = Bytes::Random::Secure::random_bytes_hex(int(($bits + 7) / 8));

--
kmx