├── Client ├── CA.cert ├── SSLUtil.py ├── chat_client.py ├── createCert.py ├── dh.py ├── ecdh │ └── private.pem ├── keys │ ├── client.pkey │ └── server.pkey ├── messencrypt.py ├── mk_cert_files.py └── sign.py ├── README.md └── Server ├── CA.cert ├── CA.pkey ├── chat_server.py ├── createCA.py ├── createCert.py ├── dh.py ├── keys ├── client.pkey └── server.pkey ├── messencrypt.py ├── mk_cert_files.py └── sign.py /Client/CA.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICtDCCAZwCAQAwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UEAwwVQ2VydGlmaWNh 3 | dGUgQXV0aG9yaXR5MB4XDTE3MTEyMDA4Mjc1NVoXDTIyMTExOTA4Mjc1NVowIDEe 4 | MBwGA1UEAwwVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF 5 | AAOCAQ8AMIIBCgKCAQEA4ycfzHNxn9Knmqz1FhbzW50o0v+HXscgNzUfboj95wBq 6 | Zf2OMo7R1Gpp+PKm5ONFnFfvyGQQRmukgT366WR8ZbH8g8hRqsPutE0LZtP0+bOm 7 | 7+zz+XbOXRRM3yNRYWymDaFUtn2INqTANMrVPtA0hThdmnuBw19TUwBJcpbM+9FF 8 | zarGuTIRYRt7AQy2nbOoC2LzW726cgApT68TiF/rKGyPrBUeTZyc/IsXFEWfSlyl 9 | lqeafyouRxb+hbKNmLsPtoeMOpdBEqXUfk2REH2coKMacuQQnekx5IXhZuQxSwSn 10 | Ox2lZWoErlqfWmho9GAwuJd2IldptrVdY63Ea0ezwQIDAQABMA0GCSqGSIb3DQEB 11 | CwUAA4IBAQCS4rAax3lgVzaWNJPuMXVfw3Jvjh2IDJstGv966+oLWec8e//tV3Ki 12 | AmxZwkCm6Vp9jE/DbwbwZedbbSiIlzfYK9cw8RKzYTdjkibYZfW8ifb+z+NZ07Ol 13 | TiFKG9k6du/wTmID//gD0w3we19bomfaa09CJ4juTTvu7DqqtgM7FXWvzuR5XfPB 14 | FfJphd3SUE/Wc0dKeKxwrg2xS9mDuEnU6VscKMdIYssVDk8EYrjfUJnFOV89FwxX 15 | FbcatR5BZz4LYelYXfDFVv1OrLP4me09Ce/2kDGcAHIvZpuapjqFBoRUBaStd629 16 | k6Ff3kckoJ/plkAknc5cVI+iFOwN4Fvt 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /Client/SSLUtil.py: -------------------------------------------------------------------------------- 1 | import sys, socket, select, os 2 | from mk_cert_files import * 3 | from OpenSSL import SSL 4 | 5 | #Creates a certificate request and sends it as a PEM encoded string to the CA/server 6 | def getCertificate(): 7 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 8 | s.settimeout(2) 9 | #connect to server 10 | try: 11 | s.connect(('localhost', 9998)) 12 | except : 13 | print "Unable to connect. Either the server is down or the certificate can not be verified." 14 | sys.exit() 15 | 16 | #This is really important that this is the first thing happening BEFORE any other exchange 17 | #Create certificate request 18 | req = createRequest('client') 19 | s.send(crypto.dump_certificate_request(crypto.FILETYPE_ASN1, req)) 20 | 21 | cert_to_be_parsed = s.recv(4096) 22 | cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_to_be_parsed) 23 | open('client.cert', 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) 24 | 25 | #Sets up the SSL socket with the client keys. 26 | def initSSLClient(port): 27 | ctx = SSL.Context(SSL.TLSv1_2_METHOD) 28 | ctx.set_verify(SSL.VERIFY_PEER, verify_cb) 29 | ctx.use_privatekey_file (os.path.join('keys', 'client.pkey')) 30 | ctx.use_certificate_file(os.path.join('', 'client.cert')) 31 | ctx.load_verify_locations(os.path.join('', 'CA.cert')) 32 | #Connect the SSL sock 33 | s = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM)) 34 | s.connect(('localhost', port)) 35 | return s 36 | 37 | #Don't actually know how to do this callback, so for a production update this needs to be updated... 38 | def verify_cb(conn, cert, errnum, depth, ok): 39 | return ok 40 | -------------------------------------------------------------------------------- /Client/chat_client.py: -------------------------------------------------------------------------------- 1 | # chat_client.py 2 | import sys, socket, select, os, base64, getpass, time 3 | import simplejson as json 4 | import ssl 5 | from cryptography.hazmat.primitives import serialization 6 | from cryptography.hazmat.backends import default_backend 7 | from mk_cert_files import * 8 | from OpenSSL import SSL 9 | 10 | #Own modules 11 | from dh import * 12 | from messencrypt import * 13 | from sign import * 14 | from SSLUtil import * 15 | 16 | def chat_client(port, password): 17 | #As this is just a basic program to show encryption, signatures and 18 | #elliptic curve diffie-hellman some production features will obvious not exist 19 | #as for example an login/register which would solve some really important issues 20 | #with this program. As a public key not sent from the client before signing for example. 21 | 22 | #Create a password for the signature private key 23 | if password == None: 24 | while 1: 25 | password = getpass.getpass("Enter a password for your private key>") 26 | password_check = getpass.getpass("Enter it again>") 27 | if password == password_check: 28 | break 29 | else: 30 | print "Passwords does not match." 31 | 32 | host = '83.253.117.77' 33 | #Get CA signed SSL certificate from server 34 | getCertificate() 35 | s = initSSLClient(9009) 36 | #Selecting the port for the chatroom to join. 37 | roomport = roomHandler(s) 38 | #SSL socket with the new port. 39 | getCertificate() 40 | s = initSSLClient(roomport) 41 | username = raw_input("Enter your chatalias> ") 42 | #Create the signature 43 | signMessage(s, username, password) 44 | #Generate key for encryption. If you are not "Hub", this key will be thrown. 45 | fernet_key = generateFernetKey() 46 | #Gets the fernet key for the end-to-end encryption 47 | fernet = diffieHellmanExchange(s,fernet_key) 48 | #is Hub, use the generated key. 49 | if fernet == 0: 50 | fernet = fernet_key 51 | fernet = base64.urlsafe_b64encode(fernet) 52 | 53 | #Now you can chat 54 | print 'Connected to remote host. Press :q for exiting the chat.' 55 | sys.stdout.write('['+username+'] '); sys.stdout.flush() 56 | 57 | while 1: 58 | socket_list = [sys.stdin, s] 59 | #Get the list sockets which are readable 60 | read_sockets, write_sockets, error_sockets = select.select(socket_list , [], []) 61 | for sock in read_sockets: 62 | if sock == s: 63 | unparsed = sock.recv(4096) 64 | try: 65 | data = json.loads(unparsed) 66 | if not data : 67 | print '\nDisconnected from chat server' 68 | sys.exit() 69 | else : 70 | if data["dh"] == "h": 71 | sendFernet(s, data, base64.urlsafe_b64decode(fernet)) 72 | #server message 73 | if data["dh"] == "server": 74 | sys.stdout.write('\n' + data["message"]) 75 | sys.stdout.write('['+username+'] ') 76 | sys.stdout.flush() 77 | #You are not hub acting in diffie-hellman exchange 78 | #decrypt the message 79 | else: 80 | ciphertext = data["message"].encode("utf8") 81 | plaintext = decrypt(ciphertext, fernet) 82 | sys.stdout.write('\n'+'['+data["dh"]+'] ' + plaintext) 83 | sys.stdout.write('\n'+'['+username+'] ') 84 | sys.stdout.flush() 85 | except: 86 | continue 87 | else: 88 | try: 89 | msg = raw_input() 90 | #Handling exit of chat... 91 | except KeyboardInterrupt: 92 | sys.exit() 93 | if msg.lower() == ':q': 94 | s.close() 95 | return chat_client(9009,password) 96 | 97 | #Encypt the message with the fernet key 98 | ciphertext = encrypt(msg, fernet) 99 | s.send(ciphertext) 100 | sys.stdout.write('['+username+'] ') 101 | sys.stdout.flush() 102 | 103 | def roomHandler(s): 104 | port = 0 105 | name="" 106 | operation="" 107 | done = False 108 | try: 109 | while done == False: 110 | print "Define action: \n1. Create a room. \n2. Join a room. \n3. Get a list of all rooms.\n4. :q to quit." 111 | action = raw_input(">") 112 | if action.lower() == ":q": 113 | sys.exit() 114 | if action == "1": 115 | while True: 116 | operation = "create" 117 | name = raw_input("Name of the room>") 118 | if name.lower() == ':q': 119 | return roomHandler(s) 120 | 121 | #Check if that room already exists or the queue is full 122 | jsonstr = {"name":name, "operation":operation} 123 | s.send(json.dumps(jsonstr)) 124 | condition = s.recv(4096) 125 | 126 | if condition == "exist": 127 | print "Chatroom already exists" 128 | elif condition == "full": 129 | print "Chatroom capacity met." 130 | else: 131 | print "Room created successfully" 132 | jsonstr = {"name":name, "operation":"join"} 133 | s.send(json.dumps(jsonstr)) 134 | port = s.recv(4096) 135 | break 136 | done = True 137 | 138 | if action == "2": 139 | while 1: 140 | operation = "join" 141 | name = raw_input("Name of the room>") 142 | if name.lower() == ':q': 143 | return roomHandler(s) 144 | 145 | jsonstr = {"name":name, "operation":operation} 146 | s.send(json.dumps(jsonstr)) 147 | port = s.recv(4096) 148 | if port == "0": 149 | print "Room does not exist..." 150 | else: 151 | break 152 | done = True 153 | 154 | if action == "3": 155 | operation = "list" 156 | jsonstr = {"name": name, "operation": operation} 157 | s.send(json.dumps(jsonstr)) 158 | roomlist = s.recv(4096) 159 | print "#################ROOMS##################" 160 | for room in roomlist.split(): 161 | print "----------------------------------------" 162 | print room 163 | print "----------------------------------------" 164 | print "########################################" 165 | except KeyboardInterrupt: 166 | sys.exit() 167 | 168 | return int(port) 169 | 170 | if __name__ == "__main__": 171 | sys.exit(chat_client(9009, None)) 172 | -------------------------------------------------------------------------------- /Client/createCert.py: -------------------------------------------------------------------------------- 1 | from OpenSSL import crypto 2 | 3 | 4 | #This code is taken from pyOpenSSL examples. 5 | TYPE_RSA = crypto.TYPE_RSA 6 | TYPE_DSA = crypto.TYPE_DSA 7 | 8 | def createKeyPair(type, bits): 9 | """ 10 | Create a public/private key pair. 11 | Arguments: type - Key type, must be one of TYPE_RSA and TYPE_DSA 12 | bits - Number of bits to use in the key 13 | Returns: The public/private key pair in a PKey object 14 | """ 15 | pkey = crypto.PKey() 16 | pkey.generate_key(type, bits) 17 | return pkey 18 | 19 | def createCertRequest(pkey, digest="sha256", **name): 20 | """ 21 | Create a certificate request. 22 | Arguments: pkey - The key to associate with the request 23 | digest - Digestion method to use for signing, default is md5 24 | **name - The name of the subject of the request, possible 25 | arguments are: 26 | C - Country name 27 | ST - State or province name 28 | L - Locality name 29 | O - Organization name 30 | OU - Organizational unit name 31 | CN - Common name 32 | emailAddress - E-mail address 33 | Returns: The certificate request in an X509Req object 34 | """ 35 | req = crypto.X509Req() 36 | subj = req.get_subject() 37 | 38 | for (key,value) in name.items(): 39 | setattr(subj, key, value) 40 | 41 | req.set_pubkey(pkey) 42 | req.sign(pkey, digest) 43 | return req 44 | 45 | def createCertificate(req, (issuerCert, issuerKey), serial, (notBefore, notAfter), digest="SHA256"): 46 | """ 47 | Generate a certificate given a certificate request. 48 | Arguments: req - Certificate reqeust to use 49 | issuerCert - The certificate of the issuer 50 | issuerKey - The private key of the issuer 51 | serial - Serial number for the certificate 52 | notBefore - Timestamp (relative to now) when the certificate 53 | starts being valid 54 | notAfter - Timestamp (relative to now) when the certificate 55 | stops being valid 56 | digest - Digest method to use for signing, default is md5 57 | Returns: The signed certificate in an X509 object 58 | """ 59 | cert = crypto.X509() 60 | cert.set_serial_number(serial) 61 | cert.gmtime_adj_notBefore(notBefore) 62 | cert.gmtime_adj_notAfter(notAfter) 63 | cert.set_issuer(issuerCert.get_subject()) 64 | cert.set_subject(req.get_subject()) 65 | cert.set_pubkey(req.get_pubkey()) 66 | cert.sign(issuerKey, digest) 67 | return cert 68 | -------------------------------------------------------------------------------- /Client/dh.py: -------------------------------------------------------------------------------- 1 | import sys, os, base64 2 | import simplejson as json 3 | from cryptography.hazmat.backends import default_backend 4 | from cryptography.hazmat.primitives.asymmetric import ec 5 | from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC 6 | from cryptography.hazmat.primitives import hashes 7 | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 8 | from cryptography.hazmat.primitives import serialization 9 | from cryptography.hazmat.primitives.serialization import load_pem_public_key 10 | from messencrypt import encrypt 11 | from messencrypt import generateFernetKey 12 | 13 | #Creates the private/public key pair for the server and gets the shared key from the client public key. 14 | #Puts the shared key through a KDF for AES-128. 15 | def hubExchange(client_public, fernet_key): 16 | #Generate a private key for use in the exchange 17 | hub_private = ec.generate_private_key( 18 | ec.SECP384R1(), default_backend() 19 | ) 20 | hub_public = hub_private.public_key() 21 | hub_shared = hub_private.exchange(ec.ECDH(), client_public) 22 | salt = os.urandom(16) 23 | kdf = PBKDF2HMAC( 24 | algorithm=hashes.SHA256(), 25 | length=16, 26 | salt=salt, 27 | iterations=100000, 28 | backend=default_backend() 29 | ) 30 | 31 | hub_shared = kdf.derive(bytes(hub_shared)) 32 | 33 | serialized_hub_public = hub_public.public_bytes( 34 | encoding=serialization.Encoding.PEM, 35 | format=serialization.PublicFormat.SubjectPublicKeyInfo 36 | ) 37 | 38 | return sendKeyToClient(hub_shared, fernet_key, serialized_hub_public, salt) 39 | 40 | 41 | #Encrypts the fernet key with the shared key 42 | def sendKeyToClient(shared_key, fernet_key, serialized_hub_public, salt): 43 | iv = os.urandom(16) 44 | cipher = Cipher(algorithms.AES(shared_key), modes.CBC(iv), backend=default_backend()) 45 | encryptor = cipher.encryptor() 46 | ct = encryptor.update(bytes(fernet_key)) + encryptor.finalize() 47 | #Send public, CT, IV and salt 48 | json_string = {"hub_public": serialized_hub_public, "ciphertext": base64.b64encode(ct), "iv": base64.b64encode(iv), "salt": base64.b64encode(salt)} 49 | return json.dumps(json_string) 50 | 51 | #Decrypts the fernet key with the shared key 52 | def getKeyFromHub(client_private, public_cipher_iv): 53 | parsed = json.loads(public_cipher_iv) 54 | 55 | hub_serialized = parsed['hub_public'].encode('ascii') 56 | ct = base64.b64decode(parsed['ciphertext']) 57 | iv = base64.b64decode(parsed['iv']) 58 | salt = base64.b64decode(parsed['salt']) 59 | 60 | hub_public = load_pem_public_key(hub_serialized, backend=default_backend()) 61 | shared_key = client_private.exchange(ec.ECDH(), hub_public) 62 | #The shared key needs to be 16 bytes (AES-128). So it's put through a KDF with the same salt as the server 63 | kdf = PBKDF2HMAC( 64 | algorithm=hashes.SHA256(), 65 | length=16, 66 | salt=salt, 67 | iterations=100000, 68 | backend=default_backend() 69 | ) 70 | 71 | shared_key = kdf.derive(bytes(shared_key)) 72 | cipher = Cipher(algorithms.AES(shared_key), modes.CBC(iv), backend=default_backend()) 73 | decryptor = cipher.decryptor() 74 | fernet = decryptor.update(ct) + decryptor.finalize() 75 | return fernet 76 | 77 | #Create private/public elliptic curve key pair. 78 | def clientCreateKeys(): 79 | private_key = ec.generate_private_key(ec.SECP384R1(), default_backend()) 80 | serialized_private = private_key.private_bytes( 81 | encoding=serialization.Encoding.PEM, 82 | format=serialization.PrivateFormat.PKCS8, 83 | encryption_algorithm=serialization.BestAvailableEncryption(b'test') 84 | ) 85 | file = open("ecdh/private.pem", "w") 86 | file.write(serialized_private) 87 | file.close() 88 | public_key = private_key.public_key() 89 | serialized_public = public_key.public_bytes( 90 | encoding=serialization.Encoding.PEM, 91 | format=serialization.PublicFormat.SubjectPublicKeyInfo 92 | ) 93 | return serialized_public 94 | 95 | #DH exchange from the client point of view. Is written in order. 96 | def diffieHellmanExchange(s, fernet_key): 97 | dhDone = False 98 | while dhDone == False: 99 | serialized_public = "" 100 | fernet = 0 101 | #If you are a hub you can exit in the "blank state" with ctrl^c 102 | try: 103 | unparsed = s.recv(4096) 104 | data = json.loads(unparsed) 105 | except KeyboardInterrupt: 106 | sys.exit() 107 | 108 | if not data: 109 | print '\nDisconnected from chat server' 110 | sys.exit() 111 | else : 112 | if data["dh"] == "c": 113 | #Is client, create keys and send. 114 | keyExchange(s) 115 | if data["dh"] == "h": 116 | #The client is the key hub. 117 | sendFernet(s, data, fernet_key) 118 | dhDone = True 119 | if data["dh"] == "c1": 120 | #get private DH key 121 | private_key = getPrivateECDHKey() 122 | fernet = getKeyFromHub(private_key, data["message"]) 123 | dhDone = True 124 | return fernet 125 | 126 | 127 | def getPrivateECDHKey(): 128 | file = open("ecdh/private.pem", "r") 129 | serialized_private = ''.join(file.readlines()) 130 | private_key = serialization.load_pem_private_key( 131 | serialized_private, 132 | #Could be None. Doesn't matter as the key will be overwritten for next ECDH-exchange so it's not a security risk. 133 | password=b'test', 134 | backend=default_backend() 135 | ) 136 | return private_key 137 | 138 | def keyExchange(s): 139 | serialized_public = clientCreateKeys() 140 | s.send(serialized_public) 141 | 142 | def sendFernet(s, data, fernet_key): 143 | pub = data["message"].encode("utf8") 144 | client_public = serialization.load_pem_public_key( 145 | pub, 146 | backend=default_backend() 147 | ) 148 | jsonstr = hubExchange(client_public, fernet_key) 149 | s.send(jsonstr) 150 | -------------------------------------------------------------------------------- /Client/ecdh/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIBHDBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIaaQwNemjYfECAggA 3 | MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBDr76XrzK1vXj+YcVmpyNFbBIHA 4 | TqtaH2R7OuX9O3tJFcZS0PXcQc6wW3G1mTVfW/lZaTETKt0O9ZE5LaHgBK6XJcDp 5 | gt8esPkrDpy0VJmjchC73ugUAI+uEITkWNV6TeMMIxri9Tl3I0V1Kxe8iT4hGg0v 6 | +hiCBQMwtNY0FXgPWDF1l/LLI5rdJI1N59JhSrtVqxzX3YLGLK5azo3Q5ZhhNsSO 7 | Vi+5crMkrLEkmVOtP5EUrjEhFtrcvh+OPs0gmxC6K0Be31sG/Z478NK/TvgrDIQ3 8 | -----END ENCRYPTED PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /Client/keys/client.pkey: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDUpKRBnRujm+aE 3 | 30fJsiIr62/q1u8yRrmlkpnPHaDxrDC726LDN0dHLj455u6HYi9HRJjK4hThqS46 4 | Jng4Idj2Tehlt7oMZZcAjRgfwf9SqqWJV997qMPI8dC0VCUqrqyPuA3rUFnEZ3X7 5 | 4wY75GwnrCXJMflMl/7qHm6J2NyudQRoaYUCB3KjJq9YV2GfUTyvtMGXaa5ENYzW 6 | 9XCIBmu3+K7/JgpE9xMXRdfpnsWespwe9lLmarjaE4yZLb4N5SLlVzeNMrSDolSS 7 | DWO3oLrpsrcwOd7XHTCmNQsv5kshKnUcHcDDlbaubO8qX5ueT3iW4g4rCrt7Xpet 8 | H8x3nn/3AgMBAAECggEBAMxxp0M2bDZ3U8D4c6rmS5hyel4NXFi4SxF3y82oLO9u 9 | oERdvTV31l4HhehhsYAU1GPu/gBCMdVstXdPOVKuqEzXRtNVO9ljazUqcsFF4dd3 10 | HkTYaZQZBHQUmpZmOH27B0n+qdNOo6AD4RnWgk3fJmVWS9YO6CPOF1a8/4YlLLfF 11 | JR5OKuwD2RDw5i6PvrZtlSzVAs0LIBdXTFyW/AS1hb3FjRzZgE2tO3+T9S2jHX6E 12 | 8zCfotKJnQ0ukzfg8JjBCtMCNyUMs2QtrUr+Xaqj051UPkAB8Y6XcuEA3OxURj/w 13 | ca6l5T5wJK2whF2G7iyCuX9v31WN7IGznG7KBk8mN9ECgYEA8ReNEWrzDtjGS17h 14 | 071mvVDmOnzUGz6AnZX1ayHP+vjRBvFdS4kpstS6p9p/0WpaKxarqrHVVDgTXDt1 15 | ZL2HDWLywLAJngwl0WBMoMh5OhQAI814m1C4t1wabxuJbJdaxzBWQzSwA+ZuKJtw 16 | 6ycMBs2KWwZavtiE9TB2O0wGTqsCgYEA4cq/3D+UPC7FBc7XGRdXW+yj1jtMclKW 17 | IZv6XwsNZ2Fxs8s5XuDM6He4b8hL0cTuqZSxMHbnWbunstBr+TqVgMKbBxkUeq1n 18 | dgDtVAXDFbvAt1UzU27S6Yk1id1xyFBNijAaEG/X5Xpl6jKLzQOCYMEfNwaRiiWJ 19 | AtLVYFiqY+UCgYEA8I0dFdnuNkwYXi+Hh2sdTZuge479W0mbrqBnRMNx7unANfsx 20 | qSJsVqBmtQAQNHS3DvMJycCyJ9vjhU1WLnPkgnjN2XBLyEQ6n5REINCmFErMcOyc 21 | f+ukwl7FiUKidZT24HPcPiYd0WZjV2Tw39AP8eG3QGcwt+b1kmcOMNAEIw0CgYEA 22 | 1vsTaCJFAnRJdTk2RhaaP9T1PaHiLKuDdGatoVbFoVv/RX/GlRaXIvPycMQVpJnL 23 | 3zzK5Gkh56cria62T2s/M4SW1NBFVnTKIGnLjgeacpP8btI5d0P6nw7cIfLtRhtn 24 | aJ5aArJqrny2kqEjyMSBm5vlG6AubIlGhaC2NvDU8x0CgYBXR0Uq7gfItNJbJ7t5 25 | Ce+bkb87mjFFBm9hwoyrONwtw/1caKRUc3nciqZy0ZagGAUzVgfh1QErTayJCrKt 26 | K6vD76dj6KKLP7esFtB3jy/2qXphwxszHnGhvgXcY0jbrXlW1P2Ioh5kpvaTXAo2 27 | 4Je40bCxEYESnTrfWH9x4HyiRA== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /Client/keys/server.pkey: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC19WfJ32jmoqGt 3 | ozwFsMRvEiG/L5t/MB9TCLRPD62NXUy9QLa0mRkApMLDTL7n3vNIUz7bgKrgtzDU 4 | o7nMj172L2u0qWALfmp9zE59q2vNjAz0HnYenMMqiZJYMe/lfMUPBp7QLMu9LxEU 5 | HCpV3bo3lx1mUJy0RN8vo/udR8/+f5qOzgBAO2Br5ShFEqWV93rMrn4zRbkM4Yhh 6 | AcmZH/oDmEZFfX7W9MoLhM6eaNSTT23X5HdRMIjtgID/UjsNTBmFkOo3pJvr34kQ 7 | xgVla+sLYcl7geVITzOhVscZam+RH1miA8IA+tb61rJcGIF5ZDAiMQJ7BLtAYtxX 8 | 0LAYlyvxAgMBAAECggEBAJ0PQ9QrhEe3z2iqwq+CwjvBYeG9/TGO3yr0E3c+6CZi 9 | h0/AYTSIBd3ZWcfsIcjRWBtwx4v+hwDK/1o8LK9WfbhwfD6ICMBDETaeuI2e/sGA 10 | htncxUZtN8Uq1GhOSm1DAnGS3FiCoiYdT1eVbDGL3yYGyTi8klbEMAXWPOSozO81 11 | zwdycTkSceFVrxDg3lT7DWUWVFUNL5xtsg6O4CG2uj3qlGCoSa/6WvXIyG+uxDpA 12 | kxeZk9PRbU9S1qQTXPUsPH6+vU6Mh/Qtgp1j1979lngpzjkgqko108ftQcRfC+Rc 13 | ihH5iB/enNsFlj7QEgbSvsBJFpcPW12N14N+x9W5KMUCgYEA3QT9UD2ptlEDRZ3l 14 | rddru2LkIlTPCQiAES/jWt/2Q0g85d73f7XdKImrGrfXTrZCnhzCLMRs/a2WFLpU 15 | x2cPBi1dXc4LW7XKNe3w2pIUdNBvztQqqjZJLLnGpwQcsQC39IpT3SpItJsTxOCW 16 | GRrGxMs4stsw+0D9wfKQa3pySM8CgYEA0sHK9t8mW3eiJIOK5sib1qfCNzc23wnK 17 | sZCAhLktWjqTvVtK85e7McOmzKzlLkOqWrFu0bEFRDSv+hjuJqxyVSBjUaFSOedz 18 | mYxGBY4CfW5uFCVUZoPn59fOImbk0PHRE2D0s+9TEvB+BaIJoh+y27cp4Pn6k47j 19 | CXSKjBci7z8CgYEAimGaXMgO30i/3LGs4XhUSoHzRo+FBSneTWSHXBmHrb6n6mD8 20 | 42gxrTTs/ZCHbQ/ui+KDrNXbS8WIhzfW5vZe0MZMg/QC5overH9q4t69khoDNyNe 21 | By5wCGHUwDNx510E0EZflcTourOx3/2h3NwIAaUfvdRBSb76YpmF1NVYgr8CgYAE 22 | ZwnvWD/73ybi7mCDa6Ndu4liD+Q0iobSaLtWimu5G8okRXjuV7gQVQ7x7Zan/b/c 23 | PyKafMEoaJVYexbWdfm5b4DDnsQR85TFm1mHGi1RI3JnN1RpA85fWHG05ENZXgPJ 24 | DNhvvcsVdTKcuYLMAS+CSXQ1f+NNG8oAU4/lV0JijwKBgF6spjD7mF+XiidfmXJu 25 | uTBIVCQd6UUf0P94nkY2kw51BafO2AOn0iTiaKOHdh307zBYhHgY6Z8PV3/iIt08 26 | lNyQiSo5f5Nx4vmi5y6N4w9ZHIQGg+pv6KVwyTUpKZEiIBKlJJLVV4LqBST2rY9o 27 | 0oV+Xx3TFe40WiJHVUT+9Kqz 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /Client/messencrypt.py: -------------------------------------------------------------------------------- 1 | from cryptography.fernet import Fernet 2 | from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC 3 | from cryptography.hazmat.primitives import hashes 4 | from cryptography.hazmat.backends import default_backend 5 | import base64,os, json 6 | 7 | def encrypt(plaintext, key): 8 | #Create a Fernet block and use it for encryption. 9 | fblock = Fernet(key) 10 | return fblock.encrypt(bytes(plaintext)) 11 | 12 | def decrypt(ciphertext, key): 13 | fblock = Fernet(key) 14 | return fblock.decrypt(bytes(ciphertext)) 15 | 16 | def generateFernetKey(): 17 | #Generate the fernet key and put it through a KDF (for AES cipher compability) 18 | salt = os.urandom(16) 19 | fernet_key = Fernet.generate_key() 20 | kdf = PBKDF2HMAC( 21 | algorithm=hashes.SHA256(), 22 | length=32, 23 | salt=salt, 24 | iterations=100000, 25 | backend=default_backend() 26 | ) 27 | fernet_key = kdf.derive(fernet_key) 28 | return fernet_key 29 | -------------------------------------------------------------------------------- /Client/mk_cert_files.py: -------------------------------------------------------------------------------- 1 | from OpenSSL import crypto 2 | from createCert import * 3 | 4 | #modified examples from pyOpenSSL examples 5 | def createCA(): 6 | cakey = createKeyPair(TYPE_RSA, 2048) 7 | careq = createCertRequest(cakey, CN='Certificate Authority') 8 | cacert = createCertificate(careq, (careq, cakey), 0, (0, 60*60*24*365*5)) # five years 9 | open('CA.pkey', 'w').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, cakey)) 10 | open('CA.cert', 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, cacert)) 11 | 12 | return cakey, cacert 13 | 14 | def createRequest(origin): 15 | pkey = createKeyPair(TYPE_RSA, 2048) 16 | req = createCertRequest(pkey, CN=origin) 17 | open('keys/%s.pkey' % (origin,), 'w').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)) 18 | return req 19 | 20 | def signCertificates(req, cacert, cakey): 21 | cert = createCertificate(req, (cacert, cakey), 1, (0, 60*60*24)) #24 hours. 22 | return cert 23 | -------------------------------------------------------------------------------- /Client/sign.py: -------------------------------------------------------------------------------- 1 | from cryptography.hazmat.backends import default_backend 2 | from cryptography.hazmat.primitives import hashes 3 | from cryptography.hazmat.primitives.asymmetric import ec 4 | from cryptography.hazmat.primitives import serialization 5 | 6 | import json, sys, base64 7 | import getpass 8 | 9 | def createSignature(pathToPrivateKey, prehashed): 10 | file = open(pathToPrivateKey, "r") 11 | serialized_private = ''.join(file.readlines()) 12 | #The password set by the user 13 | password = getpass.getpass("Enter your key password>") 14 | try: 15 | private_key = serialization.load_pem_private_key( 16 | serialized_private, 17 | password=bytes(password), 18 | backend=default_backend() 19 | ) 20 | signature = private_key.sign( 21 | base64.b64decode(prehashed), 22 | ec.ECDSA(hashes.SHA256()) 23 | ) 24 | return signature 25 | 26 | except: 27 | print "Wrong password." 28 | return createSignature(pathToPrivateKey, prehashed) 29 | 30 | #Verifies the signed message sent from the client 31 | def verifySignature(serialized_public, signature, prehashed): 32 | public_key = serialization.load_pem_public_key( 33 | serialized_public, 34 | backend=default_backend() 35 | ) 36 | #Verify raises an exception if not valid. 37 | try: 38 | public_key.verify(signature, prehashed, ec.ECDSA(hashes.SHA256())) 39 | return True 40 | except: 41 | return False 42 | 43 | #Creates private/public elliptic curve key pair. 44 | def createSerializedKeys(password): 45 | private_key = ec.generate_private_key(ec.SECP384R1(), default_backend()) 46 | serialized_private = private_key.private_bytes( 47 | encoding=serialization.Encoding.PEM, 48 | format=serialization.PrivateFormat.PKCS8, 49 | encryption_algorithm=serialization.BestAvailableEncryption(bytes(password)) 50 | ) 51 | file = open("private.pem", "w") 52 | file.write(serialized_private) 53 | file.close() 54 | public_key = private_key.public_key() 55 | serialized_public = public_key.public_bytes( 56 | encoding=serialization.Encoding.PEM, 57 | format=serialization.PublicFormat.SubjectPublicKeyInfo 58 | ) 59 | file = open("public.pem", "w") 60 | file.write(serialized_public) 61 | file.close() 62 | 63 | #Sign the message sent by the server. 64 | def signMessage(s, username, password): 65 | unparsed = s.recv(4096) 66 | data = json.loads(unparsed) 67 | if not data : 68 | print '\nDisconnected from chat server' 69 | sys.exit() 70 | else : 71 | #First step in DH, is client 72 | createSerializedKeys(password) 73 | signature = createSignature("private.pem", bytes(data["message"])) 74 | public_key = getPublicKey("public.pem") 75 | json_str = {"username": username, "public_key": public_key, "signature": base64.b64encode(signature)} 76 | s.send(json.dumps(json_str)) 77 | 78 | def getPublicKey(pathToPublicKey): 79 | file = open(pathToPublicKey, "r") 80 | serialized_public = ''.join(file.readlines()) 81 | return serialized_public 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python chat 2 | 3 | ## Introduction 4 | A basic example of a chat application which supports groups up to 10 people for each room. The goals of this project was to gain knowledge and experience in implementing encryption, diffie-hellman key exchange, digital signatures and SSL certificates into an Python application. This application is NOT(!!!!) meant for production or the sending of any sensitive data, therefore the GUI is ugly and very simplified and some huge security details are overlooked, see security issues for details. 5 | 6 | Some security features/measures is: 7 | 8 | * End-to-end encrypted message between clients using Fernet, which is a method that builds upon AES-128-CBC mode and a SHA256 hash authentication code (HMAC). 9 | 10 | * Elliptic curve diffie-hellman (ECDH) key exchange. I've implemented ECDH so that it works for more than two clients. Further explaination below. 11 | 12 | * SSL socket between clients and server, where server acts as both server and CA. 13 | 14 | * Digital signatures using elliptical curves. 15 | 16 | ## ECDH 17 | The client that creates the chatroom is elected "key hub". The key hub is responsible for creating a Fernet key which will be used by all parties. Each client that now connects to the chat room does a key exchange with the key hub using elliptic curve diffie-hellman to negotiate a shared key. The shared key is then used by the hub to encrypt the Fernet key by using AES-CBC mode. The client then decrypts the Fernet key by using the shared key. Both parties now shares the same Fernet key and can exchange messages without any interception. 18 | 19 | If the key hub exits the chat a new hub is elected, and no further action is needed as the new key hub already has the Fernet key. 20 | 21 | ## Security issues 22 | Those that I know of: 23 | * When a server verifies a client the client first sends it public key to the server. This is obviously wrong. A solution would be a login/register for clients which would generate a public key put in a database that the server would check with each challenge. 24 | * The SSL certificate verification from the client has a callback function that is not implemented. Further research is essential for this to be OK. 25 | 26 | ## Usage 27 | Before connecting to a server the user has to create a password. This password is for encrypting the PEM encoded elliptic curve private key used in digital signatures. 28 | When first connecting to the server a menu is presented with several choices. Here you can choose between creating a new chat room, joining a chat room by name and getting a list of all chat rooms currently active. 29 | 30 | The rest of the implementation details is commented in the code. 31 | 32 | 33 | ## Snapshots 34 | ![img](https://imgur.com/c6T6RRv.png) 35 | 36 | ![img](https://imgur.com/b1kyk2j.png) 37 | 38 | ![img](https://imgur.com/3EPV8ko.png) 39 | 40 | ![img](https://imgur.com/DYFmOl3.png) 41 | 42 | As mentioned earlier. The GUI could be alot better. But that was not the focus of this project. 43 | 44 | -------------------------------------------------------------------------------- /Server/CA.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICtDCCAZwCAQAwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UEAwwVQ2VydGlmaWNh 3 | dGUgQXV0aG9yaXR5MB4XDTE3MTEyMDA4Mjc1NVoXDTIyMTExOTA4Mjc1NVowIDEe 4 | MBwGA1UEAwwVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF 5 | AAOCAQ8AMIIBCgKCAQEA4ycfzHNxn9Knmqz1FhbzW50o0v+HXscgNzUfboj95wBq 6 | Zf2OMo7R1Gpp+PKm5ONFnFfvyGQQRmukgT366WR8ZbH8g8hRqsPutE0LZtP0+bOm 7 | 7+zz+XbOXRRM3yNRYWymDaFUtn2INqTANMrVPtA0hThdmnuBw19TUwBJcpbM+9FF 8 | zarGuTIRYRt7AQy2nbOoC2LzW726cgApT68TiF/rKGyPrBUeTZyc/IsXFEWfSlyl 9 | lqeafyouRxb+hbKNmLsPtoeMOpdBEqXUfk2REH2coKMacuQQnekx5IXhZuQxSwSn 10 | Ox2lZWoErlqfWmho9GAwuJd2IldptrVdY63Ea0ezwQIDAQABMA0GCSqGSIb3DQEB 11 | CwUAA4IBAQCS4rAax3lgVzaWNJPuMXVfw3Jvjh2IDJstGv966+oLWec8e//tV3Ki 12 | AmxZwkCm6Vp9jE/DbwbwZedbbSiIlzfYK9cw8RKzYTdjkibYZfW8ifb+z+NZ07Ol 13 | TiFKG9k6du/wTmID//gD0w3we19bomfaa09CJ4juTTvu7DqqtgM7FXWvzuR5XfPB 14 | FfJphd3SUE/Wc0dKeKxwrg2xS9mDuEnU6VscKMdIYssVDk8EYrjfUJnFOV89FwxX 15 | FbcatR5BZz4LYelYXfDFVv1OrLP4me09Ce/2kDGcAHIvZpuapjqFBoRUBaStd629 16 | k6Ff3kckoJ/plkAknc5cVI+iFOwN4Fvt 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /Server/CA.pkey: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDjJx/Mc3Gf0qea 3 | rPUWFvNbnSjS/4dexyA3NR9uiP3nAGpl/Y4yjtHUamn48qbk40WcV+/IZBBGa6SB 4 | PfrpZHxlsfyDyFGqw+60TQtm0/T5s6bv7PP5ds5dFEzfI1FhbKYNoVS2fYg2pMA0 5 | ytU+0DSFOF2ae4HDX1NTAElylsz70UXNqsa5MhFhG3sBDLads6gLYvNbvbpyAClP 6 | rxOIX+sobI+sFR5NnJz8ixcURZ9KXKWWp5p/Ki5HFv6Fso2Yuw+2h4w6l0ESpdR+ 7 | TZEQfZygoxpy5BCd6THkheFm5DFLBKc7HaVlagSuWp9aaGj0YDC4l3YiV2m2tV1j 8 | rcRrR7PBAgMBAAECggEBALsUIt1IiuD9Jmp+Xc+WkVD3i4LTTtMhHog5aSypCKuy 9 | rO5YuD8tXuUZyzNr7UX/Y/pSdjpNJxZIWxDSUg7cYpOC1hxW4+PHQJYgCe7jTBRH 10 | AIsDVSA019rS+EiiFsktKUl6gIFXu9J9N4uDLcNwyICbnoO3inS00rQ+PT9xsVsG 11 | P6QfO/16/elRCwJDe0FGuZ1I+gdMBoWVXnV7yj/uDv9CYv9pFoNpHlL0s76JBQwH 12 | QL/zH8ZkDvI7WT3Z2a9rPcwAWHDbISifdFVhoE0QSfxxoWd1OgIVO7pxqQKhOQ5q 13 | T3YWUyCIu6LGLg1NM6BPFaQQpbtp7VXyOe/Te8fqjZ0CgYEA96HufFgmCpnms2B+ 14 | l4lqgVlZdcwJJepjCcs8A84qElBGlZwDbM3eL9oYBCLgjQSbHrt3FFrTchcL8LwR 15 | +rEN0c1GV0jKFSCAcvE7oVtXN1S4G730jLP0ofH00/znmmd073u6pTgaCCE/a9Cr 16 | 6+5YkB1VxT0Pflm2ZPhLe6CpfAcCgYEA6tQKCUYu3cFJhhtWhbGeWCP5TSTnWD5/ 17 | Ro6kDqbtyIDfKxT8tMsAdDhcm65BQmr1SGLS6zARojf9rmf9HFlppjPSjoc18qNe 18 | IDG1Am8n2f43W2DrxVnIe3qBvqporEWTwmOM3ZZiJXwt+YUofEHsysU9XcyRLhLB 19 | wjPk8dK2b/cCgYA5CWQ74bXJZU9c65Hc53rPljvjxP4qbIUpQVVubvMDytenKAsl 20 | 6XkOvoYflcTw1inAd19jIdRoB+TpKvhdLKDJPSXBfvOE0DqlvuiELty00t+hDCyi 21 | p+Rl1KwWk77osoGdk3sXCQf97WKFO8gNjJV7hDY4OusxqAo50XLZY9UFywKBgQCu 22 | sFha99PLcYtxcQrXX/+MUA6YPdi/w5ZI9bnDkJiZLI1X7j/jepR24Kq7wVCYyRp4 23 | 49NF1Dd59q8HvCIFodo/IpQDJUCTpMZTqsad7fS2bS/fEWWzJQxIFyVHoltFtGis 24 | FgRnnwSaIBvPYChy71Por/vruE9T9+4dux8Yagj/lwKBgQCW7sz/fj/SlMrDwCZh 25 | a8j5fA85EXeHwt3JvBd2JFrJ84I/H+vS/AW2ExV7mZ6WrxGq/A+9F3AW0ZCiOeAx 26 | Icpp4iglzlR8/RDEsks9xyGOTOPhwAWXI9Ry9Qg/rEhRXFUYebui0y63x/0q9sC+ 27 | YW74ZLoCC5ku9rKLKd6yBKXxPQ== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /Server/chat_server.py: -------------------------------------------------------------------------------- 1 | # chat_server.py 2 | import sys, socket, select, base64, os 3 | import threading 4 | from threading import Lock 5 | import simplejson as json 6 | from sign import verifySignature 7 | import Queue 8 | 9 | #ssl 10 | from mk_cert_files import * 11 | from OpenSSL import SSL 12 | 13 | #x509 14 | from cryptography.hazmat.backends import default_backend 15 | from cryptography.hazmat.primitives import serialization 16 | from cryptography.hazmat.primitives.asymmetric import rsa 17 | from cryptography import x509 18 | from cryptography.x509.oid import NameOID 19 | from cryptography.hazmat.primitives import hashes 20 | import datetime 21 | 22 | def verify_cb(conn, cert, errnum, depth, ok): 23 | # This obviously has to be updated 24 | #print 'Got certificate: %s' % cert.get_subject() 25 | return ok 26 | 27 | def main(): 28 | #create server certificates. 29 | createServerCert() 30 | #Defines which port that should be accessible 31 | q = Queue.Queue() 32 | for i in range(9010, 9015): 33 | q.put(i) 34 | chat_handler(q) 35 | 36 | #JSON = {nameOfRoom, passwordToRoom, operation: create} - create a thread 37 | #JSON = {nameOfRoom, operation: search} - return port number of room 38 | #JSON = {operation:list} - return all rooms 39 | def chat_handler(queue): 40 | HOST = '' 41 | SOCKET_LIST = [] 42 | RECV_BUFFER = 4096 43 | PORT = 9009 44 | HubElector = 0 45 | threadName = 0 46 | #Creates a dictionary for port and servername 47 | roomDict = {} 48 | 49 | #START SIGNING THREAD 50 | signThread = threading.Thread(target=signCertThread,) 51 | signThread.daemon = True 52 | signThread.start() 53 | 54 | #Init a SSL socket 55 | ctx = initSSL() 56 | server_socket = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM)) 57 | server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 58 | server_socket.bind(('', PORT)) 59 | server_socket.listen(10) 60 | SOCKET_LIST.append(server_socket) 61 | 62 | done = False 63 | while done == False: 64 | ready_to_read,ready_to_write,in_error = select.select(SOCKET_LIST,[],[],0) 65 | for sock in ready_to_read: 66 | #New connection 67 | if sock == server_socket: 68 | sockfd, addr = server_socket.accept() 69 | SOCKET_LIST.append(sockfd) 70 | else: 71 | try: 72 | unparsed = sock.recv(RECV_BUFFER) 73 | data = json.loads(unparsed) 74 | if data: 75 | if data["operation"] == "create": 76 | #Check if room already exists 77 | if data["name"] in roomDict: 78 | sock.send("exist") 79 | else: 80 | port = queue.get() 81 | #Create thread for chat_server and add to rooms 82 | threadName = threading.Thread(target=chat_server, args = (port, queue,roomDict, data["name"])) 83 | #Check Queue status 84 | if queue.empty() == False: 85 | sock.send("create") 86 | roomDict[data["name"]] = port 87 | threadName.daemon = True 88 | threadName.start() 89 | threadName += 1 90 | else: 91 | sock.send("full") 92 | 93 | if data["operation"] == "join": 94 | try: 95 | port = roomDict[data["name"]] 96 | except: 97 | port = 0 98 | sock.send(str(port)) 99 | 100 | if data["operation"] == "list": 101 | #get list of all server rooms 102 | roomlist = " " 103 | for key in roomDict: 104 | roomlist+=key + " " 105 | sock.send(str(roomlist)) 106 | 107 | else: 108 | # remove the socket that's broken 109 | print "removing\n" 110 | if sock in SOCKET_LIST: 111 | SOCKET_LIST.remove(sock) 112 | # exception 113 | except: 114 | continue 115 | 116 | server_socket.close() 117 | 118 | def chat_server(PORT, queue, roomDict, name): 119 | HOST = '' 120 | SOCKET_LIST = [] 121 | RECV_BUFFER = 4096 122 | HubElector = 0 123 | HUBSOCK = '' 124 | mutex = Lock() 125 | #Creates a dictionary with addresses and usernames 126 | userDict = {} 127 | 128 | #Init and create SSL socket 129 | ctx = initSSL() 130 | server_socket = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM)) 131 | server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 132 | server_socket.bind(('', PORT)) 133 | server_socket.listen(10) 134 | SOCKET_LIST.append(server_socket) 135 | 136 | print "Chat server started on port " + str(PORT) 137 | 138 | chatDone = False 139 | while chatDone == False: 140 | # get the list sockets which are ready to be read through select 141 | # 4th arg, time_out = 0 : poll and never block 142 | ready_to_read,ready_to_write,in_error = select.select(SOCKET_LIST,[],[],0) 143 | 144 | for sock in ready_to_read: 145 | # a new connection request recieved 146 | if sock == server_socket: 147 | sockfd, addr = server_socket.accept() 148 | #s.connect(('localhost', port)) 149 | SOCKET_LIST.append(sockfd) 150 | 151 | #Verify the signature of the client 152 | #IMPORTANT!!! The public key should be authenticated with a login/register, where a user generates a key pair 153 | #and saves that key to the database, which the server will then get. 154 | 155 | #server receives username 156 | #getPublicKey(username) --sql query. 157 | 158 | #In this basic example the sender will send the public key for testing purposes. 159 | check = createAnVerifySignature(sockfd, addr, userDict) 160 | 161 | if HubElector != 0: 162 | #Preventing hub socket from hadnling data from two sockets 163 | mutex.acquire() 164 | keyExchange(HUBSOCK, sockfd, server_socket) 165 | mutex.release() 166 | 167 | #Electing hub 168 | if HubElector == 0: 169 | HUBSOCK = sockfd 170 | HubElector = 1 171 | 172 | print "Client (%s, %s) connected" % addr 173 | 174 | broadcast(SOCKET_LIST, server_socket, sockfd, "server", "[%s] entered our chatting room\n" % userDict[addr]) 175 | 176 | else: 177 | try: 178 | #Broadcast the data sent by the client 179 | data = sock.recv(RECV_BUFFER) 180 | if data: 181 | broadcast(SOCKET_LIST, server_socket, sock, userDict[(sock.getpeername())], data) 182 | else: 183 | #Chat room is empty. Quit the thread and put the port back in the queue 184 | if len(SOCKET_LIST) == 1: 185 | chatDone = True 186 | #Socket who disconnected was the Hub, elect new hub by finding the first valid. 187 | if sock == HUBSOCK: 188 | HUBSOCK = electNewHub(SOCKET_LIST, server_socket) 189 | 190 | #Redundancy, remove the sock cause it's probably broken. 191 | SOCKET_LIST.remove(sock) 192 | broadcast(SOCKET_LIST, server_socket, sock, "server", "Client (%s) is offline\n" % userDict[sock.getpeername()]) 193 | userDict.pop(sock.getpeername(), None) 194 | except: 195 | SOCKET_LIST.remove(sock) 196 | broadcast(SOCKET_LIST, server_socket, sock, "server" ,"Client (%s) is offline\n" % userDict[sock.getpeername()]) 197 | userDict.pop(sock.getpeername(), None) 198 | continue 199 | 200 | #Quitting the thread. 201 | print "Quitting thread: ", PORT 202 | queue.put(PORT) 203 | roomDict.pop(name, None) 204 | server_socket.close() 205 | return 206 | 207 | # broadcast chat messages to all connected clients 208 | def broadcast (SOCKET_LIST, server_socket, sock, sender, data): 209 | for socket in SOCKET_LIST: 210 | # send the message only to peer 211 | if socket != server_socket and socket != sock : 212 | try : 213 | #Server message 214 | if sender == "server": 215 | message = data 216 | dh = "server" 217 | else: 218 | message = data 219 | dh = sender 220 | json_string = {"message": message, "dh": dh} 221 | socket.send(json.dumps(json_string)) 222 | except : 223 | print "error" 224 | # broken socket connection 225 | socket.close() 226 | # broken socket, remove it 227 | if socket in SOCKET_LIST: 228 | SOCKET_LIST.remove(socket) 229 | 230 | def keyExchange(hub_socket, client_socket, server_socket): 231 | json_string = {"message":"", "dh":"c"} 232 | client_socket.send(json.dumps(json_string)) 233 | #listen for client keys 234 | client_public = client_socket.recv(4096) 235 | #hend the public client key to Hub 236 | json_string = {"message": client_public, "dh": "h"} 237 | hub_socket.send(json.dumps(json_string)) 238 | #listen for the encrypted Fernet key from the Hub 239 | encrypted_fernet = hub_socket.recv(4096) 240 | #Send the encrypted fernet key, hub pubzlic key and some encryption data to the client. 241 | json_string = {"message": encrypted_fernet, "dh": "c1"} 242 | client_socket.send(json.dumps(json_string)) 243 | 244 | def createAnVerifySignature(client_socket, addr, user_dictionary): 245 | #First send the hashed message 246 | prehash = base64.b64encode(os.urandom(16)) 247 | json_str = {"message":prehash} 248 | client_socket.send(json.dumps(json_str)) 249 | #Receive signature and public key IMPORTANT!!! Obviously not for production 250 | unparsed = client_socket.recv(4096) 251 | data = json.loads(unparsed) 252 | #Verify the signature and send acknowledgement 253 | check = verifySignature(data["public_key"], base64.b64decode(data["signature"]), prehash) 254 | user_dictionary[addr] = data["username"] 255 | return check 256 | 257 | def electNewHub(socket_list, server_socket): 258 | for socket in SOCKET_LIST: 259 | if socket != server_socket: 260 | HUBSOCK = socket 261 | break 262 | return HUBSOCK 263 | 264 | def initSSL(): 265 | # Initialize context 266 | #Could be a function from SSLutils. 267 | ctx = SSL.Context(SSL.TLSv1_2_METHOD) 268 | ctx.set_options(SSL.OP_NO_SSLv2) 269 | ctx.set_verify(SSL.VERIFY_PEER|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb) 270 | ctx.use_privatekey_file (os.path.join('keys', 'server.pkey')) 271 | ctx.use_certificate_file(os.path.join('', 'server.cert')) 272 | ctx.load_verify_locations(os.path.join('', 'CA.cert')) 273 | return ctx 274 | 275 | def createServerCert(): 276 | #load CAkey and cert 277 | file = open('CA.pkey') 278 | cakey = ''.join(file.readlines()) 279 | cakey = crypto.load_privatekey(crypto.FILETYPE_PEM, cakey) 280 | file.close() 281 | 282 | file = open('CA.cert') 283 | cacert = ''.join(file.readlines()) 284 | cacert = crypto.load_certificate(crypto.FILETYPE_PEM, cacert) 285 | file.close() 286 | 287 | #Creating server certificate and signing it with the CA private key. Is ok as the server is also the CA :-) 288 | serv_req = createRequest('server') 289 | serv_cert = signCertificates(serv_req, cacert, cakey) 290 | #Writes the cert as PEM encoded to disk 291 | open('server.cert', 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, serv_cert)) 292 | 293 | def signCertThread(): 294 | print "Started signing thread" 295 | HOST = '' 296 | RECV_BUFFER = 4096 297 | PORT = 9998 298 | #Creates a dictionary for port and servername 299 | SOCKET_LIST = [] 300 | server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 301 | server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 302 | server_socket.bind((HOST, PORT)) 303 | server_socket.listen(10) 304 | 305 | SOCKET_LIST.append(server_socket) 306 | 307 | #load CAkey and cert 308 | file = open('CA.pkey') 309 | cakey = ''.join(file.readlines()) 310 | cakey = crypto.load_privatekey(crypto.FILETYPE_PEM, cakey) 311 | file.close() 312 | 313 | file = open('CA.cert') 314 | cacert = ''.join(file.readlines()) 315 | cacert = crypto.load_certificate(crypto.FILETYPE_PEM, cacert) 316 | file.close() 317 | 318 | done = False 319 | while done == False: 320 | 321 | ready_to_read,ready_to_write,in_error = select.select(SOCKET_LIST,[],[],0) 322 | for sock in ready_to_read: 323 | # a new connection request recieved 324 | if sock == server_socket: 325 | sockfd, addr = server_socket.accept() 326 | 327 | SOCKET_LIST.append(sockfd) 328 | else: 329 | data = sock.recv(4096) 330 | req = crypto.load_certificate_request(crypto.FILETYPE_ASN1,data) 331 | 332 | #Sign the cert_req with CA and return the certificate. 333 | cert_to_be_parsed = signCertificates(req, cacert, cakey) 334 | sock.send(crypto.dump_certificate(crypto.FILETYPE_PEM, cert_to_be_parsed)) 335 | SOCKET_LIST.remove(sock) 336 | sock.close() 337 | 338 | 339 | main() 340 | -------------------------------------------------------------------------------- /Server/createCA.py: -------------------------------------------------------------------------------- 1 | #Creating CA cert and keys 2 | from mk_cert_files import * 3 | createCA() -------------------------------------------------------------------------------- /Server/createCert.py: -------------------------------------------------------------------------------- 1 | from OpenSSL import crypto 2 | 3 | TYPE_RSA = crypto.TYPE_RSA 4 | TYPE_DSA = crypto.TYPE_DSA 5 | 6 | def createKeyPair(type, bits): 7 | """ 8 | Create a public/private key pair. 9 | Arguments: type - Key type, must be one of TYPE_RSA and TYPE_DSA 10 | bits - Number of bits to use in the key 11 | Returns: The public/private key pair in a PKey object 12 | """ 13 | pkey = crypto.PKey() 14 | pkey.generate_key(type, bits) 15 | return pkey 16 | 17 | def createCertRequest(pkey, digest="sha256", **name): 18 | """ 19 | Create a certificate request. 20 | Arguments: pkey - The key to associate with the request 21 | digest - Digestion method to use for signing, default is md5 22 | **name - The name of the subject of the request, possible 23 | arguments are: 24 | C - Country name 25 | ST - State or province name 26 | L - Locality name 27 | O - Organization name 28 | OU - Organizational unit name 29 | CN - Common name 30 | emailAddress - E-mail address 31 | Returns: The certificate request in an X509Req object 32 | """ 33 | req = crypto.X509Req() 34 | subj = req.get_subject() 35 | 36 | for (key,value) in name.items(): 37 | setattr(subj, key, value) 38 | 39 | req.set_pubkey(pkey) 40 | req.sign(pkey, digest) 41 | return req 42 | 43 | def createCertificate(req, (issuerCert, issuerKey), serial, (notBefore, notAfter), digest="SHA256"): 44 | """ 45 | Generate a certificate given a certificate request. 46 | Arguments: req - Certificate reqeust to use 47 | issuerCert - The certificate of the issuer 48 | issuerKey - The private key of the issuer 49 | serial - Serial number for the certificate 50 | notBefore - Timestamp (relative to now) when the certificate 51 | starts being valid 52 | notAfter - Timestamp (relative to now) when the certificate 53 | stops being valid 54 | digest - Digest method to use for signing, default is md5 55 | Returns: The signed certificate in an X509 object 56 | """ 57 | cert = crypto.X509() 58 | cert.set_serial_number(serial) 59 | cert.gmtime_adj_notBefore(notBefore) 60 | cert.gmtime_adj_notAfter(notAfter) 61 | cert.set_issuer(issuerCert.get_subject()) 62 | cert.set_subject(req.get_subject()) 63 | cert.set_pubkey(req.get_pubkey()) 64 | cert.sign(issuerKey, digest) 65 | return cert -------------------------------------------------------------------------------- /Server/dh.py: -------------------------------------------------------------------------------- 1 | import sys, os, base64 2 | import simplejson as json 3 | from cryptography.hazmat.backends import default_backend 4 | from cryptography.hazmat.primitives.asymmetric import ec 5 | from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC 6 | from cryptography.hazmat.primitives import hashes 7 | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 8 | from cryptography.hazmat.primitives import serialization 9 | from cryptography.hazmat.primitives.serialization import load_pem_public_key 10 | from messencrypt import encrypt 11 | from messencrypt import generateFernetKey 12 | 13 | def hubExchange(client_public, fernet_key): 14 | #Generate a private key for use in the exchange 15 | hub_private = ec.generate_private_key( 16 | ec.SECP384R1(), default_backend() 17 | ) 18 | hub_public = hub_private.public_key() 19 | hub_shared = hub_private.exchange(ec.ECDH(), client_public) 20 | salt = os.urandom(16) 21 | kdf = PBKDF2HMAC( 22 | algorithm=hashes.SHA256(), 23 | length=16, 24 | salt=salt, 25 | iterations=100000, 26 | backend=default_backend() 27 | ) 28 | 29 | hub_shared = kdf.derive(bytes(hub_shared)) 30 | 31 | serialized_hub_public = hub_public.public_bytes( 32 | encoding=serialization.Encoding.PEM, 33 | format=serialization.PublicFormat.SubjectPublicKeyInfo 34 | ) 35 | 36 | return sendKeyToClient(hub_shared, fernet_key, serialized_hub_public, salt) 37 | 38 | 39 | def sendKeyToClient(shared_key, fernet_key, serialized_hub_public, salt): 40 | 41 | iv = os.urandom(16) 42 | cipher = Cipher(algorithms.AES(shared_key), modes.CBC(iv), backend=default_backend()) 43 | encryptor = cipher.encryptor() 44 | ct = encryptor.update(bytes(fernet_key)) + encryptor.finalize() 45 | #Send public, CT, IV and salt 46 | json_string = {"hub_public": serialized_hub_public, "ciphertext": base64.b64encode(ct), "iv": base64.b64encode(iv), "salt": base64.b64encode(salt)} 47 | return json.dumps(json_string) 48 | 49 | 50 | def getKeyFromHub(client_private, public_cipher_iv): 51 | parsed = json.loads(public_cipher_iv) 52 | 53 | hub_serialized = parsed['hub_public'].encode('ascii') 54 | ct = base64.b64decode(parsed['ciphertext']) 55 | iv = base64.b64decode(parsed['iv']) 56 | salt = base64.b64decode(parsed['salt']) 57 | 58 | hub_public = load_pem_public_key(hub_serialized, backend=default_backend()) 59 | shared_key = client_private.exchange(ec.ECDH(), hub_public) 60 | #The shared key needs to be 16 bytes (AES blocksize). So it's put through a KDF 61 | kdf = PBKDF2HMAC( 62 | algorithm=hashes.SHA256(), 63 | length=16, 64 | salt=salt, 65 | iterations=100000, 66 | backend=default_backend() 67 | ) 68 | 69 | shared_key = kdf.derive(bytes(shared_key)) 70 | cipher = Cipher(algorithms.AES(shared_key), modes.CBC(iv), backend=default_backend()) 71 | decryptor = cipher.decryptor() 72 | fernet = decryptor.update(ct) + decryptor.finalize() 73 | return fernet 74 | 75 | def clientCreateKeys(): 76 | private_key = ec.generate_private_key(ec.SECP384R1(), default_backend()) 77 | serialized_private = private_key.private_bytes( 78 | encoding=serialization.Encoding.PEM, 79 | format=serialization.PrivateFormat.PKCS8, 80 | encryption_algorithm=serialization.BestAvailableEncryption(b'test') 81 | ) 82 | file = open("ecdh/private.pem", "w") 83 | file.write(serialized_private) 84 | file.close() 85 | public_key = private_key.public_key() 86 | serialized_public = public_key.public_bytes( 87 | encoding=serialization.Encoding.PEM, 88 | format=serialization.PublicFormat.SubjectPublicKeyInfo 89 | ) 90 | return serialized_public 91 | 92 | def diffieHellmanExchange(s, fernet_key): 93 | dhDone = False 94 | while dhDone == False: 95 | serialized_public = "" 96 | fernet = 0 97 | #If you are a hub you can exit in the "blank state" with ctrl^c 98 | try: 99 | unparsed = s.recv(4096) 100 | data = json.loads(unparsed) 101 | except KeyboardInterrupt: 102 | sys.exit() 103 | 104 | if not data: 105 | print '\nDisconnected from chat server' 106 | sys.exit() 107 | else : 108 | if data["dh"] == "c": 109 | #Is client, create keys and send. 110 | keyExchange(s) 111 | if data["dh"] == "h": 112 | #The client is the key hub. 113 | sendFernet(s, data, fernet_key) 114 | dhDone = True 115 | if data["dh"] == "c1": 116 | #get private DH key 117 | private_key = getPrivateECDHKey() 118 | fernet = getKeyFromHub(private_key, data["message"]) 119 | dhDone = True 120 | return fernet 121 | 122 | 123 | def getPrivateECDHKey(): 124 | file = open("ecdh/private.pem", "r") 125 | serialized_private = ''.join(file.readlines()) 126 | private_key = serialization.load_pem_private_key( 127 | serialized_private, 128 | #Could be None. Doesn't matter as the key will be overwritten for next ECDH-exchange so it's not a security risk. 129 | password=b'test', 130 | backend=default_backend() 131 | ) 132 | return private_key 133 | 134 | def keyExchange(s): 135 | serialized_public = clientCreateKeys() 136 | s.send(serialized_public) 137 | 138 | def sendFernet(s, data, fernet_key): 139 | pub = data["message"].encode("utf8") 140 | client_public = serialization.load_pem_public_key( 141 | pub, 142 | backend=default_backend() 143 | ) 144 | jsonstr = hubExchange(client_public, fernet_key) 145 | s.send(jsonstr) 146 | -------------------------------------------------------------------------------- /Server/keys/client.pkey: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDUpKRBnRujm+aE 3 | 30fJsiIr62/q1u8yRrmlkpnPHaDxrDC726LDN0dHLj455u6HYi9HRJjK4hThqS46 4 | Jng4Idj2Tehlt7oMZZcAjRgfwf9SqqWJV997qMPI8dC0VCUqrqyPuA3rUFnEZ3X7 5 | 4wY75GwnrCXJMflMl/7qHm6J2NyudQRoaYUCB3KjJq9YV2GfUTyvtMGXaa5ENYzW 6 | 9XCIBmu3+K7/JgpE9xMXRdfpnsWespwe9lLmarjaE4yZLb4N5SLlVzeNMrSDolSS 7 | DWO3oLrpsrcwOd7XHTCmNQsv5kshKnUcHcDDlbaubO8qX5ueT3iW4g4rCrt7Xpet 8 | H8x3nn/3AgMBAAECggEBAMxxp0M2bDZ3U8D4c6rmS5hyel4NXFi4SxF3y82oLO9u 9 | oERdvTV31l4HhehhsYAU1GPu/gBCMdVstXdPOVKuqEzXRtNVO9ljazUqcsFF4dd3 10 | HkTYaZQZBHQUmpZmOH27B0n+qdNOo6AD4RnWgk3fJmVWS9YO6CPOF1a8/4YlLLfF 11 | JR5OKuwD2RDw5i6PvrZtlSzVAs0LIBdXTFyW/AS1hb3FjRzZgE2tO3+T9S2jHX6E 12 | 8zCfotKJnQ0ukzfg8JjBCtMCNyUMs2QtrUr+Xaqj051UPkAB8Y6XcuEA3OxURj/w 13 | ca6l5T5wJK2whF2G7iyCuX9v31WN7IGznG7KBk8mN9ECgYEA8ReNEWrzDtjGS17h 14 | 071mvVDmOnzUGz6AnZX1ayHP+vjRBvFdS4kpstS6p9p/0WpaKxarqrHVVDgTXDt1 15 | ZL2HDWLywLAJngwl0WBMoMh5OhQAI814m1C4t1wabxuJbJdaxzBWQzSwA+ZuKJtw 16 | 6ycMBs2KWwZavtiE9TB2O0wGTqsCgYEA4cq/3D+UPC7FBc7XGRdXW+yj1jtMclKW 17 | IZv6XwsNZ2Fxs8s5XuDM6He4b8hL0cTuqZSxMHbnWbunstBr+TqVgMKbBxkUeq1n 18 | dgDtVAXDFbvAt1UzU27S6Yk1id1xyFBNijAaEG/X5Xpl6jKLzQOCYMEfNwaRiiWJ 19 | AtLVYFiqY+UCgYEA8I0dFdnuNkwYXi+Hh2sdTZuge479W0mbrqBnRMNx7unANfsx 20 | qSJsVqBmtQAQNHS3DvMJycCyJ9vjhU1WLnPkgnjN2XBLyEQ6n5REINCmFErMcOyc 21 | f+ukwl7FiUKidZT24HPcPiYd0WZjV2Tw39AP8eG3QGcwt+b1kmcOMNAEIw0CgYEA 22 | 1vsTaCJFAnRJdTk2RhaaP9T1PaHiLKuDdGatoVbFoVv/RX/GlRaXIvPycMQVpJnL 23 | 3zzK5Gkh56cria62T2s/M4SW1NBFVnTKIGnLjgeacpP8btI5d0P6nw7cIfLtRhtn 24 | aJ5aArJqrny2kqEjyMSBm5vlG6AubIlGhaC2NvDU8x0CgYBXR0Uq7gfItNJbJ7t5 25 | Ce+bkb87mjFFBm9hwoyrONwtw/1caKRUc3nciqZy0ZagGAUzVgfh1QErTayJCrKt 26 | K6vD76dj6KKLP7esFtB3jy/2qXphwxszHnGhvgXcY0jbrXlW1P2Ioh5kpvaTXAo2 27 | 4Je40bCxEYESnTrfWH9x4HyiRA== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /Server/keys/server.pkey: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC19WfJ32jmoqGt 3 | ozwFsMRvEiG/L5t/MB9TCLRPD62NXUy9QLa0mRkApMLDTL7n3vNIUz7bgKrgtzDU 4 | o7nMj172L2u0qWALfmp9zE59q2vNjAz0HnYenMMqiZJYMe/lfMUPBp7QLMu9LxEU 5 | HCpV3bo3lx1mUJy0RN8vo/udR8/+f5qOzgBAO2Br5ShFEqWV93rMrn4zRbkM4Yhh 6 | AcmZH/oDmEZFfX7W9MoLhM6eaNSTT23X5HdRMIjtgID/UjsNTBmFkOo3pJvr34kQ 7 | xgVla+sLYcl7geVITzOhVscZam+RH1miA8IA+tb61rJcGIF5ZDAiMQJ7BLtAYtxX 8 | 0LAYlyvxAgMBAAECggEBAJ0PQ9QrhEe3z2iqwq+CwjvBYeG9/TGO3yr0E3c+6CZi 9 | h0/AYTSIBd3ZWcfsIcjRWBtwx4v+hwDK/1o8LK9WfbhwfD6ICMBDETaeuI2e/sGA 10 | htncxUZtN8Uq1GhOSm1DAnGS3FiCoiYdT1eVbDGL3yYGyTi8klbEMAXWPOSozO81 11 | zwdycTkSceFVrxDg3lT7DWUWVFUNL5xtsg6O4CG2uj3qlGCoSa/6WvXIyG+uxDpA 12 | kxeZk9PRbU9S1qQTXPUsPH6+vU6Mh/Qtgp1j1979lngpzjkgqko108ftQcRfC+Rc 13 | ihH5iB/enNsFlj7QEgbSvsBJFpcPW12N14N+x9W5KMUCgYEA3QT9UD2ptlEDRZ3l 14 | rddru2LkIlTPCQiAES/jWt/2Q0g85d73f7XdKImrGrfXTrZCnhzCLMRs/a2WFLpU 15 | x2cPBi1dXc4LW7XKNe3w2pIUdNBvztQqqjZJLLnGpwQcsQC39IpT3SpItJsTxOCW 16 | GRrGxMs4stsw+0D9wfKQa3pySM8CgYEA0sHK9t8mW3eiJIOK5sib1qfCNzc23wnK 17 | sZCAhLktWjqTvVtK85e7McOmzKzlLkOqWrFu0bEFRDSv+hjuJqxyVSBjUaFSOedz 18 | mYxGBY4CfW5uFCVUZoPn59fOImbk0PHRE2D0s+9TEvB+BaIJoh+y27cp4Pn6k47j 19 | CXSKjBci7z8CgYEAimGaXMgO30i/3LGs4XhUSoHzRo+FBSneTWSHXBmHrb6n6mD8 20 | 42gxrTTs/ZCHbQ/ui+KDrNXbS8WIhzfW5vZe0MZMg/QC5overH9q4t69khoDNyNe 21 | By5wCGHUwDNx510E0EZflcTourOx3/2h3NwIAaUfvdRBSb76YpmF1NVYgr8CgYAE 22 | ZwnvWD/73ybi7mCDa6Ndu4liD+Q0iobSaLtWimu5G8okRXjuV7gQVQ7x7Zan/b/c 23 | PyKafMEoaJVYexbWdfm5b4DDnsQR85TFm1mHGi1RI3JnN1RpA85fWHG05ENZXgPJ 24 | DNhvvcsVdTKcuYLMAS+CSXQ1f+NNG8oAU4/lV0JijwKBgF6spjD7mF+XiidfmXJu 25 | uTBIVCQd6UUf0P94nkY2kw51BafO2AOn0iTiaKOHdh307zBYhHgY6Z8PV3/iIt08 26 | lNyQiSo5f5Nx4vmi5y6N4w9ZHIQGg+pv6KVwyTUpKZEiIBKlJJLVV4LqBST2rY9o 27 | 0oV+Xx3TFe40WiJHVUT+9Kqz 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /Server/messencrypt.py: -------------------------------------------------------------------------------- 1 | from cryptography.fernet import Fernet 2 | from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC 3 | from cryptography.hazmat.primitives import hashes 4 | from cryptography.hazmat.backends import default_backend 5 | import base64 6 | import os 7 | import json 8 | 9 | def encrypt(plaintext, key): 10 | #Create a Fernet block and use it for encryption. 11 | fblock = Fernet(key) 12 | return fblock.encrypt(bytes(plaintext)) 13 | 14 | def decrypt(ciphertext, key): 15 | fblock = Fernet(key) 16 | return fblock.decrypt(bytes(ciphertext)) 17 | 18 | def generateFernetKey(): 19 | #Generate the fernet key and put it through a KDF (for AES cipher compability) 20 | salt = os.urandom(16) 21 | fernet_key = Fernet.generate_key() 22 | kdf = PBKDF2HMAC( 23 | algorithm=hashes.SHA256(), 24 | length=32, 25 | salt=salt, 26 | iterations=100000, 27 | backend=default_backend() 28 | ) 29 | fernet_key = kdf.derive(fernet_key) 30 | return fernet_key 31 | -------------------------------------------------------------------------------- /Server/mk_cert_files.py: -------------------------------------------------------------------------------- 1 | from OpenSSL import crypto 2 | from createCert import * 3 | 4 | def createCA(): 5 | cakey = createKeyPair(TYPE_RSA, 2048) 6 | careq = createCertRequest(cakey, CN='Certificate Authority') 7 | cacert = createCertificate(careq, (careq, cakey), 0, (0, 60*60*24*365*5)) # five years 8 | open('CA.pkey', 'w').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, cakey)) 9 | open('CA.cert', 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, cacert)) 10 | 11 | return cakey, cacert 12 | 13 | def createRequest(origin): 14 | pkey = createKeyPair(TYPE_RSA, 2048) 15 | req = createCertRequest(pkey, CN=origin) 16 | open('keys/%s.pkey' % (origin,), 'w').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)) 17 | return req 18 | 19 | def signCertificates(req, cacert, cakey): 20 | cert = createCertificate(req, (cacert, cakey), 1, (0, 60*60*24)) #24 hours. 21 | return cert -------------------------------------------------------------------------------- /Server/sign.py: -------------------------------------------------------------------------------- 1 | from cryptography.hazmat.backends import default_backend 2 | from cryptography.hazmat.primitives import hashes 3 | from cryptography.hazmat.primitives.asymmetric import ec 4 | from cryptography.hazmat.primitives import serialization 5 | 6 | import json, sys, base64 7 | import getpass 8 | # -*- coding: utf-8 -*- 9 | 10 | def createSignature(pathToPrivateKey, prehashed): 11 | file = open(pathToPrivateKey, "r") 12 | serialized_private = ''.join(file.readlines()) 13 | 14 | password = getpass.getpass("Enter your password>") 15 | try: 16 | private_key = serialization.load_pem_private_key( 17 | serialized_private, 18 | password=bytes(password), 19 | backend=default_backend() 20 | ) 21 | signature = private_key.sign( 22 | base64.b64decode(prehashed), 23 | ec.ECDSA(hashes.SHA256()) 24 | ) 25 | return signature 26 | 27 | except: 28 | print "Wrong password." 29 | return createSignature(pathToPrivateKey, prehashed) 30 | 31 | def verifySignature(serialized_public, signature, prehashed): 32 | public_key = serialization.load_pem_public_key( 33 | serialized_public, 34 | backend=default_backend() 35 | ) 36 | #Verify raises an exception if not valid. 37 | try: 38 | public_key.verify(signature, prehashed, ec.ECDSA(hashes.SHA256())) 39 | return True 40 | except: 41 | return False 42 | 43 | def createSerializedKeys(password): 44 | private_key = ec.generate_private_key(ec.SECP384R1(), default_backend()) 45 | serialized_private = private_key.private_bytes( 46 | encoding=serialization.Encoding.PEM, 47 | format=serialization.PrivateFormat.PKCS8, 48 | encryption_algorithm=serialization.BestAvailableEncryption(bytes(password)) 49 | ) 50 | file = open("private.pem", "w") 51 | file.write(serialized_private) 52 | file.close() 53 | public_key = private_key.public_key() 54 | serialized_public = public_key.public_bytes( 55 | encoding=serialization.Encoding.PEM, 56 | format=serialization.PublicFormat.SubjectPublicKeyInfo 57 | ) 58 | file = open("public.pem", "w") 59 | file.write(serialized_public) 60 | file.close() 61 | 62 | def signMessage(s, username, password): 63 | unparsed = s.recv(4096) 64 | data = json.loads(unparsed) 65 | #Could be wrapped in a try-catch but whatever 66 | if not data : 67 | print '\nDisconnected from chat server' 68 | sys.exit() 69 | else : 70 | #First step in DH, is client 71 | createSerializedKeys(password) 72 | signature = createSignature("private.pem", bytes(data["message"])) 73 | public_key = getPublicKey("public.pem") 74 | json_str = {"username": username, "public_key": public_key, "signature": base64.b64encode(signature)} 75 | s.send(json.dumps(json_str)) 76 | 77 | def getPublicKey(pathToPublicKey): 78 | file = open(pathToPublicKey, "r") 79 | serialized_public = ''.join(file.readlines()) 80 | return serialized_public --------------------------------------------------------------------------------