Skip to content

Commit

Permalink
Merge pull request #9 from olivluca/main
Browse files Browse the repository at this point in the history
Simplify usage when a tag cycles through many keys, add status byte, google maps link
  • Loading branch information
biemster authored Sep 9, 2022
2 parents 9563c87 + 682269a commit 82cb6b6
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 28 deletions.
37 changes: 27 additions & 10 deletions generate_keys.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python2
import sys,base64,hashlib,random
from p224 import scalar_mult,curve
import argparse

def int_to_bytes(n, length, endianess='big'):
h = '%x' % n
Expand All @@ -13,11 +14,20 @@ def sha256(data):
return digest.digest()


nkeys = 1
if len(sys.argv) == 2 and sys.argv[1].isdigit():
nkeys = int(sys.argv[1])

for i in range(nkeys):
parser = argparse.ArgumentParser()
parser.add_argument('-k','--keys', help='number of keys to generate', type=int, default=1)
parser.add_argument('-n','--name', help='name (prefix) of the keyfiles')
parser.add_argument('-y','--yaml', help='yaml file where to write the list of generated keys')
parser.add_argument('-v','--verbose', help='print keys as they are generated', action="store_true")
args=parser.parse_args()

if args.yaml is not None:
yaml=open(args.yaml+'.yaml','w')
yaml.write(' keys:\n')

i=1
while i<=args.keys:
priv = random.getrandbits(224)
adv,_ = scalar_mult(priv, curve.g)

Expand All @@ -28,15 +38,22 @@ def sha256(data):
adv_b64 = base64.b64encode(adv_bytes).decode("ascii")
s256_b64 = base64.b64encode(sha256(adv_bytes)).decode("ascii")

print('%d)' % (i+1))
print('Private key: %s' % priv_b64)
print('Advertisement key: %s' % adv_b64)
print('Hashed adv key: %s' % s256_b64)
if args.verbose:
print('%d)' % (i))
print('Private key: %s' % priv_b64)
print('Advertisement key: %s' % adv_b64)
print('Hashed adv key: %s' % s256_b64)
if '/' in s256_b64[:7]:
print('no key file written, there was a / in the b64 of the hashed pubkey :(')
else:
with open('%s.keys' % s256_b64[:7], 'w') as f:
if args.name:
fname = '%s_%d.keys' % (args.name, i)
else:
fname = '%s.keys' % s256_b64[:7]
with open(fname, 'w') as f:
f.write('Private key: %s\n' % priv_b64)
f.write('Advertisement key: %s\n' % adv_b64)
f.write('Hashed adv key: %s\n' % s256_b64)

i = i +1
if args.yaml is not None:
yaml.write(' - "%s"\n' % adv_b64)
83 changes: 65 additions & 18 deletions request_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
import objc
from Foundation import NSBundle, NSClassFromString, NSData, NSPropertyListSerialization
from p224 import scalar_mult,curve

from datetime import datetime
import argparse

def bytes_to_int(b):
return int(codecs.encode(b, 'hex'), 16)
Expand All @@ -33,7 +34,8 @@ def decode_tag(data):
latitude = struct.unpack(">i", data[0:4])[0] / 10000000.0
longitude = struct.unpack(">i", data[4:8])[0] / 10000000.0
confidence = bytes_to_int(data[8:9])
return {'lat': latitude, 'lon': longitude, 'conf': confidence}
status = bytes_to_int(data[9:10])
return {'lat': latitude, 'lon': longitude, 'conf': confidence, 'status':status}

def getAppleDSIDandSearchPartyToken(iCloudKey):
# copied from https://github.com/Hsn723/MMeTokenDecrypt
Expand Down Expand Up @@ -62,8 +64,16 @@ def getCurrentTimes():


if __name__ == "__main__":
from getpass import getpass
iCloud_decryptionkey = getpass("Enter your iCloud decryption key ($ security find-generic-password -ws 'iCloud'):")
parser = argparse.ArgumentParser()
parser.add_argument('-H', '--hours', help='only show reports not older than these hours', type=int, default=24)
parser.add_argument('-p', '--prefix', help='only use keyfiles starting with this prefix', default='')
parser.add_argument('-k', '--key', help='iCloud decryption key')
args=parser.parse_args()
if args.key is None:
from getpass import getpass
iCloud_decryptionkey = getpass("Enter your iCloud decryption key ($ security find-generic-password -ws 'iCloud'):")
else:
iCloud_decryptionkey = args.key
AppleDSID,searchPartyToken = getAppleDSIDandSearchPartyToken(iCloud_decryptionkey)
machineID, oneTimePassword = getOTPHeaders()
UTCTime, Timezone, unixEpoch = getCurrentTimes()
Expand All @@ -81,11 +91,14 @@ def getCurrentTimes():
}

