Cryptopals 34 - Implement a MITM key-fixing attack on Diffie-Hellman...

TL;DR

Challenge 34 in Cryptopals.

This series about cryptopals is increasingly getting skewed over the code side instead of the explanation side.

One reason is that many other sources explain stuff very nicely. Other times, though, the challenge itself tells us what to do, so it’s a SMOP. A very instructive SMOP, I daresay.

#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';

use File::Basename 'dirname';
use lib dirname(__FILE__);
use DiffieHellman;

package EndUser;
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
use DiffieHellman;
use Digest;
use CryptoPals qw< aes_cbc_encrypt aes_cbc_decrypt random_octets
   validate_pkcs7_pad >;

sub new ($package, %args) { bless { session_for => {} }, $package }

sub new_session ($self, $name, %args) {
   $self->{session_for}{$name} = \my %session;
   $args{NIST_parameters} = 1 unless exists $args{p};
   my $dh = $session{dh} = DiffieHellman->new(%args);
   @session{qw< my_secret my_public >} = $dh->generate_key_pair;
   $self->register_peer_public_key($name, $args{public})
      if exists $args{public};
   return { g => $dh->{g}, p => $dh->{p}, public => $session{my_public} };
}

sub register_peer_public_key ($self, $name, $public_key) {
   $self->new_session($name) unless exists $self->{session_for}{$name};
   my $session = $self->{session_for}{$name};
   my $secret = $session->{shared_secret} = $session->{dh}->joint_secret(
      $session->{my_secret}, $session->{peer_public} = $public_key);
   my $digest = Digest->new('SHA-1')->add($secret)->digest;
   $session->{shared_aes_key} = substr $digest, 0, 16;
   return $self;
}

sub encrypt_message_for ($self, $name, $plaintext) {
   my $key = $self->{session_for}{$name}{shared_aes_key};
   my $iv = random_octets(16);
   my $encrypted = aes_cbc_encrypt($plaintext, $key, $iv);
   return $iv . $encrypted;
}

sub decrypt_message_from ($self, $name, $ciphertext) {
   my $key = $self->{session_for}{$name}{shared_aes_key};
   my $iv = substr $ciphertext, 0, 16, '';
   return validate_pkcs7_pad(aes_cbc_decrypt($ciphertext, $key, $iv), 16);
}

1;

package MitmUser;
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
use DiffieHellman;

our @ISA = qw< EndUser >;

sub new_session ($self, $name, %args) {
   $self->{session_for}{$name} = \my %session;
   die "no p\n" unless exists $args{p};
   my $dh = $session{dh} = DiffieHellman->new(%args);
   my $my_public = $session{my_public} =
      pack 'H*', Math::BigInt->new($args{p})->to_hex;
   my $secret = $session{shared_secret} =
      $dh->joint_secret("\x05", $my_public);
   my $digest = Digest->new('SHA-1')->add($secret)->digest;
   $session{shared_aes_key} = substr $digest, 0, 16;
   return { g => $dh->{g}, p => $dh->{p}, public => $session{my_public} };
}

1;

package main;

use Test::More;

my $Alice = EndUser->new;
my $Bob = EndUser->new;

{
   my $Alice_start = $Alice->new_session('bob');
   my $Bob_start = $Bob->new_session(alice => $Alice_start->%*);
   $Alice->register_peer_public_key(bob => $Bob_start->{public});

   my $msg = 'Hello my friends! How are you doing?!?';
   my $ciphertext = $Alice->encrypt_message_for(bob => $msg);
   my $plaintext = $Bob->decrypt_message_from(alice => $ciphertext);

   is $msg, $plaintext, 'direct, message was received correctly';
}

my $Eve = MitmUser->new;

{
   my $Alice_start = $Alice->new_session('bob');
   my $Eve_start = $Eve->new_session('alice_bob' => $Alice_start->%*);
   my $Bob_start = $Bob->new_session(alice => $Eve_start->%*);
   $Alice->register_peer_public_key(bob => $Eve_start->{public});

   my $alice_msg = 'Hello my friends! How are you doing?!?';
   my $alice_ciphertext = $Alice->encrypt_message_for(bob => $alice_msg);
   my $bob_plaintext = $Bob->decrypt_message_from(
      alice => $alice_ciphertext);

   is $alice_msg, $bob_plaintext,
      'relayed, message was received correctly';

   my $eve_plaintext = $Eve->decrypt_message_from(
      alice_bob => $alice_ciphertext);
   is $alice_msg, $eve_plaintext, 'eavesdropped message is correct';
}

done_testing();

So, once again, we are compelled to read the fine lines on the can. They explain the limits of the content, along with where and when we can use it.

In this case, it’s a memento that the Diffie-Hellman algorithm is genius for allowing to exchange a shared key over an eavesdropping channels, but does nothing against active attackers. We have been warned.

Stay safe and secure!


Comments? Octodon, Twitter, GitHub, Reddit, or drop me a line!