#! /usr/bin/perl -w

#################################################################
# A very simple program to implement diffie hellman key exchange#
# and use it with an XOR encryption in a chat conversation	#
#################################################################
#								#
# No usage licence, use it at your will ;)			#
#								#
# jve - julien@linuxwall.info - jan 2007			#
#								#
#################################################################



use strict;
use Socket;
use threads;
use FileHandle;
use Math::BigInt;

unless ($ARGV[0] =~ /^client/ || $ARGV[0] =~ /^server/){
	print 	"\n\n\t\tDiffie Hellman Chat\n\n",
		"\tjve\tt dec 2006\n\nUsage :\n",
		"\tclient : ./dhchat.pl client <ip_srv> <port_srv>\n",
		"\tserver: ./dhchat.pl server <port>\n\n\n";
	exit 0;
}

#########
# server side, listen on the port and wait for connection
# to create 2 thread (send & receive)
# 
if ($ARGV[0] =~ /^server/){

	# use tcp protocol
	my $protocol = getprotobyname('tcp');

	# creating the socket
	socket(SERVER,AF_INET,SOCK_STREAM,$protocol) || die("Unable to open the socket\n$!");

	# reuse the port if already used
	setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, 1) || die("Unable to reuse the socket\n$!");

	# creating network door
	my $netbind = sockaddr_in($ARGV[1], INADDR_ANY);
	
	# bind the port with the socket
	bind(SERVER,$netbind) || die("unable to bind the port $ARGV[1] with the socket\n$!");

	# wait for connection....
	listen(SERVER, 1) || die("Unable to switch to listening mode\n$!");
	print "Server is waiting for tcp connection on port $ARGV[1]...\n";

	# accept an incoming connection
	my $socket = accept(CLIENT, SERVER);

	print	"\t~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
		"\t~ Diffie Hellman Chat 0.1	-	j. vehent	~\n",
		"\t~		    YOU ARE THE SERVER			~\n",
		"\t~							~\n",
		"\t~ Connection established, data are sended in clear	~\n",
		"\t~ To enable protected mode, ask the client to init	~\n",
		"\t~ the DH Key exchange though the '/dhinit' command.	~\n",
		"\t~ Use ctrl+c to exit ;)				~\n",
		"\t~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n";


	# create two thread, one receive paquets, one send
	my $sendthread = threads -> create(\&srvsendmsg);
	my $recvthread = threads -> create(\&srvrecvmsg);

 	my $returnsendthread = $sendthread -> join;
	my $returnrecvthread = $recvthread -> join;

	print "thread close, use ctrl+c to exit\n";
	# close connection
	close(CLIENT);
	close (SERVER);
}

#########
# client side, establish a connection on ip and port
# and create 2 threads (send & receive)
if ($ARGV[0] =~ /^client/){
	
	my $ip = $ARGV[1];
	my $port = $ARGV[2];
	chomp $port;
	print "connection to $ip:$port\n";
	
	my $protocol = getprotobyname('tcp');
	
	# creating tcp socket
	socket(SERVER,AF_INET,SOCK_STREAM,$protocol) || die("Unable to open the socket\n$!");
	
	# reuse if already exist
	setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, 1) || die("Unable to reuse the socket\n$!");
	
	# creating destination
	my $dest = sockaddr_in($port,inet_aton("$ip"));;
	
	# connecting socket to destination
	connect(SERVER, $dest) || die ("Unable to connect to $dest\n$!");
	print "connection done\n";

	print	"\t~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
		"\t~ Diffie Hellman Chat 0.1	-	j. vehent	~\n",
		"\t~			YOU ARE THE CLIENT		~\n",
		"\t~							~\n",
		"\t~ Connection established, data are sended in clear	~\n",
		"\t~ To enable protected mode, init the exchange though	~\n",
		"\t~ the '/dhinit' command.				~\n",
		"\t~ Use ctrl+c to exit ;)				~\n",
		"\t~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n";

	
	# create two thread, one receive paquets, one send
	my $sendthread = threads -> create(\&clisendmsg);
	my $recvthread = threads -> create(\&clirecvmsg);

	# wait for threads before closing
 	my $returnsendthread = $sendthread -> join;
	my $returnrecvthread = $recvthread -> join;

	print "thread close, use ctrl+c to exit\n";
	# close connection
	close (SERVER);
	
}


#####################################################################
############################# subfunction ###########################
#####################################################################

	######## NETWORK #########
	
	
