/
null_proto.py
206 lines (168 loc) · 6.5 KB
/
null_proto.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
"""
The NULL Crypto Protocol
This is a simple (and probably not very robust) ECDHE + AES protocol using ECDSA for authentication. Please don't use
this for anything important!!
The server has a list of trusted public keys, the client the public key of the server it trusts.
When connecting a new session, the client uses an ephemeral ECDH key signed by the client's long-term ECDSA key.
The server enforces that only one ephemeral ECDH key can be in use for each client key.
Handshake:
* Client sends it's pubkey and epehemeral pubkey, and sign the ephemeral pubkey and sends that.
* The client computes a shared secret to use as an AES128 key between the ephemeral ECDH key and the
server's public key.
* If the server trusts the client's ECDSA key it computes the same shared secret using ECDH
* The server encrypts a random string using the shared key and sends it to the client.
* The client must decrypt and sign the plain text version of the string.
* The server verifies this final exchange and trusts the client.
"""
import os
import binascii
import identity
import hashlib
import pyaes
import uuid
KDFS = [
"NULLAES128sha256str"
]
DEFAULT_KDF = "NULLAES128sha256str"
def aes_encrypt_str(key, plaintext):
"""
Encrypt a string with AES.
:param key:
:param plaintext:
:return:
"""
iv = os.urandom(16)
encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(key, iv))
ctext = encrypter.feed(plaintext)
ctext += encrypter.feed()
return binascii.hexlify(ctext), binascii.hexlify(iv)
def aes_decrypt_str(key, iv, ciphertext):
"""
Decrypt a string with AES
:param key:
:param iv:
:param ciphertext:
:return:
"""
iv = binascii.unhexlify(iv)
ciphertext = binascii.unhexlify(ciphertext)
decrypter = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(key, iv))
ptext = decrypter.feed(ciphertext)
ptext += decrypter.feed()
return ptext
def derive_key(kdf, shared):
"""
Derive our secret key
:param kdf:
:param shared: shared secret ecpoints
:return:
"""
assert kdf in KDFS
if kdf == "NULLAES128sha256str":
# 128bit AES key from the last part of the sha256 hash if the ecpoint string representation
keydata = hashlib.sha256(str(shared)).digest()[-16:]
return keydata
else:
raise KeyError("no such KDF " + kdf)
def server_handshake_begin(server_priv, message):
"""
Receive the handshake
:param server_priv: server private key
:param message: message data (decoded)
:return: secret aes key, client's pubkey, handshake response to send
"""
assert server_priv.curve.openssl_name == message["keytype"]
signature = message["signature"]
client_pub = identity.loadpubstr(message["pub"]) # client long term key
session_pub = identity.loadpubstr(message["session"]) # client session ecc key
verify_msg = message["session"] + message["kdf"] + message["keytype"]
assert identity.verify_string(client_pub, signature, verify_msg) is True
shared = identity.ecdh(server_priv, session_pub)
secret = derive_key(message["kdf"], shared)
challenge_plain = str(uuid.uuid4())
challenge, iv = aes_encrypt_str(secret, challenge_plain)
response = {"challenge": challenge, "iv": iv}
return secret, client_pub, response, challenge_plain
def client_handshake_begin(ecdsa_priv, ecdh_priv, server_pub, curve=identity.DEFAULT_KEYTYPE, kdf=DEFAULT_KDF):
"""
Start the handshake
:param ecdsa_priv: long term signing privkey
:param ecdh_priv: temporary session ecc privkey
:param server_pub: server long term pub key
:param curve: EC Curve name
:param kdf: Key derivation mechanism
:return: secret aes key, handshake message to send
"""
session_pub_str = ecdh_priv.get_verifying_key().to_pem()
# sign our session key
signature = identity.sign_string(ecdsa_priv, session_pub_str + kdf + curve.openssl_name)
message = {
"pub": ecdsa_priv.get_verifying_key().to_pem(),
"session": session_pub_str,
"signature": signature,
"keytype": curve.openssl_name,
"kdf": kdf,
}
shared = identity.ecdh(ecdh_priv, server_pub)
secret = derive_key(kdf, shared)
return secret, message
def client_handshake_finish(client_priv, secret, challenge):
"""
Sign the challenge from the server's handshake response
:param client_priv:
:param secret:
:param challenge:
:return:
"""
pubkeyhash = identity.pubkeyhash(client_priv.get_verifying_key())
plaintext = aes_decrypt_str(secret, challenge["iv"], challenge["challenge"])
signature = identity.sign_string(client_priv, plaintext)
return {"signature": signature,
"fingerprint": pubkeyhash
}
def server_handshake_finish(client_pub, challenge, response):
"""
Verify the client processed our handshake correctly
:param client_pub: client's long term public key
:param challenge: challenge plaintext
:param response: message containing a signature of the plaintext and hash of the singer pubkey
:return:
"""
assert "fingerprint" in response
pubkeyhash = hashlib.sha512(client_pub.to_der()).hexdigest()
assert pubkeyhash == response["fingerprint"]
assert identity.verify_string(client_pub, response["signature"], challenge)
return {"status": "complete"}
def send_data(signer, sessionkey, data):
"""
Encrypt some data and sign it
:param signer: signing key
:param sessionkey: encryption key
:param data: message (expected string)
:return: dict containing fingerprint, signature, iv and ciphertext
"""
ctext, iv = aes_encrypt_str(sessionkey, str(data))
signature = identity.sign_string(signer, ctext)
pubkeyhash = hashlib.sha512(signer.get_verifying_key().to_der()).hexdigest()
return {
"fingerprint": pubkeyhash,
"signature": signature,
"iv": iv,
"ciphertext": ctext
}
def receive_data(pubkey, sessionkey, data):
"""
Verify and Decrypt a message
:param pubkey: the sender's signing key
:param sessionkey: the encryption key
:param data: (string)
:return: the plain text if the message verifies
"""
pubkeyhash = hashlib.sha512(pubkey.to_der()).hexdigest()
assert pubkeyhash == data["fingerprint"]
assert identity.verify_string(pubkey,
data["signature"],
data["ciphertext"])
return aes_decrypt_str(sessionkey,
data["iv"],
data["ciphertext"])