Python Notes: Diffie-Hellman (web demo)
- The information presented here is intended for educational use.
- 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
- one file of HTML (web page)
- one file of JavaScript routines (only run client-side)
- one optional file which only launches my server-side program
- one file containing my Python3 program (only run server-side)
- 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="https://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.