Python Notes: Diffie-Hellman (web demo)

  1. The information presented here is intended for educational use.
  2. The information presented here is provided free of charge, as-is, with no warranty of any kind.
Edit: 2019-11-27 (changes to "cgi.FieldStorage")

Overview

  • this application is composed of five files
    1. one file of HTML (web page)
    2. one file of JavaScript routines (only run client-side)
    3. one optional file which only launches my server-side program
    4. one file containing my Python3 program (only run server-side)
    5. one file containing my Python3 functions (only run server-side)
  • clicking a button on the web page calls a JavaScript routine
    • one JavaScript routine, named doReset(), works entirely client-side without annoying the server
    • two JavaScript routines, named setDefaults1() and setDefaults2(), will call the server (via AJAX) expecting some sort of form initialization
    • one JavaScript routine, named runDemo(), reads all the fields defined in the HTML form, tests them, then passes their contents on to the server (via AJAX) for processing
  • because this is a simple how-to demo, many of the routines are simplistic (e.g. no reliance on JavaScript libraries: jQuery, AngularJS, ReactJS although I was tempted to do so (the code would have been smaller but not readable by everyone)
  • there are numerous ways to pass data back to HTML but here I used XML because all browsers contain routines to perform XML parsing

file-1: HTML web page

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Diffie-Hellman Demo (via Python)</title>
 <script src="/js/dh_demo.js"></script>
</head>
<body style="font-family: Courier, Courier New, monospace; font-weight:bold">
 <h4>Diffie-Hellman Demo (via Python)</h4>
 <form id="form1">
  <div style="width:400px;text-align:right">
   Prime (p) <input id="p" type="text" size="20" maxlength="20"><br>
   Generate (g) <input id="g" type="text" size="20" maxlength="20"><br>
   Alice secret (a) <input id="a1" type="text" size="20" maxlength="20"><br>
   Bob's secret (b) <input id="b1" type="text" size="20" maxlength="20"><br>
   <hr>
   Alice sends (A) <input id="a2" type="text" size="20" maxlength="20" readonly="readonly"><br>
   Bob   sends (B) <input id="b2" type="text" size="20" maxlength="20" readonly="readonly"><br>
   Alice computes (X) <input id="a3" type="text" size="20" maxlength="20" readonly="readonly"><br>
   Bob   computes (Y) <input id="b3" type="text" size="20" maxlength="20" readonly="readonly"><br>
   <hr>
   <button type="button" id="but1" onclick="setDefaults1();">Defaults 1</button>
   <button type="button" id="but2" onclick="setDefaults2();">Defaults 2</button>
   <button type="button" id="but3" onclick="runDemo();">Run Demo</button>
   <button type="button" id="but4" onclick="doReset();">Reset</button>
  </div>
 </form><br>
 <div id="msg"></div><br>
 <hr>
 <div style="font-family:Calibri, Arial, sans-serif;font-weight:normal">
  <p>Neil Rieck<br>Waterloo, Ontario, Canada.<br>
   <a href="http://neilrieck.net" target="_blank">http://neilrieck.net</a></p>
 </div>
</body>
</html>

file-2: JavaScript

// title  : cgi-bin/dh_demo.js
// author : Neil Rieck
// created: 2019.09.01
// notes  : this demo done with plain JavaScript
//          (the next version will employ jQuery)
// ==============================================
var state=0;					// ajax state
var timeOutId1=0;				// timer ID
var debugFlag=0;
var nsr_domain=window.location.hostname;	// server address
var nsr_protocol=window.location.protocol;	// http or https
var p=0;					//
var g=0;					//
var a1=0;					//
var b1=0;					//
//
//	called by the RESET button
//
function doReset(){
    state=0;
    if (timeOutId1!=0){
        clearTimeout(timeOutId1);
        timeOutId1=0;
    }
    document.getElementById("p").value="";
    document.getElementById("g").value="";
    document.getElementById("a1").value="";
    document.getElementById("a2").value="";
    document.getElementById("a3").value="";
    document.getElementById("b1").value="";
    document.getElementById("b2").value="";
    document.getElementById("b3").value="";
    document.getElementById("msg").innerHTML="Reset Done";
}
//
//	called by the "SET DEFAULTS 1" button
//
function setDefaults1() {
    //alert("this is a test: 1");
    send_ajax("setDefaults1");
}
//
//	called by the "SET DEFAULTS 2" button
//
function setDefaults2() {
    //alert("this is a test: 2");
    send_ajax("setDefaults2");
}
//
//	read form fields
//
function readFields(){
    p = document.getElementById("p").value;
    g = document.getElementById("g").value;
    a1= document.getElementById("a1").value;
    b1= document.getElementById("b1").value;
}
//
//	called by the RUN DEMO button
//
function runDemo() {
    //alert("this is a test: 2");
    readFields();
    if (isStrInt(p)==false){
	document.getElementById("msg").innerHTML="error, 'p' is not an integer";
	return;
    }
    if (isStrInt(g)==false){
        document.getElementById("msg").innerHTML="error, 'g' is not an integer";
        return;
    }
    if (isStrInt(a1)==false){
        document.getElementById("msg").innerHTML="error, 'a' is not an integer";
        return;
    }
    if (isStrInt(b1)==false){
        document.getElementById("msg").innerHTML="error, 'b' is not an integer";
        return;
    }
    send_ajax("runDemo");
}
//
//	send_ajax (send an HTTP message via AJAX)
//
function send_ajax(action){
    switch(state){
    case 0:
	document.getElementById("msg").innerHTML="Connecting";
        var msg = "";				// init
	switch(action){
        case "runDemo":
	    msg=nsr_protocol+"//"+nsr_domain+"/cgi-bin/dh20?op="+ action + "&p="+p+"&g="+g+"&a1="+a1+"&b1="+b1;
	    start_ajax(msg);
	    break;
	case "setDefaults1":
	case "setDefaults2":
	    msg=nsr_protocol+"//"+nsr_domain+"/cgi-bin/dh20?op="+ action;
	    start_ajax(msg);
	    break;
	default:
	    document.getElementById("msg").innerHTML="programmer error (001)";
	    break;
	}
    default:
	document.getElementById("msg").innerHTML="Please wait (state:"+state+")";
	break;
    }
}
//
//	start_ajax (start an AJAX transaction)
//
function start_ajax(msg){
    response=null;
    if (typeof XMLHttpRequest == "undefined")
    XMLHttpRequest=function(){
	try { return new ActiveXObject("Msxml2.XMLHTTP.6.0")    } catch (e) {}
	try { return new ActiveXObject("Msxml2.XMLHTTP.3.0")    } catch (e) {}
	try { return new ActiveXObject("Msxml2.XMLHTTP")        } catch (e) {}
	try { return new ActiveXObject("Microsoft.XMLHTTP")     } catch (e) {}
	throw new Error("This browser does not support XMLHttpRequest or XMLHTTP.")
    }
    if (msg != ""){
	response=new XMLHttpRequest();
	if (response != null){
	    response.onreadystatechange=ajax_event_handler;
	    response.open("GET", msg, true);		// async=true
	    response.send(null);			// not null for POST
	}
	state = 1;
	init_timer1();
    }
}
//	ajax_event_handler (only executed if something is received) 
//
function ajax_event_handler(){
    if (response.readyState == 4){
	state=0;
	if (timeOutId1!=0){
	    clearTimeout(timeOutId1);
	    timeOutId1=0;
        }
        if (response.status == 200)
        {   document.getElementById("msg").innerHTML="Ready";
	    var resp$=response.responseText;
	    if (debugFlag==1) {
		alert("raw data:"+htmlEncode(resp$));
            }
	    if (window.DOMParser) {
		parser=new DOMParser();
		gXmlDoc=parser.parseFromString(resp$,"text/xml");
	    }else{
		gXmlDoc=new ActiveXObject("Microsoft.XMLDOM");
		gXmlDoc.async=false;
		gXmlDoc.loadXML(resp$);
	    }
	    var status= get_xml_data("status");
	    var fldP  = get_xml_data("p");
	    var fldG  = get_xml_data("g");
	    var fldA1 = get_xml_data("a1");
	    var fldB1 = get_xml_data("b1");
	    var fldMsg= get_xml_data("msg");
	    var fldA2 = get_xml_data("a2");
	    var fldB2 = get_xml_data("b2");
	    var fldA3 = get_xml_data("a3");
	    var fldB3 = get_xml_data("b3");
	    var status = 1;
	    if ((status == 1)||(status == 2)) {
//		document.getElementById("msg").innerHTML="raw data:"+htmlEncode(resp$);
		if (fldP !=null) document.getElementById("p").value=fldP;
		if (fldG !=null) document.getElementById("g").value=fldG;
		if (fldA1!=null) document.getElementById("a1").value=fldA1;
		if (fldB1!=null) document.getElementById("b1").value=fldB1;
		if (fldA2!=null) document.getElementById("a2").value=fldA2;
		if (fldB2!=null) document.getElementById("b2").value=fldB2;
		if (fldA3!=null) document.getElementById("a3").value=fldA3;
		if (fldB3!=null) document.getElementById("b3").value=fldB3;
//		document.getElementById("msg").innerHTML="p:"+fldP+" g:"+fldG+" b3:"+fldB3
		if (fldMsg!=null) document.getElementById("msg").innerHTML=fldMsg;
	    }else{
		document.getElementById("msg").innerHTML="error:"+status;
	    }
	}
    }
}
//
//	the server must answer back in 3 seconds
//
function init_timer1(){
    if (timeOutId1==0){					// if available
	timeOutId1=setTimeout("timer_job1();",3000);	// arm for 3 seconds
    }
}
//
//	this code is ONLY executed on TIMEOUT
//
function timer_job1(){
    state=0;						// reset state
    if (timeOutId1!=0){
	clearTimeout(timeOutId1);
	timeOutId1=0;
    }
    var txt="Error: the timer has expired";
    document.getElementById("msg").innerHTML=txt;
}
//
//	does string 'x' represent a positive integer?
//
function isStrInt(x){
x=x.replace(/\s/g,'');
    if (x=="") return false;
    try{
	y = parseInt(x);
    }catch(e){
	y = -1;
    }
    if(y>0){
    	return true;
    }else{
	return false;
    }
}
function htmlEncode(str) {
    return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
}
//
//	get_xml_data (look for one item)
//
function get_xml_data(tag){
    var x;
    try{
	x = gXmlDoc.getElementsByTagName(tag)[0].childNodes[0].nodeValue;
    }catch(e){
//	alert("t:"+tag+" e:"+e);
	x = null;
    }
    return(x);
}
//
//	get_xml_data2 (look for numerous similar items)
//
function get_xml_data2(xmlObj, tag){
    var x;
    try{
	x = xmlObj.getElementsByTagName(tag)[0].childNodes[0].nodeValue;
    }catch(e){
//	alert("t:"+tag+" e:"+e);
        x = null;
    }
    return(x);
}

file-3: Python3 (optional launcher)

#!/usr/bin/python3
# ----------------------
# Title  : cgi-bin/dh_20 (caveat: NO FILE EXTENSION)
# Author : Neil Rieck
# created: 2019-11-05
# purpose: ensure the use of compiled python (.pyc)
# notes  : this filename has no extension 
# ----------------------
import dh20_main	# use this auto-compiled module 
# dh20_main.main()	# enable if module only contains functions 

file-4: Python3 (program)

#!/usr/bin/python3
# ------------------------------------------------------------
# Title  : dh20_main.py (this is the web version)
# Author : Neil Rieck
# created: 2019-08-31
# notes  :
# 1) Diffie-Hellman key-exchange demo
# 2) references:
#    https://en.wikipedia.org/wiki/Diffie-Hellman_key_exchange
# ------------------------------------------------------------
#
# uncomment this next line if your ISP is stuck at Python2
# from __future__ import print_function
#
import math, sys, cgi, cgitb, os, datetime
#
#	my Diffie-Hellman support routines live here
#
import dh20_module as dhm
#
#	declare a few variables
#
a2=""
b2=""
a3=""
b3=""
#
#	just a little hacking (probably only valid from HTTP GET)
#
try:
    query_string = os.environ['QUERY_STRING']
except:
    query_string ="BLANK"
#
#	extract CGI field data
#
fld  = cgi.FieldStorage(keep_blank_values=True)	# grab all CGI data
if 'p' in fld:
    p = fld.getvalue('p')
else:
    p = "?"
if 'g' in fld:
    g  = fld.getvalue('g')
else:
    g = "?"
if 'a1' in fld:
    a1 = fld.getvalue('a1')
else:
    a1 = "?"
if 'b1' in fld:
    b1 = fld.getvalue('b1')
else:
    b1 = "?"
if 'op' in fld:
    op = fld.getvalue('op')
else:
    op = "setDefaults1"
#
#	just a little hacking/logging (SELinux needs to allow it)
#
try:
    fn="dhlogs/dh20_"+datetime.datetime.now().strftime('%Y%m%d')+".txt"
    f = open(fn,"a")
    f.write("new event: %s\r\n" % datetime.datetime.now().strftime('%Y%m%d%H%M%S'))
    f.write("q  %s\r\n" % query_string)
    f.write("p  %s\r\n" % p)
    f.write("g  %s\r\n" % g)
    f.write("a1 %s\r\n" % a1)
    f.write("b1 %s\r\n" % b1)
    f.write("op %s\r\n" % op)
    f.write("f  %s\r\n" % fld)
    f.close()
except:
    z=""
#
#	send a response back to Apache
#	note: some popular return types include text/html, text/xml, text/plain
#
print('Content-Type: text/xml; charset=windows-1252')		# we're returning an XML response
print('')							# end of HTTP header
print('<?xml version="1.0" encoding="iso-8859-1"?>')		# start of XML content
print('<result>')						# user-defined by me (see JavaScript)
if op=="setDefaults1":
    print('<status>1</status>')
    print('<msg>Defaults from the Wikipedia article</msg>')
    print('<p>23</p><g>5</g><a1>4</a1><b1>3</b1>')
    print('<a2> </a2><b2> </b2><a3> </a3><b3> </b3>')
elif op=="setDefaults2":
    print('<status>1</status>')
    print('<msg>Using programmer defaults</msg>')
    print('<p>10007</p><g>5</g><a1>456</a1><b1>789</b1>')
    print('<a2> </a2><b2> </b2><a3> </a3><b3> </b3>')
elif op=="runDemo":
    try:
        (status, msg, a2, b2, a3, b3) = dhm.dh_machine(p,g,a1,b1)
    except Exception as e:
        status = 95
        #msg = 'yikes'
        msg = "{}".format(e)
    print('<status>'+ str(status) +'</status>')
    #
    # since we're sending back XML it might be wise to escape...
    # ...the contents of msg like so: html.escape()
    #
    print('<msg>'+ cgi.escape(msg) +'</msg>')
    print('<a2>'+ str(a2) + '</a2><b2>'+ str(b2) +'</b2><a3>'+ str(a3) +'</a3><b3>'+ str(b3) +'</b3>')
else:
    print('<status>1</status>')
    print('<msg>Defaults from the Wikipedia article</msg>')
    print('<p>23</p><g>5</g><a1>4</a1><b1>3</b1>')
    print('<a2> </a2><b2> </b2><a3> </a3><b3> </b3>')
print('</result>')
print('')
sys.exit()

file-5: Python3 (routines)

#!/usr/bin/python3 
# =========================================================
# file   : dh20_module.py
# author : Neil Rieck
# created: 2019-09-07
# purpose: python functions here are called by dh20_main.py
# =========================================================
#
# ===================================
#   test if n is prime
# ===================================
import math
def is_prime(n):
    n=int(n)
    if (((n % 2) == 0) and (n > 2)):
        return False
    for i in range(3, int(math.sqrt(n)) + 1, 2):
        if n % i == 0:
            return False
    return True
# ===========================================
#   test if n is a non-zero positive integer
# ===========================================
def is_nzp_integer(n):
    try:
        n=int(n)
    except:
        n=0
    if (n>0):
        return n
    else:
        return 0
# ===================================
#   get_primes hint
# ===================================
def get_primes_hints():
    a = "(hint) primes between 500 and 999: "
    for I in range(3,1000):
        if (is_prime(I)==True):
            a += i +" "
# ========================================================
#   Diffie-Hellman Key Exchange Demo
#   4   items are pass   in   (p,g,a1,b1)
#   2+4 items are passed back (status,msg,a2,b2,a3,b3)
# ========================================================
def dh_machine(p,g,a1,b1):
    if 1 != 1:			# enable to debug
        try:
            fn="dhlogs/dh20_"+datetime.datetime.now().strftime('%Y%m%d')+".txt"
            f=open(fn,"a")
            f.write("--- in function ---\r\n")
            f.write("p  %s\r\n" % p)
            f.write("g  %s\r\n" % g)
            f.write("a1 %s\r\n" % a1)
            f.write("b1 %s\r\n" % b1)
        except:
            z = 0
        f.write("1)\r\n")
    #
    # even though we have pre-tested this data in javascript,
    # test it again
    #
    if is_prime(p):
        p=int(p)
    else:
        return (99,"error, P is not a prime number",0,0,0,0)
    #
    if is_nzp_integer(g)>0:
        g=int(g)
    else:
        return (99,"error, G is not a positive integer",0,0,0,0)
    #
    if is_nzp_integer(a1)>0:
        a1=int(a1)
    else:
        return (99,"error, A is not a positive integer",0,0,0,0)
    #
    if is_nzp_integer(b1)>0:
        b1=int(b1)
    else:
        return (99,"error, B is not a positive integer",0,0,0,0)
    #
    #   Alice Sends Bob: a2 = g^a1 mod p
    #
    a2 = (g**a1) % p
    # 
    #   Bob Sends Alice: b2 = g^b1 mod p
    #
    b2 = (g**b1) % p
    #
    #   <<< Private computation's begin >>>
    #
    #   Alice Employs Bob's Shared Secret: s = b2^a1 mod p
    #
    a3 = (b2**a1) % p
    # 
    #   Bob Employs Alice's Shared Secret: s = a2^b1 mod p
    #
    b3 = (a2**b1) % p
    #
    # caveat: embedded HTML will mess up XML so needs to be escaped
    #
    msg = ("D-H solution: symmetric key = " + str(a3) +
        "<br>Evesdropper 'Eve' only sees: p, g, A, B")
    return (1,msg,a2,b2,a3,b3)
# ================================================================

External Links


 Back to Home
Neil Rieck
Waterloo, Ontario, Canada.