ids = {}
for keyfile in glob.glob('./*keys'):
names = {}
found = {}
for keyfile in glob.glob(args.prefix+'*.keys'):
# read key files generated with generate_keys.py
with open(keyfile) as f:
hashed_adv = ''
priv = ''
name = keyfile[:-5]
for line in f:
key = line.rstrip('\n').split(': ')
if key[0] == 'Private key':
Expand All @@ -95,10 +108,17 @@ def getCurrentTimes():

if priv and hashed_adv:
ids[hashed_adv] = priv
names[hashed_adv] = name
subkey=name[len(args.prefix):]
found[subkey] = False
else:
print "Couldn't find key pair in " + keyfile

data = '{"search": [{"endDate": %d, "startDate": %d, "ids": %s}]}' % (unixEpoch *1000, (unixEpoch *1000) -(86400000 *7), ids.keys())
print "keys ", len(ids)
enddate = unixEpoch
startdate = enddate - 60 * 60 * args.hours
coco = 978307200
data = '{"search": [{"endDate": %d, "startDate": %d, "ids": %s}]}' % ((enddate-coco) *1000000, (startdate-coco)*1000000, ids.keys())

# send out the whole thing
import httplib, urllib
Expand All @@ -109,21 +129,48 @@ def getCurrentTimes():
res = json.loads(response.read())['results']
print '%d reports received.' % len(res)

ordered = []
for report in res:
priv = bytes_to_int(base64.b64decode(ids[report['id']]))
data = base64.b64decode(report['payload'])

# the following is all copied from https://github.com/hatomist/openhaystack-python, thanks @hatomist!
timestamp = bytes_to_int(data[0:4])
eph_key = (bytes_to_int(data[6:34]), bytes_to_int(data[34:62]))
shared_key = scalar_mult(priv, eph_key)
symmetric_key = sha256(int_to_bytes(shared_key[0], 28) + int_to_bytes(1, 4) + data[5:62])
decryption_key = symmetric_key[:16]
iv = symmetric_key[16:]
enc_data = data[62:72]
tag = data[72:]

decrypted = decrypt_AES(enc_data, decryption_key, modes.GCM(iv, tag))
res = decode_tag(decrypted)
res['timestamp'] = timestamp + 978307200
print report['id'] + ': ' + str(res)
if timestamp + 978307200 >= startdate:
eph_key = (bytes_to_int(data[6:34]), bytes_to_int(data[34:62]))
shared_key = scalar_mult(priv, eph_key)
symmetric_key = sha256(int_to_bytes(shared_key[0], 28) + int_to_bytes(1, 4) + data[5:62])
decryption_key = symmetric_key[:16]
iv = symmetric_key[16:]
enc_data = data[62:72]
tag = data[72:]

decrypted = decrypt_AES(enc_data, decryption_key, modes.GCM(iv, tag))
res = decode_tag(decrypted)
res['timestamp'] = timestamp + 978307200
res['isodatetime'] = datetime.fromtimestamp(res['timestamp']).isoformat()
name=names[report['id']]
if args.prefix:
res['key'] = name[len(args.prefix)]
found[res['key']]=True
res['goog'] = 'https://maps.google.com/maps?q='+str(res['lat'])+','+str(res['lon'])
#print str(res)
ordered.append(res)
print '%d reports used.' % len(ordered)
ordered.sort(key=lambda item: item.get('timestamp'))
for x in ordered:
print x

if args.prefix:
f = []
m = []
for x in found:
if found[x]:
f.append(x)
else:
m.append(x)

f.sort(key=lambda x: (len(x),x))
m.sort(key=lambda x: (len(x),x))
print "missing ", len(m),': ', ', '.join(m)
print "found ", len(f),': ',', '.join(f)

0 comments on commit 82cb6b6

Please sign in to comment.