This repository was archived by the owner on Dec 29, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 758
/
Copy patheidtools.py
407 lines (336 loc) · 12.6 KB
/
eidtools.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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
#!/usr/bin/python
"""A few utilities for working with the cryptography around ephemeral ids.
In particular, it can:
1. compute ephemeral ids given an Identity Key, a scaler and a timestamp;
2. compute the identity key from the Curve25519 shared secret and the public
keys;
3. compute all the steps needed to register a beacon starting from the service
public key;
4. compute the Curve25519 key pair starting from a source binary string;
5. compute the shared secret between two Curve25519 key pairs.
It requires pycrypto and hkdf. The easiest way of using this is by doing:
(sudo) pip install pycrypto hkdf
"""
import base64
import hashlib
import random
import sys
import time
from Crypto.Cipher import AES
from Crypto.Util import number
import hkdf
def Usage():
"""Print the usage and exits."""
print """\
Usage:
python eidtools.py <command> <arguments>
Usage for command "registration":
python eidtools.py registration <public_key> <scaler> <beacon_time_seconds>
Given the 32-bytes service public key, and a set of beacon parameters,
generates a random key pair for the beacon, computes the shared secret, the
identity key, and the first EID.
Usage for command "beacon":
python eidtools.py beacon <ik> <scaler> <beacon_initial_time_seconds> \
<service_initial_time_seconds>
Given the identity key, the beacon rotation exponent, and the times of
registration for the beacon and the service, this computes the eid that the
beacon is currently broadcasting.
Usage for command "keygen":
python eidtools.py keygen <source>
Generates curve25519 keypair from <source>. It must have 32-bytes.
Usage for command "shared":
python eidtools.py shared <private> <public>
Generates the shared secret between the owner of <private> and the
owner of the private key associate to <public>.
Usage for command "ik":
python eidtools.py ik <shared> <service_public> <beacon_public>
Generates an identity key from a shared 32-bytes curve25519 secret, and the
public keys of the service and the beacon.
Usage for command "eid":
python eidtools.py eid <ik> <scaler> <beacon_time_seconds>
Generates an EID from the given 16-bytes identity key, the scaler and the
beacon time in seconds.
Binary arguments are interpreted depending on their first byte:
a: the rest of the string is interpreted as ASCII;
h: the rest of the string is interpreted as bytes in hex;
b: the rest of the string is interpreted as base64.
d: the rest of the string is interpreted as decimal (in little endian).
Integer inputs can be written as decimal, or as hex as in 0x1234.
"""
sys.exit(1)
def ToHex(data):
"""Return a string representing data in hexadecimal format."""
s = ""
for c in data:
s += ("%02x" % ord(c))
return s
def PrintSeconds(seconds):
"""Return a string representing the given time in seconds."""
orig = seconds
minutes = seconds // 60
seconds %= 60
hours = minutes // 60
minutes %= 60
days = hours // 24
hours %= 24
s = ""
if days > 0:
s += "%dd " % days
if hours > 0:
s += "%02dh " % hours
if minutes > 0:
s += "%02dm " % minutes
s += "%02ds" % seconds
s += " (%ds)" % orig
return s
def PrintBinary(desc, data):
"""Print the binary data in data in several formats."""
s = desc + ":\n"
s += " ASCII: "
if all("\x20" <= c < "\x80" for c in data):
s += data + ""
else:
s += "[unprintable]"
s += "\n"
s += " Hex: " + ToHex(data) + "\n"
s += " Base64: " + base64.b64encode(data) + "\n"
s += " Int: " + str(FromBinary(data)) + "\n"
for i, c in enumerate(data):
if i % 8 == 0:
if i != 0:
s += "\n"
s += " "
s += ("(byte) 0x%02x, " % ord(c))
s += "\n"
print s
def GetBinary(data, required_length=None):
"""Read binary data from a string using the format explained in the usage."""
ret = ""
if data[0] == "a":
ret = data[1:]
elif data[0] == "h":
ret = data[1:].decode("hex")
elif data[0] == "b":
ret = base64.b64decode(data[1:])
elif data[0] == "d" and required_length is not None:
ret = ToBinary(long(data[1:]), required_length)
else:
print "Invalid format for binary data."
Usage()
if required_length is not None and len(ret) != required_length:
print "Binary data must have %d bytes." % required_length
sys.exit(1)
return ret
def ToBinary(x, length):
"""Change x to a little-endian binary string of given length."""
# We assume that long_to_bytes() convert to a big-endian string; since we need
# a little-endian one, we reverse it.
ret = number.long_to_bytes(x)[::-1]
if len(ret) < length:
ret += "\x00" * (length - len(ret))
return ret
def FromBinary(x):
"""Return an integer corresponding to the little-endian binary string x."""
# We assume that bytes_to_long() convert from a big-endian string; since we
# have a little-endian one, we reverse it.
return number.bytes_to_long(x[::-1])
class Curve25519(object):
"""Implementation of Curve25519 using big integers."""
P = 2 ** 255 - 19
BASE_POINT = "\x09" + ("\x00" * 31)
@staticmethod
def ToPrivate(source):
"""Translate a generic 32-bytes string into a private key."""
return (
chr(ord(source[0]) & 248) +
source[1:31] +
chr((ord(source[31]) & 127) | 64)
)
@staticmethod
def ScalarMult(n, q=BASE_POINT):
"""Return the point nq."""
n = FromBinary(Curve25519.ToPrivate(n))
# The reference implementation ignores the most significative bit.
q = FromBinary(q) % (2**255)
nq = Curve25519._Multiple(n, q)
nq = (nq.x * number.inverse(nq.z, Curve25519.P)) % Curve25519.P
ret = ToBinary(nq, 32)
return ret
@staticmethod
def _Multiple(n, q):
"""Inner version of scalarMult, accepts big integers instead of strings."""
nq = Curve25519.Montgomery(1, 0)
nqpq = Curve25519.Montgomery(q, 1)
for i in range(255, -1, -1):
if (n >> i) & 1:
nqpq, nq = Curve25519._Montgomery(nqpq, nq, q)
else:
nq, nqpq = Curve25519._Montgomery(nq, nqpq, q)
return nq
@staticmethod
def _Montgomery(q, p, qmp):
"""Given two points q and p, and q-p, return 2q and q+p."""
qprime = q.ToSumDiffComponents()
pprime = p.ToSumDiffComponents()
pprime = pprime.CrossMul(qprime)
qprime = qprime.ToSquareComponents()
qpp = pprime.ToSumDiffComponents().ToSquareComponents()
qpp.z *= qmp
qpq = qprime.ToMulDiffComponents()
t = qpq.z
qpq.z = (((qpq.z * 121665) + qprime.x) * t) % Curve25519.P
return qpq, qpp
class Montgomery(object):
"""A number represented as x/z."""
def __init__(self, x, z):
self.x = x % Curve25519.P
self.z = z % Curve25519.P
def CrossMul(self, other):
return Curve25519.Montgomery(self.x * other.z, self.z * other.x)
def ToSquareComponents(self):
return Curve25519.Montgomery(self.x ** 2, self.z ** 2)
def ToSumDiffComponents(self):
return Curve25519.Montgomery(self.x + self.z, self.z - self.x)
def ToMulDiffComponents(self):
return Curve25519.Montgomery(self.x * self.z, self.x - self.z)
def ToNumber(self):
return (self.x * number.inverse(self.z, Curve25519.P)) % Curve25519.P
def GetAndPrintIdentityKey(shared_secret,
service_public_key, beacon_public_key):
"""Compute the identity key from a Curve25519 shared secret."""
salt = service_public_key + beacon_public_key
PrintBinary("Salt", salt)
prk = hkdf.hkdf_extract(salt, shared_secret, hash=hashlib.sha256)
PrintBinary("Prk (extracted bytes)", prk)
ik = hkdf.hkdf_expand(prk, "", 32, hash=hashlib.sha256)[:16]
PrintBinary("Identity key", ik)
return ik
def GetAndPrintEid(ik, scaler, beacon_time_seconds):
"""Return the EID generated by the given parameters."""
tkdata = (
"\x00" * 11 +
"\xFF" +
"\x00" * 2 +
chr((beacon_time_seconds / (2 ** 24)) % 256) +
chr((beacon_time_seconds / (2 ** 16)) % 256))
PrintBinary("Temporary Key data", tkdata)
tk = AES.new(ik, AES.MODE_ECB).encrypt(tkdata)
PrintBinary("Temporary Key", tk)
beacon_time_seconds = (beacon_time_seconds // 2 ** scaler) * (2 ** scaler)
eiddata = (
"\x00" * 11 +
chr(scaler) +
chr((beacon_time_seconds / (2 ** 24)) % 256) +
chr((beacon_time_seconds / (2 ** 16)) % 256) +
chr((beacon_time_seconds / (2 ** 8)) % 256) +
chr((beacon_time_seconds / (2 ** 0)) % 256))
PrintBinary("Ephemeral Id data", eiddata)
eid = AES.new(tk, AES.MODE_ECB).encrypt(eiddata)[:8]
PrintBinary("Ephemeral Id", eid)
return eid
def main(command, args):
if command == "registration":
if len(args) != 3:
Usage()
service_public_key = GetBinary(args[0], 32)
PrintBinary("Service public key", service_public_key)
beacon_source = "".join(chr(random.randint(0, 255)) for _ in range(32))
beacon_private_key = Curve25519.ToPrivate(beacon_source)
PrintBinary("Beacon private key (random)", beacon_private_key)
beacon_public_key = Curve25519.ScalarMult(beacon_private_key)
PrintBinary("Beacon public key", beacon_public_key)
shared = Curve25519.ScalarMult(beacon_private_key, service_public_key)
PrintBinary("Shared secret", shared)
if shared == "\x00" * 32:
print "NOTE: shared key is invalid, due to an invalid service public key."
ik = GetAndPrintIdentityKey(shared, service_public_key, beacon_public_key)
scaler = int(args[1], 0)
beacon_time_seconds = int(args[2], 0)
eid = GetAndPrintEid(ik, scaler, beacon_time_seconds)
# Print the request to register this beacon.
print
print "Registration line:"
print (" hpost /v1beta1/beacons:register "
"""{"advertisedId": {type:"EDDYSTONE", "id":"<beacon_id>"}, """
"""status:"ACTIVE", ephemeral_id_registration: """
"""{beacon_ecdh_public_key: "%s", """
"""service_ecdh_public_key: "%s", """
"""initial_clock_value: %s, """
"""rotation_period_exponent: %s, """
"""initial_eidr: "%s" }}""" % (
base64.b64encode(beacon_public_key),
base64.b64encode(service_public_key),
beacon_time_seconds,
scaler,
base64.b64encode(eid))
)
# Print the request to register this beacon.
print
print "Broadcast line:"
print (" python %s beacon b%s %s %s %s" % (
sys.argv[0],
base64.b64encode(ik),
scaler,
beacon_time_seconds,
int(time.time())))
elif command == "beacon":
if len(args) != 4:
Usage()
ik = GetBinary(args[0], 16)
PrintBinary("Identity key", ik)
scaler = int(args[1], 0)
beacon_initial_time_seconds = int(args[2], 0)
service_initial_time_seconds = int(args[3], 0)
beacon_time_seconds = beacon_initial_time_seconds + (
int(time.time()) - service_initial_time_seconds)
quantum = beacon_time_seconds // (2 ** scaler)
print "Beacon time in seconds: %s" % PrintSeconds(beacon_time_seconds)
print "Beacon quantum: %s" % quantum
print "Start of quantum: %s" % PrintSeconds(quantum * (2 ** scaler))
print "End of quantum: %s" % PrintSeconds((quantum + 1) * (2 ** scaler))
print
eid = GetAndPrintEid(ik, scaler, beacon_time_seconds)
# Print the request to get the beacon.
print
print "Get beacon:"
print " hget /v1beta1/beacons/4!%s" % ToHex(eid)
elif command == "keygen":
if len(args) != 1:
Usage()
source = Curve25519.ToPrivate(GetBinary(args[0], 32))
PrintBinary("Private", source)
public = Curve25519.ScalarMult(source)
PrintBinary("Public", public)
elif command == "shared":
if len(args) != 2:
Usage()
private = Curve25519.ToPrivate(GetBinary(args[0], 32))
PrintBinary("Private", private)
public = GetBinary(args[1], 32)
PrintBinary("Public", public)
PrintBinary("Shared secret", Curve25519.ScalarMult(private, public))
elif command == "ik":
if len(args) != 3:
Usage()
shared = GetBinary(args[0], 32)
PrintBinary("Shared secret", shared)
service_public_key = GetBinary(args[1], 32)
PrintBinary("Service public key", service_public_key)
beacon_public_key = GetBinary(args[2], 32)
PrintBinary("Beacon public key", beacon_public_key)
ik = GetAndPrintIdentityKey(shared, service_public_key, beacon_public_key)
elif command == "eid":
if len(args) != 3:
Usage()
ik = GetBinary(args[0], 16)
PrintBinary("Identity Key", ik)
scaler = int(args[1], 0)
beacon_time_seconds = int(args[2], 0)
GetAndPrintEid(ik, scaler, beacon_time_seconds)
else:
Usage()
return 0
if __name__ == "__main__":
if len(sys.argv) < 2:
Usage()
sys.exit(main(sys.argv[1], sys.argv[2:]))