Cryptography
Cryptography is the practice and study of techniques for securing communication and information against unauthorized access, modification, or forgery. It is used to protect sensitive data by transforming it into a format that is difficult for unintended recipients to understand or manipulate. This transformation often relies on mathematical algorithms and cryptographic keys.
Hash functions are algorithms that take an input and produce a fixed-size string of characters, which is unique to the input data. In Figure 1 you can encrypt a message using a hash-function. The `shared secret’ is a number that is used to encrypt the message.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 200
from shiny import App, Inputs, Outputs, Session, render, ui
from shiny import reactive
import numpy as np
from pathlib import Path
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import sympy as sp
import pandas as pd
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dh
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
app_ui = ui.page_fluid(
ui.layout_sidebar(
ui.panel_sidebar(
ui.input_slider(id="shared_secret",label="shared secret",min=10,max=3000,value=23,step=1),
ui.input_text(id='text',label="Message to encrypt",value="Yarrrr"),
),
ui.panel_main(ui.output_table("result"),),
),
)
def server(input, output, session):
def encrypt_message(key, plaintext):
iv=5
iv=iv.to_bytes(16, 'big')
cipher = Cipher(algorithms.AES(key), modes.CFB(iv))
encryptor = cipher.encryptor()
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
return iv,ciphertext
@render.table
def result():
# list of strings
shared_secret=int(input.shared_secret())
text=input.text()
# Step 1: Generate Diffie-Hellman Parameters and Keys
#shared_secret=int(np.mod(g**(a*b),p))
shared_secret_bytes=shared_secret.to_bytes(16,'big')
#shared_secret=g**(a*b)
derived_key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=None,
info=b"diffie-hellman-key-exchange",
).derive(shared_secret_bytes)
# Step 4: Encrypt a Message Using AES
message = text.encode(encoding="utf-8")
iv, ciphertext = encrypt_message(derived_key, message)
# Decrypt the message
data_dict = {
'Encrypted Message':[ciphertext.hex()],
'Encrypted Message':[ciphertext.hex()],
}
# Create a DataFrame
df = pd.DataFrame(data_dict)
# Calling DataFrame constructor on list
return df
app = App(app_ui, server)
Given knowledge of the shared secret an encrypted message can be decrypted. If you paste the encrypted message from Figure 1 into Figure 2 you will recover the original message, assuming that you use the same shared secret.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 200
from shiny import App, Inputs, Outputs, Session, render, ui
from shiny import reactive
import numpy as np
from pathlib import Path
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import sympy as sp
import pandas as pd
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dh
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import base64
app_ui = ui.page_fluid(
ui.layout_sidebar(
ui.panel_sidebar(
ui.input_slider(id="shared_secret",label="Shared secret",min=10,max=3000,value=31,step=1),
ui.input_text(id='text',label="Message to decrypt",value="8968d20bbc5b"),
),
ui.panel_main(ui.output_table("result"),),
),
)
def server(input, output, session):
def decrypt_message(key, iv, ciphertext):
cipher = Cipher(algorithms.AES(key), modes.CFB(iv))
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
return plaintext
@render.table
def result():
# list of strings
shared_secret=int(input.shared_secret())
encrypted_text=str(input.text())
encrypted_text=bytes.fromhex(encrypted_text)
#text=text.encode(encoding="utf-8")
#encrypted_text = encrypted_text.encode('ISO-8859-1')
shared_secret_bytes=shared_secret.to_bytes(16,'big')
derived_key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=None,
info=b"diffie-hellman-key-exchange",
).derive(shared_secret_bytes)
iv=5
iv=iv.to_bytes(16, 'big')
decrypted_message = decrypt_message(derived_key, iv, encrypted_text)
data_dict = {
'Decrypted Message':[decrypted_message]
}
# Create a DataFrame
df = pd.DataFrame(data_dict)
# Calling DataFrame constructor on list
return df
app = App(app_ui, server)
For safe encryption using hash functions, two parties must safely share a secret in order that they can encrypt/decrypt messages.
The discrete logarithm problem
For security of the Diffie Hellman algorithm it is required that the shared secret cannot be easily deduced from the publicly available information. If, for example, an outsider were to identify Alice’s private key, \(x\), then they could easily compute the shared secret
\[ Y^x \pmod p \] and therefore decrypt the message.
Consider the publicly available information: \(p\), \(g\) and the public key \(X\) and \(Y\). To obtain the shared secret a hacker could solve the following problem
\[ g^x \pmod p = X, \]
where \(x\) is Alice’s private key. This is known as the discrete logarithm problem . In Figure 4 you can explore how an observer could identify Alice’s private key, \(x\) (where the marker sits on the horizontal line).
It can be shown that when \(p\) is chosen appropriately that there are not efficient methods to solve this problem. Hence the Diffie-Hellman is secure given appropriate choices for \(p\) and \(g\).
#| standalone: true
#| components: [viewer]
#| viewerHeight: 500
from shiny import App, Inputs, Outputs, Session, render, ui
from shiny import reactive
import numpy as np
from pathlib import Path
import matplotlib.pyplot as plt
from scipy.integrate import odeint
app_ui = ui.page_fluid(
ui.layout_sidebar(
ui.panel_sidebar(
ui.input_slider(id="p",label="p (prime number)",min=5,max=300,value=23,step=1),
ui.input_slider(id="g",label="g (generator)",min=0,max=30,value=5,step=1),
ui.input_slider(id="s",label="Public key",min=1,max=100,value=5,step=1),
),
ui.panel_main(ui.output_plot("plot"),),
),
)
def server(input, output, session):
@render.plot
def plot():
fig, ax = plt.subplots()
#ax.set_ylim([-2, 2])
# Filter fata
p=int(input.p())
g=int(input.g())
s=int(input.s())
ax.set_xlabel('$x$')
ax.set_ylabel('$f$')
a_vec=np.linspace(0,p-1,p,dtype=int)
function_mod=(g**a_vec)%p
ax.plot(a_vec,function_mod,'x',a_vec,np.ones_like(a_vec)*s,'r--')
fig.tight_layout()
plt.grid()
plt.show()
app = App(app_ui, server)
Encryption with Diffie-Helmann
In Figure 5 you can explore the encryption of a message using the Diffie-Helmann algorithm.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 500
from shiny import App, Inputs, Outputs, Session, render, ui
from shiny import reactive
import numpy as np
from pathlib import Path
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import sympy as sp
import pandas as pd
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dh
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
app_ui = ui.page_fluid(
ui.layout_sidebar(
ui.panel_sidebar(
ui.input_slider(id="p",label="p (prime number)",min=10,max=3000,value=23,step=1),
ui.input_slider(id="g",label="g (generator)",min=0,max=15.0,value=5,step=1),
ui.input_slider(id="a",label="Private key Bob",min=1,max=10,value=2,step=1),
ui.input_slider(id="b",label="Private key Alice",min=1,max=10,value=3,step=1),
ui.input_text(id='text',label="Message to encrypt",value="sin(x)"),
),
ui.panel_main(ui.output_table("result"),),
),
)
def server(input, output, session):
def encrypt_message(key, plaintext):
iv=5
iv=iv.to_bytes(16, 'big')
cipher = Cipher(algorithms.AES(key), modes.CFB(iv))
encryptor = cipher.encryptor()
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
return iv,ciphertext
def decrypt_message(key, iv, ciphertext):
cipher = Cipher(algorithms.AES(key), modes.CFB(iv))
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
return plaintext
@render.table
def result():
# list of strings
p=int(input.p())
g=int(input.g())
a=int(input.a())
text=input.text()
b=int(input.b())
# Step 1: Generate Diffie-Hellman Parameters and Keys
parameters = dh.generate_parameters(generator=2, key_size=512)
# Generate private/public key pairs for two parties
private_key_a = parameters.generate_private_key()
private_key_b = parameters.generate_private_key()
# Generate public keys
public_key_a = private_key_a.public_key()
public_key_b = private_key_b.public_key()
# Step 2: Derive Shared Secret
shared_key_a = private_key_a.exchange(public_key_b)
shared_key_b = private_key_b.exchange(public_key_a)
# Validate shared keys are identical
assert shared_key_a == shared_key_b, "Shared keys are not equal!"
shared_secret=int(np.mod(g**(a*b),p))
shared_secret_bytes=shared_secret.to_bytes(16,'big')
#shared_secret=g**(a*b)
derived_key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=None,
info=b"diffie-hellman-key-exchange",
).derive(shared_secret_bytes)
# Step 4: Encrypt a Message Using AES
message = text.encode(encoding="utf-8")
iv, ciphertext = encrypt_message(derived_key, message)
print("Ciphertext:", ciphertext.hex())
# Decrypt the message
decrypted_message = decrypt_message(derived_key, iv, ciphertext)
data_dict = {
'p': [p],
'g': [g],
'Message': [text],
'Shared secret Alice': [shared_secret],
'Encrypted Message':[ciphertext.hex()],
}
# Create a DataFrame
df = pd.DataFrame(data_dict)
# Calling DataFrame constructor on list
return df
app = App(app_ui, server)
Decrypting a message
Suppose that we want to decrypt the message
f04ed308495b04694943d414e0444151fe141d05581b3eb2a4f168
Suppose that we know that it has been encrypted using Diffie Helman algorithm. The encryptors have been a little slap dash and used the following values for publicly available information:
p=31 q=23
Can you use Figure 6 to unencrypt the message using the available information?
#| standalone: true
#| components: [viewer]
#| viewerHeight: 500
from shiny import App, Inputs, Outputs, Session, render, ui
from shiny import reactive
import numpy as np
from pathlib import Path
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import sympy as sp
import pandas as pd
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dh
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import base64
app_ui = ui.page_fluid(
ui.layout_sidebar(
ui.panel_sidebar(
ui.input_slider(id="p",label="p",min=10,max=3000,value=23,step=1),
ui.input_slider(id="g",label="g (generator)",min=0,max=33,value=5,step=1),
ui.input_slider(id="a",label="Private key Bob",min=1,max=10,value=2,step=1),
ui.input_slider(id="b",label="Private key Sue",min=1,max=10,value=3,step=1),
ui.input_text(id='text',label="Message to decrypt",value="8968d20bbc5b"),
),
ui.panel_main(ui.output_table("result"),),
),
)
def server(input, output, session):
def decrypt_message(key, iv, ciphertext):
cipher = Cipher(algorithms.AES(key), modes.CFB(iv))
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
return plaintext
@render.table
def result():
# list of strings
p=int(input.p())
g=int(input.g())
a=int(input.a())
encrypted_text=str(input.text())
b=int(input.b())
encrypted_text=bytes.fromhex(encrypted_text)
#text=text.encode(encoding="utf-8")
#encrypted_text = encrypted_text.encode('ISO-8859-1')
shared_secret=int(np.mod(g**(a*b),p))
shared_secret_bytes=shared_secret.to_bytes(16,'big')
derived_key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=None,
info=b"diffie-hellman-key-exchange",
).derive(shared_secret_bytes)
#bytes_data = bytes.fromhex(text)
# Encode the bytes into a Base64 string
#encrypted_text = base64.b64encode(text.encode())#.decode('utf-8')
iv=5
iv=iv.to_bytes(16, 'big')
decrypted_message = decrypt_message(derived_key, iv, encrypted_text)
data_dict = {
'Decrypted Message':[decrypted_message]
}
# Create a DataFrame
df = pd.DataFrame(data_dict)
# Calling DataFrame constructor on list
return df
app = App(app_ui, server)
At Dundee, core concepts from integration are introduced in the modules Maths 1A and Maths 1B and developed further in the modules Maths 2A and Maths 2B.
In the modules Introduction to Programming and Computer Algebra and Dynamical systems you would be introduced to techniques that enable you to perform numerical integration.
You can find out more about these modules here.