Brethren,
I have a threaded Perl ICB client which features client-side end-to-end encryption. It's been working fine for years using Crypt::DH and Crypt::Blowfish (plus supporting modules of course). Crypt::DH is used to create a shared one-time key for Blowfish. I just tried to update my application to use Crypt::DH::GMP, in order to improve performance and reduce the number of module dependencies, and I'm running into an error that I don't fully understand.
Here are the relevant bits of code:
use Module::Load::Conditional qw[can_load check_install requires];
$Module::Load::Conditional::VERBOSE = 1;
my $crypt_mods = {'Crypt::DH::GMP' => undef,
'Crypt::CBC' => undef,
'Crypt::Blowfish' => undef,
'Digest::SHA' => undef,
'MIME::Base64' => undef,
'Compress::Zlib' => undef};
if (can_load(modules => $crypt_mods, verbose => 1))
{
$encryption_avail = 1;
}
my ($DH_public, $DH_public2,
$DH_private, $DH_secret);
[...]
sub create_public_key
{
my $to = lc($_[0]);
my $from = lc($_[1]);
my $player = $_[2];
my ($DH, $p, $g, $i, $public, @chars);
@chars = split(//,($player ? $to : $from));
$g = ord(shift(@chars));
while (scalar @chars)
{
$g += ord(shift(@chars));
}
@chars = split(//, ($player ? $from : $to));
$i = ord(shift(@chars));
while (scalar @chars)
{
$i += ord(shift(@chars));
}
$i %= scalar(@EICB_PRIMES);
$p = $EICB_PRIMES[$i];
$DH = Crypt::DH::GMP->new;
$DH->g($g);
$DH->p($p);
$DH->generate_keys;
$DH_public = $DH->pub_key;
$DH_private = $DH->priv_key;
}
sub create_secret_key
{
my $to = lc($_[0]);
my $from = lc($_[1]);
my $player = $_[2];
my ($DH, $p, $g, $i, $public, @chars);
@chars = split(//,($player ? $to : $from));
$g = ord(shift(@chars));
while (scalar @chars)
{
$g += ord(shift(@chars));
}
@chars = split(//,($player ? $from : $to));
$i = ord(shift(@chars));
while (scalar @chars)
{
$i += ord(shift(@chars));
}
$i %= scalar(@EICB_PRIMES);
$p = $EICB_PRIMES[$i];
$DH = Crypt::DH::GMP->new;
$DH->g($g);
$DH->p($p);
$DH->priv_key($DH_private);
$DH->generate_keys;
$DH_secret = $DH->compute_secret($DH_public2);
}
[...]
sub do_crypt_command
{
my ($who, $args, $time) = @_;
my @args = split(/\s+/, $args);
my $command = shift (@args);
if ($command == $EICB_DH_INIT)
{
create_public_key($who, $cur_nick, 0);
$connection->sendpriv($who,
sprintf('%s%s %s',
$EICB_CRYPT_PREFIX,
$EICB_DH_REPLY,
$DH_public));
}
elsif ($command == $EICB_DH_REPLY)
{
$DH_public2 = shift(@args);
create_public_key($who, $cur_nick, 1);
$connection->sendpriv($who,
sprintf('%s%s %s',
$EICB_CRYPT_PREFIX,
$EICB_DH_REPLY2,
$DH_public));
create_secret_key($who, $cur_nick, 1);
}
elsif ($command == $EICB_DH_REPLY2)
{
$DH_public2 = shift(@args);
create_secret_key($who, $cur_nick, 0);
my $sha1 = Digest::SHA->new;
$sha1->add($logbuffer[rand(scalar @logbuffer)]);
my $key = $sha1->b64digest;
$session_keys{lc($who)} = $key;
encrypt(\$key, $cipher, $DH_secret);
$connection->sendpriv($who,
sprintf('%s%s %s',
$EICB_CRYPT_PREFIX,
$EICB_SESSION_KEY,
$key));
timestamp($time) if ($timestamps_active);
icb_print ($output_window, 'sbrkt', "%s", "[=");
icb_print ($output_window, 'status', "%s", "SECURE");
icb_print ($output_window, 'sbrkt', "%s", "=]");
icb_print ($output_window, 'status', " Session key for user %s
established\n", $who);
$tabhist{lc($who)} = time();
}
[...]
Logic flow is as follows: When client A requests an encrypted session with client B, A sends B a cleartext message containing an EICB_DH_INIT request. If B has encryption enabled, B calls create_public_key( ) and sends its public key back to A in a EICB_DH_REPLY message. A receives the EICB_DH_REPLY message, calls create_public_key( ) to create its own public key, and sends that back to B in a EICB_DH_REPLY2 message. A and B then both call create_secret_key( ), and B generates a one-time Blowfish session key and sends it back to A encrypted using the shared DH secret.
Using Crypt::DH, all of this works. Using Crypt::DH::GMP, create_public_key( ) works, but create_secret_key( ) fails at the
$DH->priv_key($DH_private) statement:
Thread 3 terminated abnormally: Usage: Crypt::DH::GMP::priv_key(dh) at
+ ./icbm line 2434.
I'm not quite sure what I need to change here. The limited documentation in Crypt::DH::GMP does not make any mention of any change in usage of the priv_key( ) method.
Can anyone point out what I'm doing wrong?