-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdouble_ratchet_offline.py
203 lines (159 loc) · 7.41 KB
/
double_ratchet_offline.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
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey, Ed25519PrivateKey
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.backends import default_backend
from Cryptodome.Cipher import AES
def b64(msg):
# base64 encoding helper function
return base64.encodebytes(msg).decode('utf-8').strip()
def hkdf(inp, length):
# use HKDF on an input to derive a key
hkdf = HKDF(algorithm=hashes.SHA256(), length=length, salt=b'',
info=b'', backend=default_backend())
return hkdf.derive(inp)
class SymmRatchet(object):
def __init__(self, key):
self.state = key
def next(self, inp=b''):
# turn the ratchet, changing the state and yielding a new key and IV
print("state", self.state)
print("inp", inp)
output = hkdf(self.state + inp, 80)
print("output", output)
self.state = output[:32]
outkey, iv = output[32:64], output[64:]
return outkey, iv
class Bob(object):
def __init__(self):
# generate Bob's keys
self.IKb = X25519PrivateKey.generate()
self.SPKb = X25519PrivateKey.generate()
self.OPKb = X25519PrivateKey.generate()
self.DHratchet = X25519PrivateKey.generate()
def x3dh(self, alice):
# perform the 4 Diffie Hellman exchanges (X3DH)
dh1 = self.SPKb.exchange(alice.IKa.public_key())
dh2 = self.IKb.exchange(alice.EKa.public_key())
dh3 = self.SPKb.exchange(alice.EKa.public_key())
dh4 = self.OPKb.exchange(alice.EKa.public_key())
# the shared key is KDF(DH1||DH2||DH3||DH4)
self.sk = hkdf(dh1 + dh2 + dh3 + dh4, 32)
print(f"sk:: {self.sk} \n\n")
print('[Bob]\tShared key:', b64(self.sk))
def init_ratchets(self):
# initialise the root chain with the shared key
self.root_ratchet = SymmRatchet(self.sk)
# initialise the sending and recving chains
self.recv_ratchet = SymmRatchet(self.root_ratchet.next()[0])
self.send_ratchet = SymmRatchet(self.root_ratchet.next()[0])
def dh_ratchet(self, alice_public):
# perform a DH ratchet rotation using Alice's public key
dh_recv = self.DHratchet.exchange(alice_public)
shared_recv = self.root_ratchet.next(dh_recv)[0]
# use Alice's public and our old private key
# to get a new recv ratchet
self.recv_ratchet = SymmRatchet(shared_recv)
print('[Bob]\tRecv ratchet seed:', b64(shared_recv))
# generate a new key pair and send ratchet
# our new public key will be sent with the next message to Alice
self.DHratchet = X25519PrivateKey.generate()
dh_send = self.DHratchet.exchange(alice_public)
shared_send = self.root_ratchet.next(dh_send)[0]
self.send_ratchet = SymmRatchet(shared_send)
print('[Bob]\tSend ratchet seed:', b64(shared_send))
def send(self, alice, msg):
key, iv = self.send_ratchet.next()
cipher = AES.new(key, AES.MODE_CBC, iv).encrypt(pad(msg))
print('[Bob]\tSending ciphertext to Alice:', b64(cipher))
# send ciphertext and current DH public key
alice.recv(cipher, self.DHratchet.public_key())
def recv(self, cipher, alice_public_key):
# receive Alice's new public key and use it to perform a DH
self.dh_ratchet(alice_public_key)
key, iv = self.recv_ratchet.next()
# decrypt the message using the new recv ratchet
msg = unpad(AES.new(key, AES.MODE_CBC, iv).decrypt(cipher))
print('[Bob]\tDecrypted message:', msg)
class Alice(object):
def __init__(self):
# generate Alice's keys
self.IKa = X25519PrivateKey.generate()
self.EKa = X25519PrivateKey.generate()
self.DHratchet = None
def x3dh(self, bob):
# perform the 4 Diffie Hellman exchanges (X3DH)
dh1 = self.IKa.exchange(bob.SPKb.public_key())
dh2 = self.EKa.exchange(bob.IKb.public_key())
dh3 = self.EKa.exchange(bob.SPKb.public_key())
dh4 = self.EKa.exchange(bob.OPKb.public_key())
# the shared key is KDF(DH1||DH2||DH3||DH4)
self.sk = hkdf(dh1 + dh2 + dh3 + dh4, 32)
print('[Alice]\tShared key:', b64(self.sk))
def init_ratchets(self):
# initialise the root chain with the shared key
self.root_ratchet = SymmRatchet(self.sk)
# initialise the sending and recving chains
self.send_ratchet = SymmRatchet(self.root_ratchet.next()[0])
self.recv_ratchet = SymmRatchet(self.root_ratchet.next()[0])
def dh_ratchet(self, bob_public):
# perform a DH ratchet rotation using Bob's public key
if self.DHratchet is not None:
# the first time we don't have a DH ratchet yet
dh_recv = self.DHratchet.exchange(bob_public)
shared_recv = self.root_ratchet.next(dh_recv)[0]
# use Bob's public and our old private key
# to get a new recv ratchet
self.recv_ratchet = SymmRatchet(shared_recv)
print('[Alice]\tRecv ratchet seed:', b64(shared_recv))
# generate a new key pair and send ratchet
# our new public key will be sent with the next message to Bob
self.DHratchet = X25519PrivateKey.generate()
dh_send = self.DHratchet.exchange(bob_public)
shared_send = self.root_ratchet.next(dh_send)[0]
self.send_ratchet = SymmRatchet(shared_send)
print('[Alice]\tSend ratchet seed:', b64(shared_send))
def send(self, bob, msg):
key, iv = self.send_ratchet.next()
cipher = AES.new(key, AES.MODE_CBC, iv).encrypt(pad(msg))
print('[Alice]\tSending ciphertext to Bob:', b64(cipher))
# send ciphertext and current DH public key
bob.recv(cipher, self.DHratchet.public_key())
def recv(self, cipher, bob_public_key):
# receive Bob's new public key and use it to perform a DH
self.dh_ratchet(bob_public_key)
key, iv = self.recv_ratchet.next()
# decrypt the message using the new recv ratchet
msg = unpad(AES.new(key, AES.MODE_CBC, iv).decrypt(cipher))
print('[Alice]\tDecrypted message:', msg)
def pad(msg):
# pkcs7 padding
num = 16 - (len(msg) % 16)
return msg + bytes([num] * num)
def unpad(msg):
# remove pkcs7 padding
return msg[:-msg[-1]]
alice = Alice()
bob = Bob()
# Alice performs an X3DH while Bob is offline, using his uploaded keys
alice.x3dh(bob)
# Bob comes online and performs an X3DH using Alice's public keys
bob.x3dh(alice)
# Initialize their symmetric ratchets
alice.init_ratchets()
bob.init_ratchets()
# Initialise Alice's sending ratchet with Bob's public key
alice.dh_ratchet(bob.DHratchet.public_key())
# Alice sends Bob a message and her new DH ratchet public key
alice.send(bob, b'Hello Bob!')
# Bob uses that information to sync with Alice and send her a message
bob.send(alice, b'Hello to you too, Alice!')
# # Alice sends Bob a message and her new DH ratchet public key
# alice.send(bob, b'asfd Bob!')
# # Bob uses that information to sync with Alice and send her a message
# bob.send(alice, b'Heasdfo, Alice!')
# # Alice sends Bob a message and her new DH ratchet public key
# alice.send(bob, b'Hasdfllo Bob!')
# # Bob uses that information to sync with Alice and send her a message
# bob.send(alice, b'Hello asdfasd, Alice!')