Files
comp537-messenger/messenger.py
T

173 lines
5.8 KiB
Python

import os
import pickle
import string
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
CURVE = ec.SECP256R1()
def GENERATE_DH():
return ec.generate_private_key(CURVE)
def DH(dh_pair, dh_pub):
return dh_pair.exchange(ec.ECDH(), dh_pub)
class MessengerServer:
def __init__(self, server_signing_key, server_decryption_key):
self.server_signing_key = server_signing_key
self.server_decryption_key = server_decryption_key
def decryptReport(self, ct):
data = pickle.loads(ct)
key = ec.EllipticCurvePublicKey.from_encoded_point(
CURVE, data["bytes"]
)
shared = DH(self.server_decryption_key, key)
derived_key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=None,
info=b""
).derive(shared)
aesgcm = AESGCM(derived_key)
plaintext = aesgcm.decrypt(data["nonce"], data["ciphertext"], data["bytes"])
return plaintext.decode("latin1")
def signCert(self, cert):
return self.server_signing_key.sign(cert, ec.ECDSA(hashes.SHA256()))
class MessengerClient:
def __init__(self, name, server_signing_pk, server_encryption_pk):
self.name = name
self.server_signing_pk = server_signing_pk
self.server_encryption_pk = server_encryption_pk
self.conns = {}
self.certs = {}
self.dh = GENERATE_DH()
self.certificate = None
def RatchetInitAlice(state, SK, bob_dh_public_key):
state.DHs = GENERATE_DH()
state.DHr = bob_dh_public_key
state.RK, state.CKs = KDF_RK(SK, DH(state.DHs, state.DHr))
state.CKr = None
state.Ns = 0
state.Nr = 0
state.PN = 0
state.MKSKIPPED = {}
def RatchetInitBob(state, SK, bob_dh_key_pair):
state.DHs = bob_dh_key_pair
state.DHr = None
state.RK = SK
state.CKs = None
state.CKr = None
state.Ns = 0
state.Nr = 0
state.PN = 0
state.MKSKIPPED = {}
def RatchetSendKey(state):
state.CKs, mk = KDF_CK(state.CKs)
Ns = state.Ns
state.Ns += 1
return Ns, mk
def RatchetEncrypt(state, plaintext, AD):
Ns, mk = RatchetSendKey(state)
header = HEADER(state.DHs, state.PN, Ns)
return header, ENCRYPT(mk, plaintext, CONCAT(AD, header))
def RatchetReceiveKey(state, header):
mk = TrySkippedMessageKeys(state, header)
if mk != None:
return mk
if header.dh != state.DHr:
SkipMessageKeys(state, header.pn)
DHRatchet(state, header)
SkipMessageKeys(state, header.n)
state.CKr, mk = KDF_CK(state.CKr)
state.Nr += 1
return mk
def RatchetDecrypt(state, header, ciphertext, AD):
mk = RatchetReceiveKey(state, header)
return DECRYPT(mk, ciphertext, CONCAT(AD, header))
def TrySkippedMessageKeys(state, header):
if (header.dh, header.n) in state.MKSKIPPED:
mk = state.MKSKIPPED[header.dh, header.n]
del state.MKSKIPPED[header.dh, header.n]
return mk
else:
return None
def SkipMessageKeys(state, until):
if state.Nr + MAX_SKIP < until:
raise Error()
if state.CKr != None:
while state.Nr < until:
state.CKr, mk = KDF_CK(state.CKr)
state.MKSKIPPED[state.DHr, state.Nr] = mk
state.Nr += 1
def DHRatchet(state, header):
state.PN = state.Ns
state.Ns = 0
state.Nr = 0
state.DHr = header.dh
state.RK, state.CKr = KDF_RK(state.RK, DH(state.DHs, state.DHr))
state.DHs = GENERATE_DH()
state.RK, state.CKs = KDF_RK(state.RK, DH(state.DHs, state.DHr))
def generateCertificate(self):
self.certificate = pickle.dumps((self.name, self.dh.public_key().public_bytes(serialization.Encoding.X962,
serialization.PublicFormat.UncompressedPoint)))
return self.certificate
def receiveCertificate(self, certificate, signature):
self.server_signing_pk.verify(signature, certificate, ec.ECDSA(hashes.SHA256()))
name, public_bytes = pickle.loads(certificate)
public_key = ec.EllipticCurvePublicKey.from_encoded_point(CURVE, public_bytes)
self.certs[name] = public_key
def sendMessage(self, name, message):
raise Exception("not implemented!")
return
def receiveMessage(self, name, header, ciphertext):
raise Exception("not implemented!")
return
# this is technically hybrid encryption instead of just el gamal but I blame the instructions instead of myself
# maybe i'll change this if I want to use a late day
def report(self, name, message):
report = (name, message)
plaintext = pickle.dumps(report)
temp_key = GENERATE_DH()
temp_bytes = temp_key.public_key().public_bytes(
serialization.Encoding.X962,
serialization.PublicFormat.UncompressedPoint,
)
shared_secret = DH(temp_key, self.server_encryption_pk)
hkdf = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=None,
info=b""
)
key = hkdf.derive(shared_secret)
nonce = os.urandom(12)
aesgcm = AESGCM(key)
ciphertext = aesgcm.encrypt(nonce, plaintext, temp_bytes) # not sure if passing in temp_bytes is correct, but it seems like it should be
data = {
"bytes": temp_bytes,
"nonce": nonce,
"ciphertext": ciphertext
}
return plaintext.decode("latin1"), pickle.dumps(data)