#######
# server function to print and interpret message received from the client
sub srvrecvmsg{
	my $line="NULL";
		while ($line = <CLIENT>){
			chomp $line;
			
			if($line =~ /^INIT-DH-EXCHANGE/i){
				my $dhthread = threads -> create(\&srvdhinit,$line);
				print STDOUT "\t#Diffie Hellman exchange started\n";
				print STDOUT "\t#$line\n";
			}
			else{
				if($line =~ /^CLIENT-SIDE-PART-KEY/i){
					my $dhthread = threads -> create(\&srvdhfinish,$line);
					print STDOUT "\t#$line\n";
				}
				else{
					if( -e "/tmp/dhkey"){
						# call xor with the string and the argument to encode or decode
						my $deciphered = threads->create(\&xor, $line, 0);
						my $returnedstring = $deciphered->join;
						print STDOUT "client> $returnedstring\n";
					}
					else{
						print STDOUT "$line\n";
					}
				}
			}
			
		}
	print STDOUT "sortie du thread \n";
}

#######
# server function to send message to the client
sub srvsendmsg{
	autoflush CLIENT, 1;
	my $msg;
	do{	
		$msg=<STDIN>;
		chomp $msg;
		if ($msg=~/^\/key/i){
			if ( -e "/tmp/dhkey"){
				open (DHFILE, "/tmp/dhkey");
				print STDOUT "\t#Diffie Hellman Key : ",<DHFILE>,"\n";
				close DHFILE;
			}
			else{
				print STDOUT 	"\t#Diffie Hellman Key not defined\n",
						"\t#ask the client to use '/dhinit'\n";

			}
		}
		else{
			if( -e "/tmp/dhkey"){
				my $ciphered = threads->create(\&xor, $msg, 1);
				my $returnedstring = $ciphered->join;
				print CLIENT "$returnedstring\n";
			}
			else{
				print CLIENT "$msg\n";
			}
		}
	}while(1);
	
}

#######
# client function to print and interpret message received from the server
sub clirecvmsg{
	my $line;
		while ($line = <SERVER>){
			if($line =~ /^SERVER-SIDE-PART-KEY/i){
				chomp $line;
				my $dhthread = threads -> create(\&clidhfinish,$line);
				print STDOUT "\t#$line\n";
			}
			else{
				if( -e "/tmp/dhkey"){
					chomp $line;
					my $deciphered = threads->create(\&xor, $line, 0);
					my $returnedstring = $deciphered->join;
					print STDOUT "server> $returnedstring\n";
				}
				else{
					print STDOUT "$line\n";
				}
			}
		}
	print STDOUT "sortie du thread \n";
}

#######
# client function to send message to the client
sub clisendmsg{
	autoflush SERVER, 1;
	my $msg;
	do{	
		$msg=<STDIN>;
		chomp $msg;
		if ($msg=~/^\/key/i){
			if ( -e "/tmp/dhkey"){
				open (DHFILE, "/tmp/dhkey");
				print STDOUT "\t#Diffie Hellman Key : ",<DHFILE>,"\n";
				close DHFILE;
			}
			else{
				print STDOUT 	"\t#Diffie Hellman Key not defined\n",
						"\t#to init DH key, use '/dhinit'\n";
			}
		}
		else{
			if($msg =~ /^\/dhinit/i){
				my $dhtread = threads -> create(\&clidhinit);
				print STDOUT "\t#Diffie Hellman exchange started\n";
			}
			else{
				if( -e "/tmp/dhkey"){
					my $ciphered = threads->create(\&xor, $msg, 1);
					my $returnedstring = $ciphered->join;
					print SERVER "$returnedstring\n";
				}
				else{
					print SERVER "$msg\n";
				}
			}
		}
	}while(1);
	
}


	######### DIFFIE HELLMAN ########

#########
# Init the DH key exchange by generating a random number $x
# and raise it with $g mod $p
#
# send the result to the client
sub srvdhinit{
	my @dhline = @_;
	
	# INIT-DH-EXCHANGE syntax is : INIT-DH-EXCHANGE-$p-$g
	# this block of code get back p and g from the init phrase
	# (we just read the phrase from the end to parse $p and $g)
	my $i = -1;
	while (substr($dhline[0],$i,1) !~ /\-/){$i--;}
	my $g = substr($dhline[0],$i+1,-$i);
	$g = "$g";
	
	my $j=(-$i);
	$i--;
	while (substr($dhline[0],$i,1) !~ /\-/){$i--;}
	my $p = substr($dhline[0],$i+1,-$i-$j-1);
	$p="$p";

	print "\t#p=$p; g=$g\n";
	
	# we need a random number
	$i=1;
	my $x="";
	while ($i<=256){
		$x=$x.int(rand(2));
		$i++;
	}

	# convert n into a bigint via binary conversion
	my $bigx = new Math::BigInt('0');
	foreach my $i (1..256){
		if(substr($x,-$i,1) eq '1'){$bigx->badd(2**($i-1));}
	}
	
	# compute the key in a BIG INTEGER, we use the Math library functions
	print "\t#computing DH part key.........\n";
	my $A = new Math::BigInt($g);
	$A->bmodpow($bigx,$p);

	# send the key to the client
	autoflush CLIENT, 1;
	print CLIENT "SERVER-SIDE-PART-KEY-$A\n";
	print "\t#SERVER-SIDE-PART-KEY-$A\n";

	# store the private values to a physical file
	open (LOCALVALUES,">/tmp/A");
	print LOCALVALUES "$bigx\n$p\n";
	close LOCALVALUES;
}

