I wrote an example of KDC Server, using the package cryptography.fernet.
I cannot understand why, randomly, sometimes it runs correctly and sometimes it ends with an exception: cryptography.exceptions.InvalidSignature: Signature did not match digest.
The keys are created once, at the startup of the main. So the issue seems not to be related to creation of different random keys.
Anyone could help me to detect what is wrong?
from cryptography.fernet import Fernet
import uuid
import pickle
def generate_challenge():
return uuid.uuid4().bytes
def serialize(o):
return pickle.dumps(o)
def deserialize(o):
return pickle.loads(o)
class InitiateRequest:
def __init__(self, initiator, responder, challenge):
self.initiator = initiator # IDa
self.responder = responder # IDb
self.challenge = challenge # N1
class InitiateResponse:
def __init__(self, session_key, initiator, responder, challenge):
self.session_key = session_key # Ks
self.initiator = initiator # IDa
self.responder = responder # IDb
self.challenge = challenge # N1
class InvitationForward:
def __init__(self, session_key, initiator):
self.session_key = session_key # Ks
self.initiator = initiator # IDa
class KDCServer:
def __init__(self, generator: Fernet):
self.generator = generator
self.map_keys = {} # user+key pairs
def subscribe(self, id, key):
self.map_keys[id] = key
print("KDCServer: I'm registering key " + str(key) + " for " + id)
def issue_session_key(self, r: InitiateRequest):
session_key = self.generator.generate_key()
response = InitiateResponse(session_key, r.initiator, r.responder, r.challenge)
invitation = InvitationForward(session_key, r.initiator)
print(self.map_keys)
print("KDCServer: I'm using " + str(self.map_keys[r.initiator]) + " as keyA_KDC")
keyA_KDC = Fernet(self.map_keys[r.initiator])
print("KDCServer: I'm using " + str(self.map_keys[r.responder]) + " as keyB_KDC")
keyB_KDC = Fernet(self.map_keys[r.responder])
print("KDCServer: I've just issued a session key for " + r.initiator + " and " + r.responder)
return {
keyA_KDC.encrypt(serialize(response)), # E(Ka,[Ks|IDa|IDb|N1])
keyB_KDC.encrypt(serialize(invitation)) # E(Kb,[Ks|IDa])
}
class User:
def __init__(self, id:str, key:bytes):
self.id = id
self.key = key
self.session_keys = {}
def initiate(self, responder):
challenge = generate_challenge()
print(self.id + ": Let's retrieve a session key to communicate with " + responder)
# store request for matching
self.request = InitiateRequest(self.id, responder, challenge)
return self.request
def match_request(self, check:InitiateResponse)->bool:
return (self.request.challenge == check.challenge) and (self.request.initiator == check.initiator) and (self.request.responder == check.responder)
def accept_response(self, response):
print(self.id + ": I'm decrypting using my key " + str(self.key))
check = deserialize(Fernet(self.key).decrypt(response))
if self.match_request(check):
self.session_keys[check.responder] = check.session_key # save session key Ks
print(self.id + ": I've got the session key to communicate with " + check.responder)
def accept_invitation(self, invitation):
check = deserialize(Fernet(self.key).decrypt(invitation))
print(self.id + ": I've accepted the invitation from " + check.initiator)
self.session_keys[check.initiator] = check.session_key # save session key Ks
def send_message(self, message, receiver):
print(self.id + ": I'm sending this message " + str(message) + " using the session key " + str(self.session_keys[receiver]))
return Fernet(self.session_keys[receiver]).encrypt(message)
def receive_message(self, cyphered, sender):
print(self.id + ": I'm decrypting a message using the session key " + str(self.session_keys[sender]))
message = Fernet(self.session_keys[sender]).decrypt(cyphered)
print(self.id + ": I've received the message " + str(message) + " from " + sender)
def main():
alice_key = Fernet.generate_key()
bob_key = Fernet.generate_key()
alice = User('Alice', alice_key)
bob = User('Bob', bob_key)
server = KDCServer(Fernet)
server.subscribe(alice.id, alice_key)
server.subscribe(bob.id, bob_key)
# Alice sends request to KDC to get a session key
request = alice.initiate(bob.id)
response, invitation = server.issue_session_key(request)
# Alice accepts response from KDC and forwards invitation to Bob
alice.accept_response(response)
# Bob accepts invitation from Alice
bob.accept_invitation(invitation)
cyphered = bob.send_message(b"My secret message", alice.id)
alice.receive_message(cyphered, bob.id)
if __name__ == "__main__":
main()
The problem is caused by the Set returned in issue_session_key()
. A Python Set is unordered, so response
and invitation
will be swapped in main()
50% of the time, causing the error. Use e.g. a Tuple instead of the Set. The Python Tuple is ordered:
...
return (
keyA_KDC.encrypt(serialize(response)), # E(Ka,[Ks|IDa|IDb|N1])
keyB_KDC.encrypt(serialize(invitation)) # E(Kb,[Ks|IDa])
)
...