Compare commits
10
Commits
3d65625fa0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eacc77925a | ||
|
|
0c523ff732 | ||
|
|
ec5b6322df | ||
|
|
0fcc0267dc | ||
|
|
0076fe7803 | ||
|
|
cd3dcaf4a3 | ||
|
|
305e2a2b28 | ||
|
|
2573c12295 | ||
|
|
4d733408eb | ||
|
|
00f1ccc6f6 |
@@ -54,6 +54,8 @@ print("Testing Reporting")
|
|||||||
content = "inappropriate message contents"
|
content = "inappropriate message contents"
|
||||||
reportPT, reportCT = alice.report("Bob", content)
|
reportPT, reportCT = alice.report("Bob", content)
|
||||||
decryptedReport = server.decryptReport(reportCT)
|
decryptedReport = server.decryptReport(reportCT)
|
||||||
|
# print(reportPT)
|
||||||
|
# print(decryptedReport)
|
||||||
if decryptedReport != reportPT:
|
if decryptedReport != reportPT:
|
||||||
error("report did not decrypt properly")
|
error("report did not decrypt properly")
|
||||||
print(reportPT)
|
print(reportPT)
|
||||||
|
|||||||
+191
-9
@@ -1,6 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
import string
|
import hmac
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from cryptography.exceptions import InvalidTag
|
||||||
from cryptography.hazmat.primitives import hashes, serialization
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
from cryptography.hazmat.primitives.asymmetric import ec
|
from cryptography.hazmat.primitives.asymmetric import ec
|
||||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||||
@@ -8,17 +14,72 @@ from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
|||||||
|
|
||||||
CURVE = ec.SECP256R1()
|
CURVE = ec.SECP256R1()
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MessageHeader:
|
||||||
|
dh: object
|
||||||
|
pn: int
|
||||||
|
n: int
|
||||||
|
|
||||||
def GENERATE_DH():
|
def GENERATE_DH():
|
||||||
return ec.generate_private_key(CURVE)
|
return ec.generate_private_key(CURVE)
|
||||||
|
|
||||||
|
def DH(dh_pair, dh_pub):
|
||||||
|
return dh_pair.exchange(ec.ECDH(), dh_pub)
|
||||||
|
|
||||||
|
def KDF_RK(rk, dh_out):
|
||||||
|
output = HKDF(
|
||||||
|
algorithm=hashes.SHA256(),
|
||||||
|
length=64,
|
||||||
|
salt=rk,
|
||||||
|
info=b"",
|
||||||
|
).derive(dh_out)
|
||||||
|
return output[:32], output[32:]
|
||||||
|
|
||||||
|
def KDF_CK(ck):
|
||||||
|
return (
|
||||||
|
hmac.new(ck, b"\x01", "sha256").digest(),
|
||||||
|
hmac.new(ck, b"\x02", "sha256").digest(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def ENCRYPT(mk, plaintext, associated_data):
|
||||||
|
nonce = os.urandom(12)
|
||||||
|
return nonce + AESGCM(mk).encrypt(nonce, plaintext, associated_data)
|
||||||
|
|
||||||
|
def DECRYPT(mk, ciphertext, associated_data):
|
||||||
|
if mk is None or not isinstance(ciphertext, bytes) or len(ciphertext) < 12:
|
||||||
|
return None
|
||||||
|
return AESGCM(mk).decrypt(ciphertext[:12], ciphertext[12:], associated_data)
|
||||||
|
|
||||||
|
def HEADER(dh_pair, pn, n):
|
||||||
|
public_key = dh_pair.public_key()
|
||||||
|
return MessageHeader(public_key, int(pn), int(n))
|
||||||
|
|
||||||
|
def CONCAT(ad, header):
|
||||||
|
header_tup = pickle.dumps(
|
||||||
|
(header.dh.public_bytes(serialization.Encoding.X962,serialization.PublicFormat.UncompressedPoint), header.pn, header.n),
|
||||||
|
)
|
||||||
|
return len(ad).to_bytes(8, "big") + ad + header_tup
|
||||||
|
|
||||||
class MessengerServer:
|
class MessengerServer:
|
||||||
def __init__(self, server_signing_key, server_decryption_key):
|
def __init__(self, server_signing_key, server_decryption_key):
|
||||||
self.server_signing_key = server_signing_key
|
self.server_signing_key = server_signing_key
|
||||||
self.server_decryption_key = server_decryption_key
|
self.server_decryption_key = server_decryption_key
|
||||||
|
|
||||||
def decryptReport(self, ct):
|
def decryptReport(self, ct):
|
||||||
raise Exception("not implemented!")
|
data = pickle.loads(ct)
|
||||||
return
|
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):
|
def signCert(self, cert):
|
||||||
return self.server_signing_key.sign(cert, ec.ECDSA(hashes.SHA256()))
|
return self.server_signing_key.sign(cert, ec.ECDSA(hashes.SHA256()))
|
||||||
@@ -34,6 +95,71 @@ class MessengerClient:
|
|||||||
self.dh = GENERATE_DH()
|
self.dh = GENERATE_DH()
|
||||||
self.certificate = None
|
self.certificate = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def RatchetInitAlice(state, SK, bob_dh_public_key, alice_dh_key_pair=None):
|
||||||
|
state.DHs = alice_dh_key_pair or 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 = {}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
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 = {}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def RatchetSendKey(state):
|
||||||
|
state.CKs, mk = KDF_CK(state.CKs)
|
||||||
|
Ns = state.Ns
|
||||||
|
state.Ns += 1
|
||||||
|
return Ns, mk
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def RatchetEncrypt(state, plaintext, AD):
|
||||||
|
Ns, mk = MessengerClient.RatchetSendKey(state)
|
||||||
|
header = HEADER(state.DHs, state.PN, Ns)
|
||||||
|
return header, ENCRYPT(mk, plaintext, CONCAT(AD, header))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def RatchetReceiveKey(state, header):
|
||||||
|
if not isinstance(header, MessageHeader) or header.dh is None:
|
||||||
|
return None
|
||||||
|
if header.pn < 0 or header.n < 0:
|
||||||
|
return None
|
||||||
|
if state.DHr is None or header.dh.public_bytes(serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint) != state.DHr.public_bytes(serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint):
|
||||||
|
MessengerClient.DHRatchet(state, header)
|
||||||
|
if header.n != state.Nr:
|
||||||
|
return None
|
||||||
|
state.CKr, mk = KDF_CK(state.CKr)
|
||||||
|
state.Nr += 1
|
||||||
|
return mk
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def RatchetDecrypt(state, header, ciphertext, AD):
|
||||||
|
mk = MessengerClient.RatchetReceiveKey(state, header)
|
||||||
|
return DECRYPT(mk, ciphertext, CONCAT(AD, header))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
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):
|
def generateCertificate(self):
|
||||||
self.certificate = pickle.dumps((self.name, self.dh.public_key().public_bytes(serialization.Encoding.X962,
|
self.certificate = pickle.dumps((self.name, self.dh.public_key().public_bytes(serialization.Encoding.X962,
|
||||||
serialization.PublicFormat.UncompressedPoint)))
|
serialization.PublicFormat.UncompressedPoint)))
|
||||||
@@ -46,13 +172,69 @@ class MessengerClient:
|
|||||||
self.certs[name] = public_key
|
self.certs[name] = public_key
|
||||||
|
|
||||||
def sendMessage(self, name, message):
|
def sendMessage(self, name, message):
|
||||||
raise Exception("not implemented!")
|
if name not in self.certs:
|
||||||
return
|
return None
|
||||||
|
state = self.conns.get(name)
|
||||||
|
if state is None:
|
||||||
|
state = SimpleNamespace()
|
||||||
|
initial_secret = DH(self.dh, self.certs[name])
|
||||||
|
self.RatchetInitAlice(
|
||||||
|
state, initial_secret, self.certs[name], self.dh
|
||||||
|
)
|
||||||
|
self.conns[name] = state
|
||||||
|
plaintext = message.encode("utf-8")
|
||||||
|
return self.RatchetEncrypt(state, plaintext, b"")
|
||||||
|
|
||||||
def receiveMessage(self, name, header, ciphertext):
|
def receiveMessage(self, name, header, ciphertext):
|
||||||
raise Exception("not implemented!")
|
if name not in self.certs:
|
||||||
return
|
return None
|
||||||
|
state = self.conns.get(name)
|
||||||
|
if state is None:
|
||||||
|
state = SimpleNamespace()
|
||||||
|
initial_secret = DH(self.dh, self.certs[name])
|
||||||
|
self.RatchetInitBob(state, initial_secret, self.dh)
|
||||||
|
temp_state = copy.copy(state) # fixes the issue with bad ciphertexts corrupting the state
|
||||||
|
try:
|
||||||
|
plaintext = self.RatchetDecrypt(temp_state, header, ciphertext, b"")
|
||||||
|
message = plaintext.decode("utf-8")
|
||||||
|
except (
|
||||||
|
InvalidTag,
|
||||||
|
ValueError,
|
||||||
|
TypeError,
|
||||||
|
AttributeError,
|
||||||
|
IndexError,
|
||||||
|
UnicodeDecodeError,
|
||||||
|
pickle.UnpicklingError,
|
||||||
|
EOFError,
|
||||||
|
KeyError,
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
self.conns[name] = temp_state
|
||||||
|
return message
|
||||||
|
|
||||||
|
|
||||||
def report(self, name, message):
|
def report(self, name, message):
|
||||||
raise Exception("not implemented!")
|
report = (name, message)
|
||||||
return
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user