#######
# Finish DH key generation on server side
# receive the part key from the client and compute the final DH key
sub srvdhfinish{
	my @dhline = @_;
	
	# if the server hasn't finished the to compute the part key, we wait a little
	unless ( -e "/tmp/A"){sleep 5;}
	
	# get the private values
	open(LOCALVALUES,"/tmp/A") or die "Init file does not exist !!!\n$!";
	my $tmp = <LOCALVALUES>;
	my $x = new Math::BigInt($tmp);
	$tmp = <LOCALVALUES>;
	my $p = new Math::BigInt($tmp);
	close LOCALVALUES;
	unlink("/tmp/A");

	print "\t#local values :\n\t#x=$x\n\t#p=$p\n";

	# CLIENT-SIDE-PART-KEY syntax is : CLIENT-SIDE-PART-KEY-$B
	# this block of code get back B, the value computed by the client
	# (we just read the phrase from the end and parse $b)
	my $i = -1;
	while (substr($dhline[0],$i,1) !~ /\-/){$i--;}
	my $B = new Math::BigInt (substr($dhline[0],$i+1,-$i));
	print "\t#B=$B\n";
	
	# compute the final DH key
	print "\t#computing DH key....\n";
	$B->bmodpow($x,$p);
	
	print "\t#final DH key : $B\n";
	
	#store DH Key in local file
	open (DHFILE, ">/tmp/dhkey");
	print DHFILE $B;
	close DHFILE;

	print STDOUT "\t#Diffie Hellman exchange ended\n\t#Use '/key' to view the DH Key\n";

}

#######
# Init the part key on the client side
# The client choose a prime number p and a little number g
# and send them to the server.
# Then, it compute a part key and send it to the server
sub clidhinit{
	print 	"\t#initiating Diffie Hellman exchange\n",
		"\t#generating random 256 bits prime number... please wait (~40s)\n";
	
	my $p = new Math::BigInt('0');

	do{
		# generate a random number composed of random binary digits
		my $i=1;
		my $n="";
		while ($i<=254){
			$n=$n.int(rand(2));
			$i++;
		}
		# add 1 at the beginning and the end of n
		# then $n is big and odd
		$n = "1".$n."1";
	
		# convert n into a bigint via binary conversion
		my $bign = new Math::BigInt('0');
		foreach my $i (1..256){
			if(substr($n,-$i,1) eq '1'){$bign->badd(2**($i-1));}
		}
	
		####################################
		# Fermat test
		#
		# test if (a^(n-1) modulo n) egal 1
		# with a in 2,3,5,7
		# 
		# if n verify this four tests, it's 
		# certainly a prime number !
		#
		my $temoin = new Math::BigInt('2');
		if ($temoin->bmodpow(($bign-1),$bign) == 1){
			$temoin->bone();$temoin->bmul(3);
			if ($temoin->bmodpow(($bign-1),$bign) == 1){
				$temoin->bone();$temoin->bmul(5);
				if ($temoin->bmodpow(($bign-1),$bign) == 1){
					$temoin->bone();$temoin->bmul(7);
					if ($temoin->bmodpow(($bign-1),$bign) == 1){
						$p->bone();
						$p->bmul($bign);
					}
				}
			}
		}
	
	}while ($p == 0);
	print "\t#random prime number is : $p\n";

	# generating random $g between 2 and 9
	my $g=0;
	while ($g<2){$g=int(rand(10));}
	
	my $sequence = "INIT-DH-EXCHANGE-$p-$g";

	print 	"\t#random generator is : $g\n",
		"\t#$sequence\n";

	autoflush SERVER, 1;
	print SERVER "$sequence\n";

	# generating a part key
	# we need a random number
	my $i=1;
	my $x="";
	while ($i<=256){
		$x=$x.int(rand(2));
		$i++;
	}

	# convert n into a bigint via binary conversion
	my $bigx = new Math::BigInt('0');
	foreach my $i (1..256){
		if(substr($x,-$i,1) eq '1'){$bigx->badd(2**($i-1));}
	}
	
	# compute the key in a BIG INTEGER, we use the Math library functions
	print "\t#computing DH part key.........\n";
	my $B = new Math::BigInt($g);
	$B->bmodpow($bigx,$p);

	# send the key to the client
	autoflush CLIENT, 1;
	print SERVER "CLIENT-SIDE-PART-KEY-$B\n";
	print "\t#CLIENT-SIDE-PART-KEY-$B\n";

	# store the private values to a physical file
	open (LOCALVALUES,">/tmp/B");
	print LOCALVALUES "$bigx\n$p\n";
	close LOCALVALUES;

}

