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.
  3. Edit: 2025-02-08 (changes for python3.9)

Overview

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="https://neilrieck.net" target="_blank">http://neilrieck.net</a></p>
 </div>
</body>
</html>

file-2: JavaScript

// ========================================================
// title  : dh_demo.js
// author : Neil Rieck
// created: 2019-09-01
// edit   : 2025-02-07 (tweaks)
// notes  : this demo is done with plain JavaScript (the next version will employ jQuery)
// ========================================================
var state=0;								// ajax state
var timeOutId1=0;							// timer ID
var debugFlag=0;							//
var p=0;									//
var g=0;									//
var a1=0;									//
var b1=0;									//
var SECS=15;                				//
var MS=SECS*1000;           				//
var nsr_domain=window.location.hostname;	// server address
var nsr_protocol=window.location.protocol;	// http or https
//
//	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="";
    writeMsg("Reset Done");
}
function writeMsg(x){
    console.log("msg:",x);
	// document.getElementById("msg").value=x;		// if element is INPUT
	document.getElementById("msg").innerHTML=x;		// if element is DIV
	console.log("msg done");
}
//
//	called by the "SET DEFAULTS 1" button
//
function setDefaults1() {
    send_ajax("setDefaults1");
}
//
//	called by the "SET DEFAULTS 2" button
//
function setDefaults2() {
    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() {
    readFields();
    if (isStrInt(p)==false){
		writeMsg("error, 'p' is not an integer");
		return;
    }
    if (isStrInt(g)==false){
        writeMsg("error, 'g' is not an integer");
        return;
    }
    if (isStrInt(a1)==false){
        writeMsg("error, 'a' is not an integer");
        return;
    }
    if (isStrInt(b1)==false){
        writeMsg("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:
		writeMsg("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:
				writeMsg("programmer error (001)");
				break;
		}
    default:
		writeMsg("Please wait up to "+SECS+" seconds (state:"+state+")");
		break;
    }
}
//
//	start_ajax (start an AJAX transaction)
//
function start_ajax(cmd){
    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 (cmd != ""){
		//
		//	code to block AJAX caching
		//
		var dt		= new Date();			// today's local date time on YOUR machine
		var yyyy	= dt.getFullYear();		// 2000
		var mm		= dt.getMonth();		// 0-11
		var dd		= dt.getDate();			// 1-28{,29,30,31}
		var hh		= dt.getHours();
		var mm		= dt.getMinutes();
		var ss		= dt.getSeconds();
		var milli	= dt.getMilliseconds();
		var stamp	= "" + yyyy + mm + dd + hh + mm + ss +"."+ milli;
		// console.log("time:",stamp);
		//
		//	original code continues
		//
		response=new XMLHttpRequest();
		if (response != null){
			cmd += "&timestamp=" + stamp;		// to foil browser caching
			console.log("cmd:",cmd);
			response.onreadystatechange=ajax_event_handler;
			response.open("GET", cmd, 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) {
            writeMsg("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 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= get_xml_data("status");
	        // 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) writeMsg(fldMsg);
			}else{
				writeMsg("error:"+status);
			}
		}
    }
}
//
//	the server must answer back in 15 seconds
//
function init_timer1(){
    if (timeOutId1==0){					                // if available
	    timeOutId1=setTimeout("timer_job1();",MS);      // arm timer
    }
}
//
//	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 after "+SECS+" secs";
    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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
//
//	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)

#!/bin/python3.9
# -------------------------------
# title  : /var/html/cgi-bin/dh21
# author : Neil Rieck
# created: 2019-10-20
# purpose: ensure use of compiled python (.pyc)
# -------------------------------
import dh21_main    # use this auto-compiled module 
dh21_main.main()    # enable if module only contains functions
#

file-4: Python3 (program)

#!/usr/bin/python3.9
# ================================================================
# Title   : dh21_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
# who when       what
# NSR 2019-08-31 dh20 - original work for python3.6
# NSR 2025-02-06 dh21 - updated for python3.9 (cgi.escape >> html.escape)
# ================================================================
#
import math, sys, cgi, cgitb, os, datetime, html
#
#	my Diffie Hellman support routines live here
#
import dh21_module as dhm
#   -----------
#   CODE BEGINS
#   -----------

#
#   logIt (a simple logger)
#   note: SELinux needs to allow this
#
def logIt(msg, stamp14, debug):
    try:
        stamp8 = stamp14[:8]
        fn=f"dh-log/dh20_{stamp8}.txt"
        f = open(fn,"a")
        f.write(f"-i-event: {stamp14}\n"
                f"-i-msg: {msg}\n=====\n")
        f.close()
    except Exception as e:
        if debug > 0:
            print(f"-e-error: {e}")


#
#   the name says it all
#
def main():
    #
    #	declare a few Alice-Bob variables
    #
    a1 = a2 = a3 = ""
    b1 = b2 = b3 = ""
    #
    #   declare a few program variables
    #
    debug = 0 
    stamp14 = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
    method = os.environ.get('REQUEST_METHOD', "BLANK")
    query_string = os.environ.get('QUERY_STRING', "BLANK")
    #
    #   a little hook for interactive testing
    #
    if method == "BLANK":
        debug = 1
        print("-i-interactive debug mode")
        print(f"-i-QUERY_STRING: {query_string} (from your shell)")
    #
    #	extract CGI field data
    #
    fld  = cgi.FieldStorage(keep_blank_values=True)     # grab all CGI data
    p = fld.getvalue('p', '?')
    g = fld.getvalue('g', '?')
    a1 = fld.getvalue('a1', '?')
    b1 = fld.getvalue('b1', '?')
    op = fld.getvalue('op', 'setDefaults1')
    #
    log = (f"q : {query_string}\n"
           f"p : {p}\n"
           f"g : {g}\n"
           f"a1: {a1}\n"
           f"b1: {b1}\n"
           f"op: {op}\n"
           f"f : {fld}\n"
           f"m : {method}")
    logIt(log, stamp14, debug)
    #
    #	send a response back to Apache
    #	note: some popular return types include text/html, text/xml, text/plain
    #
    resp = ('Content-Type: text/xml; charset=windows-1252\n'	    # we're returning an XML response
            '\n'							                        # end of HTTP header
            '<?xml version="1.0" encoding="iso-8859-1"?>\n'	        # start of XML content
            '<result>\n')						                    # user-defined by me (see JavaScript)
    if (op=="setDefaults1"):
        resp += ('<status>1</status>\n'
                 '<p>23</p><g>5</g><a1>4</a1><b1>3</b1>\n'
                 '<a2> </a2><b2> </b2><a3> </a3><b3> </b3>\n'
                 '<msg>Defaults from the Wikipedia article</msg>\n')
    elif op=="setDefaults2":
        resp += ('<status>1</status>\n'
                 '<p>10007</p><g>5</g><a1>456</a1><b1>789</b1>\n'
                 '<a2> </a2><b2> </b2><a3> </a3><b3> </b3>\n'
                 '<msg>Using programmer defaults</msg>\n')
    elif op=="runDemo":
        try:
            (status, msg, a2, b2, a3, b3) = dhm.dh_machine(p, g, a1, b1)
            log = (f"verify:\n"
                   f"status: {status}\n"
                   f"a2: {a2}\n"
                   f"b2: {b2}\n"
                   f"a3: {a3}\n"
                   f"b3: {b3}\n"
                   f"msg: {msg}")
            logIt(log, stamp14, debug)
        except Exception as e:
            status = 95
            msg = f"{e}"
            logIt(msg)

        #
        #	since we're sending back XML it might be wise to escape...
        #	...the contents of msg like so: html.escape()
        #
        msg2 = html.escape(msg)
        resp += ('<status>'+ str(status) +'</status>\n'
                 f'<a2>{a2}</a2>\n'
                 f'<b2>{b2}</b2>\n'
                 f'<a3>{a3}</a3>\n'
                 f'<b3>{b3}</b3>\n'
                 f'<msg>{msg2}</msg>\n')
    else:
        resp = ('<status>1</status>\n'
                '<p>23</p><g>5</g><a1>4</a1><b1>3</b1>\n'
                '<a2> </a2><b2> </b2><a3> </a3><b3> </b3>\n'
                '<msg>Defaults from the Wikipedia article</msg>\n')
    resp += ('</result>\n')
    logIt(resp, stamp14, debug)
    print(resp)             # this goes to Apache
    sys.exit()

#
#   boilerplate
#
if __name__ == '__main__':
    main()
#
# this is the last line

file-5: Python3 (routines)

#!/bin/python3.9
# =========================================================
# file   : dh21_module.py
# author : Neil Rieck
# created: 2019-09-07
# purpose: python functions here are called by dh21_main.py
# 20190907 NSR dh20 original effort for python3.6
# 20250207 NSR dh21 changes for python3.9
# =========================================================
#
# ===================================
#   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="dh-log/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
    #
    # even though we have pre-tested this data client-side 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)
    #
    #	<<< computation begins >>>
    #
    #   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>Eavesdropper 'Eve' only sees: p, g, A, B")
    return (1, msg, a2, b2, a3, b3)
# ================================================================

External Links


Back to Home
Neil Rieck
Waterloo, Ontario, Canada.