2
2
3
3
import sys
4
4
5
- from pyelliptic import ecc
6
- from pyelliptic import Cipher
7
- from pyelliptic .hash import hmac_sha256
8
5
from hashlib import sha256
6
+ from binascii import hexlify , unhexlify
7
+ import hmac
8
+ import random
9
+
10
+ from cryptography .hazmat .primitives .ciphers import Cipher , modes , algorithms
11
+ from cryptography .hazmat .primitives .ciphers .algorithms import AES
12
+ from cryptography .hazmat .primitives .ciphers .modes import CTR
13
+ from cryptography .hazmat .backends import default_backend
14
+ # http://cryptography.io
15
+
16
+ from pyelliptic import ecc
17
+
18
+ class MyEx (Exception ): pass
19
+
20
+ def hmac_sha256 (k , m ):
21
+ return hmac .new (k , m , sha256 ).digest ()
22
+
23
+
24
+
25
+
9
26
10
- hexlify = ecc .hexlify
11
- unhexlify = ecc .unhexlify
12
27
13
28
## pyelliptic doesn't support compressed pubkey representations
14
29
## so we have to add some code...
22
37
ctypes .c_void_p , ctypes .c_void_p , ctypes .c_void_p , ctypes .c_int ,
23
38
ctypes .c_void_p ]
24
39
40
+ def ecc_ecdh_key (sec , pub ):
41
+ assert isinstance (sec , ecc .ECC )
42
+ if isinstance (pub , ecc .ECC ):
43
+ pub = pub .get_pubkey ()
44
+ #return sec.get_ecdh_key(pub)
45
+
46
+ pubkey_x , pubkey_y = ecc .ECC ._decode_pubkey (pub , 'binary' )
47
+
48
+ other_key = other_pub_key_x = other_pub_key_y = other_pub_key = None
49
+ own_priv_key = res = res_x = res_y = None
50
+ try :
51
+ other_key = OpenSSL .EC_KEY_new_by_curve_name (sec .curve )
52
+ if other_key == 0 :
53
+ raise Exception ("[OpenSSL] EC_KEY_new_by_curve_name FAIL ... " + OpenSSL .get_error ())
54
+
55
+ other_pub_key_x = OpenSSL .BN_bin2bn (pubkey_x , len (pubkey_x ), 0 )
56
+ other_pub_key_y = OpenSSL .BN_bin2bn (pubkey_y , len (pubkey_y ), 0 )
57
+
58
+ other_group = OpenSSL .EC_KEY_get0_group (other_key )
59
+ other_pub_key = OpenSSL .EC_POINT_new (other_group )
60
+ if (other_pub_key == None ):
61
+ raise Exception ("[OpenSSl] EC_POINT_new FAIL ... " + OpenSSL .get_error ())
62
+
63
+ if (OpenSSL .EC_POINT_set_affine_coordinates_GFp (other_group ,
64
+ other_pub_key ,
65
+ other_pub_key_x ,
66
+ other_pub_key_y ,
67
+ 0 )) == 0 :
68
+ raise Exception (
69
+ "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ..." + OpenSSL .get_error ())
70
+
71
+ own_priv_key = OpenSSL .BN_bin2bn (sec .privkey , len (sec .privkey ), 0 )
72
+
73
+ res = OpenSSL .EC_POINT_new (other_group )
74
+ if (OpenSSL .EC_POINT_mul (other_group , res , 0 , other_pub_key , own_priv_key , 0 )) == 0 :
75
+ raise Exception (
76
+ "[OpenSSL] EC_POINT_mul FAIL ..." + OpenSSL .get_error ())
77
+
78
+ res_x = OpenSSL .BN_new ()
79
+ res_y = OpenSSL .BN_new ()
80
+
81
+ if (OpenSSL .EC_POINT_get_affine_coordinates_GFp (other_group , res ,
82
+ res_x ,
83
+ res_y , 0
84
+ )) == 0 :
85
+ raise Exception (
86
+ "[OpenSSL] EC_POINT_get_affine_coordinates_GFp FAIL ... " + OpenSSL .get_error ())
87
+
88
+ resx = OpenSSL .malloc (0 , OpenSSL .BN_num_bytes (res_x ))
89
+ resy = OpenSSL .malloc (0 , OpenSSL .BN_num_bytes (res_y ))
90
+
91
+ OpenSSL .BN_bn2bin (res_x , resx )
92
+ resx = resx .raw
93
+ OpenSSL .BN_bn2bin (res_y , resy )
94
+ resy = resy .raw
95
+
96
+ return resx , resy
97
+
98
+ finally :
99
+ if other_key : OpenSSL .EC_KEY_free (other_key )
100
+ if other_pub_key_x : OpenSSL .BN_free (other_pub_key_x )
101
+ if other_pub_key_y : OpenSSL .BN_free (other_pub_key_y )
102
+ if other_pub_key : OpenSSL .EC_POINT_free (other_pub_key )
103
+ if own_priv_key : OpenSSL .BN_free (own_priv_key )
104
+ if res : OpenSSL .EC_POINT_free (res )
105
+ if res_x : OpenSSL .BN_free (res_x )
106
+ if res_y : OpenSSL .BN_free (res_y )
107
+
25
108
def get_pos_y_for_x (pubkey_x , yneg = 0 ):
26
109
key = pub_key = pub_key_x = pub_key_y = None
27
110
try :
@@ -62,6 +145,34 @@ class Onion(object):
62
145
MSG_LEN = 128
63
146
ZEROES = b"\x00 " * (HMAC_LEN + PKEY_LEN + MSG_LEN )
64
147
148
+ @staticmethod
149
+ def tweak_sha (sha , d ):
150
+ sha = sha .copy ()
151
+ sha .update (d )
152
+ return sha .digest ()
153
+
154
+ @classmethod
155
+ def get_ecdh_secrets (cls , sec , pkey_x , pkey_y ):
156
+ pkey = unhexlify ('04' ) + pkey_x + pkey_y
157
+ tmp_key = ecc .ECC (curve = 'secp256k1' , pubkey = pkey )
158
+ sec_x , sec_y = ecc_ecdh_key (sec , tmp_key )
159
+
160
+ b = '\x02 ' if ord (sec_y [- 1 ]) % 2 == 0 else '\x03 '
161
+ sec = sha256 (sha256 (b + sec_x ).digest ())
162
+
163
+ enckey = cls .tweak_sha (sec , b'\x00 ' )[:16 ]
164
+ hmac = cls .tweak_sha (sec , b'\x01 ' )
165
+ iv = cls .tweak_sha (sec , b'\x02 ' )[:16 ]
166
+ pad_iv = cls .tweak_sha (sec , b'\x03 ' )[:16 ]
167
+
168
+ return enckey , hmac , iv , pad_iv
169
+
170
+ def enc_pad (self , enckey , pad_iv ):
171
+ aes = Cipher (AES (enckey ), CTR (pad_iv ),
172
+ default_backend ()).encryptor ()
173
+ return aes .update (self .ZEROES )
174
+
175
+ class OnionDecrypt (Onion ):
65
176
def __init__ (self , onion , my_ecc ):
66
177
self .my_ecc = my_ecc
67
178
@@ -77,47 +188,104 @@ def __init__(self, onion, my_ecc):
77
188
self .get_secrets ()
78
189
79
190
def decrypt (self ):
80
- ctx = Cipher (self .enckey , self .pad_iv , 1 , ciphername = 'aes-128-ctr' )
81
- pad = ctx .ciphering (self .ZEROES )
191
+ pad = self .enc_pad (self .enckey , self .pad_iv )
82
192
83
- ctx = Cipher (self .enckey , self .iv , 0 , ciphername = 'aes-128-ctr' )
84
- self .fwd = pad + ctx .ciphering (self .onion [:self .fwd_end ])
85
- self .msg = ctx .ciphering (self .onion [self .fwd_end :self .msg_end ])
86
-
87
- def tweak_sha (self , sha , d ):
88
- sha = sha .copy ()
89
- sha .update (d )
90
- return sha .digest ()
193
+ aes = Cipher (AES (self .enckey ), CTR (self .iv ),
194
+ default_backend ()).decryptor ()
195
+ self .fwd = pad + aes .update (self .onion [:self .fwd_end ])
196
+ self .msg = aes .update (self .onion [self .fwd_end :self .msg_end ])
91
197
92
198
def get_secrets (self ):
93
199
pkey_x = self .pkey
94
- pkey_y = get_pos_y_for_x (pkey_x )
95
- pkey = unhexlify ('04' ) + pkey_x + pkey_y
96
- tmp_key = ecc .ECC (curve = 'secp256k1' , pubkey = pkey )
97
- sec_x = self .my_ecc .get_ecdh_key (tmp_key .get_pubkey ())
98
- sec_1 = sha256 (sha256 (b"\x02 " + sec_x ).digest ())
99
- sec_2 = sha256 (sha256 (b"\x03 " + sec_x ).digest ())
100
-
101
- sec = None
102
- if self .check_hmac (self .tweak_sha (sec_1 , b'\x01 ' )):
103
- sec = sec_1
104
- if self .check_hmac (self .tweak_sha (sec_2 , b'\x01 ' )):
105
- sec = sec_2
106
- if sec is None :
200
+ pkey_y = get_pos_y_for_x (pkey_x ) # always positive by design
201
+ enckey , hmac , iv , pad_iv = self .get_ecdh_secrets (self .my_ecc , pkey_x , pkey_y )
202
+ if not self .check_hmac (hmac ):
107
203
raise Exception ("HMAC did not verify" )
108
-
109
- self .enckey = self .tweak_sha (sec , b'\x00 ' )[:16 ]
110
- self .iv = self .tweak_sha (sec , b'\x02 ' )[:16 ]
111
- self .pad_iv = self .tweak_sha (sec , b'\x03 ' )[:16 ]
204
+ self .enckey = enckey
205
+ self .iv = iv
206
+ self .pad_iv = pad_iv
112
207
113
208
def check_hmac (self , hmac_key ):
114
209
calc = hmac_sha256 (hmac_key , self .onion [:- self .HMAC_LEN ])
115
210
return calc == self .hmac
116
211
117
- if __name__ == "__main__" :
212
+ class OnionEncrypt (Onion ):
213
+ def __init__ (self , msgs , pubkeys ):
214
+ assert len (msgs ) == len (pubkeys )
215
+ assert 0 < len (msgs ) <= 20
216
+ assert all ( len (m ) <= self .MSG_LEN for m in msgs )
217
+
218
+ msgs = [m + "\0 " * (self .MSG_LEN - len (m )) for m in msgs ]
219
+ pubkeys = [ecc .ECC (pubkey = pk , curve = 'secp256k1' ) for pk in pubkeys ]
220
+ n = len (msgs )
221
+
222
+ tmpkeys = []
223
+ tmppubkeys = []
224
+ for i in range (n ):
225
+ while True :
226
+ t = ecc .ECC (curve = 'secp256k1' )
227
+ if ord (t .pubkey_y [- 1 ]) % 2 == 0 :
228
+ break
229
+ # or do the math to "flip" the secret key and pub key
230
+ tmpkeys .append (t )
231
+ tmppubkeys .append (t .pubkey_x )
232
+
233
+ enckeys , hmacs , ivs , pad_ivs = zip (* [self .get_ecdh_secrets (tmpkey , pkey .pubkey_x , pkey .pubkey_y )
234
+ for tmpkey , pkey in zip (tmpkeys , pubkeys )])
235
+
236
+ # padding takes the form:
237
+ # E_(n-1)(0000s)
238
+ # D_(n-1)(
239
+ # E(n-2)(0000s)
240
+ # D(n-2)(
241
+ # ...
242
+ # )
243
+ # )
244
+
245
+ padding = ""
246
+ for i in range (n - 1 ):
247
+ pad = self .enc_pad (enckeys [i ], pad_ivs [i ])
248
+ aes = Cipher (AES (enckeys [i ]), CTR (ivs [i ]),
249
+ default_backend ()).decryptor ()
250
+ padding = pad + aes .update (padding )
251
+
252
+ if n < 20 :
253
+ padding += str (bytearray (random .getrandbits (8 )
254
+ for _ in range (len (self .ZEROES ) * (20 - n ))))
255
+
256
+ # to encrypt the message we need to bump the counter past all
257
+ # the padding, then just encrypt the final message
258
+ aes = Cipher (AES (enckeys [- 1 ]), CTR (ivs [- 1 ]),
259
+ default_backend ()).encryptor ()
260
+ aes .update (padding ) # don't care about cyphertext
261
+ msgenc = aes .update (msgs [- 1 ])
262
+
263
+ msgenc = padding + msgenc + tmppubkeys [- 1 ]
264
+ del padding
265
+ msgenc += hmac_sha256 (hmacs [- 1 ], msgenc )
266
+
267
+ # *PHEW*
268
+ # now iterate
269
+
270
+ for i in reversed (range (n - 1 )):
271
+ # drop the padding this node will add
272
+ msgenc = msgenc [len (self .ZEROES ):]
273
+ # adding the msg
274
+ msgenc += msgs [i ]
275
+ # encrypt it
276
+ aes = Cipher (AES (enckeys [i ]), CTR (ivs [i ]),
277
+ default_backend ()).encryptor ()
278
+ msgenc = aes .update (msgenc )
279
+ # add the tmp key
280
+ msgenc += tmppubkeys [i ]
281
+ # add the hmac
282
+ msgenc += hmac_sha256 (hmacs [i ], msgenc )
283
+ self .onion = msgenc
284
+
285
+ def decode_from_file (f ):
118
286
keys = []
119
287
msg = ""
120
- for ln in sys . stdin .readlines ():
288
+ for ln in f .readlines ():
121
289
if ln .startswith (" * Keypair " ):
122
290
w = ln .strip ().split ()
123
291
idx = int (w [2 ].strip (":" ))
@@ -135,9 +303,27 @@ def check_hmac(self, hmac_key):
135
303
136
304
assert msg != ""
137
305
for k in keys :
138
- o = Onion (msg , k )
306
+ o = OnionDecrypt (msg , k )
139
307
o .decrypt ()
140
308
print o .msg
141
309
msg = o .fwd
142
-
143
310
print "done"
311
+
312
+ if __name__ == "__main__" :
313
+ if len (sys .argv ) > 1 and sys .argv [1 ] == "generate" :
314
+ if len (sys .argv ) == 3 :
315
+ n = int (sys .argv [2 ])
316
+ else :
317
+ n = 20
318
+ servers = [ecc .ECC (curve = 'secp256k1' ) for _ in range (n )]
319
+ server_pubs = [s .get_pubkey () for s in servers ]
320
+ msgs = ["Howzit %d..." % (i ,) for i in range (n )]
321
+
322
+ o = OnionEncrypt (msgs , server_pubs )
323
+
324
+ for i , s in enumerate (servers ):
325
+ print " * Keypair %d: %s %s" % (
326
+ i , hexlify (s .privkey ), hexlify (s .get_pubkey ()))
327
+ print " * Message: %s" % (hexlify (o .onion ))
328
+ else :
329
+ decode_from_file (sys .stdin )
0 commit comments