sub clidhfinish{
	my @dhline = @_;
	
	# wait for the client part key generation
	sleep 5;
	
	#########################
	# get the private values
	open(LOCALVALUES,"/tmp/B") or die "Init file does not exist !!!\n $!";

	my $tmp = <LOCALVALUES>;
	my $x = new Math::BigInt($tmp);
	$tmp = <LOCALVALUES>;
	my $p = new Math::BigInt($tmp);
	close LOCALVALUES;
	unlink("/tmp/B");

	print "\t#local values :\n\t#x=$x\n\t#p=$p\n";

	###################################################################
	# SERVER-SIDE-PART-KEY syntax is : SERVER-SIDE-PART-KEY-$B
	# this block of code get back A, the value computed by the client
	# (we just read the phrase from the end and parse $b)
	my $i = -1;
	while (substr($dhline[0],$i,1) !~ /\-/){$i--;}
	my $A = new Math::BigInt (substr($dhline[0],$i+1,-$i));
	print "\t#A=$A\n";
	
	# compute the final DH key
	print "\t#computing DH key : (A exp x) mod p\n";
	$A->bmodpow($x,$p);
	
	print "\t#final DH key : $A\n";

	#store DH Key in local file
	open (DHFILE, ">/tmp/dhkey");
	print DHFILE $A;
	close DHFILE;

	print STDOUT "\t#Diffie Hellman exchange ended\n\t#Use '/key' to view the DH Key\n";

}


	######### CIPHERING / DECIPHERING ########

sub xor{
	my @args = @_;
	my $binstring = $args[0];chomp $binstring;
	my $encode = $args[1];
	
	# if string is not in binary, convert it into binary form
	if ($encode==1){
		my $temp = $binstring;
		$binstring = unpack('B*',pack ('a*', $temp));
		# we need to remember that this is a ciphering operation
		# and not a deciphering for the ascii transformation
	}

	# in decode mode, we convert the binary string in "ascii binary"
	if($encode==0){
		my $tmp = new Math::BigInt($binstring);
		$binstring="";
		while ($tmp!=0){
			$binstring = ($tmp % 2).$binstring;
			$tmp->bdiv(2);
		}
	}

	# find how many block of 256 bits we need 
	my $stringlength = length($binstring);
	my $number_of_blocks = 1 + int($stringlength / 256);
	unless((length($binstring)<256) or (length($binstring)%256 == 0)){ $number_of_blocks++;}

	# cut the string in 256 bits blocks
	my $i=0;
	my @blocks;
	for my $j (0 .. $number_of_blocks-1) {
		if (length($binstring) - $i >= 256){
			$blocks[$j] = substr($binstring, $i, 256);
			$i+=256;
		}
		else {
			$blocks[$j] = substr($binstring, $i, length($binstring)-$i);
			$i += length($binstring)-$i
		}
	}

	# take the key from local file
	open (DHFILE,"/tmp/dhkey");
	my $div= new Math::BigInt(<DHFILE>);
	close DHFILE;
	
	# convert the key in binary
	my $binkey="";
	while ($div!=0){
		$binkey = ($div % 2).$binkey;
		$div->bdiv(2);
	}
	
	###################################
	# XOR TRANSFORMATION OF THE BLOCKS
	my $c = new Math::BigInt('0');
	foreach my $b (@blocks){

		# XOR TABLE : 
		# bitstring   bitkey	result
		#   0		 0	   0
		#   0		 1	   1
		#   1		 0	   1
		#   1		 1	   0
		for ($i=0;$i<length($b);$i++){
			# if the bits from the key and the block are equal
			# then, the resultant bit is 0, else it's 1
			if (substr($binkey,$i,1) eq substr($b,$i,1)){
					# add a 0 at the end of the binary chain (left shift)
					$c->blsft(1,2);	# 1=> nb of decal; 2=> the base
			}
			else {
					# add a 0 and convert it to 1
					$c->blsft(1,2);	# left shift
					$c->bior(1);	# inclusive OR with 1
			}
		}
	}

	# in decipher mode, we need to convert the binary string in ascii
	if($encode==0){

		# new chain to return the deciphered string
		my $m="";
		do{
			# we store the last 8 bits of the ciphered string
			# using a logical AND (511 = 11111111)
			my $asciicode = ($c & 255);
			
			# "pack" convert the ascii code in a char
			$m = pack("C",$asciicode).$m;
			
			# left shift of 8 bits to reduce the size of the string
			$c->brsft(8,2);
			
		# do it while the string is not empty
		}while (length($c) > 1);
		
		# return the deciphered string
		return $m;
		
	}

	# returned the ciphered string
	return $c;
}


