Skip to content

Commit

Permalink
some code cleanup regarding keyfiles with prefix (PR #9)
Browse files Browse the repository at this point in the history
  • Loading branch information
biemster committed Sep 9, 2022
1 parent 82cb6b6 commit 55c3ae5
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 80 deletions.
38 changes: 19 additions & 19 deletions generate_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,18 @@ def sha256(data):
return digest.digest()



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('-n','--nkeys', help='number of keys to generate', type=int, default=1)
parser.add_argument('-p','--prefix', help='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()
args = parser.parse_args()

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

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

Expand All @@ -39,21 +37,23 @@ def sha256(data):
s256_b64 = base64.b64encode(sha256(adv_bytes)).decode("ascii")

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)
print('%d)' % (i+1))
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:
if args.name:
fname = '%s_%d.keys' % (args.name, i)
if args.prefix:
fname = '%s_%s.keys' % (args.prefix, s256_b64[:7])
else:
fname = '%s.keys' % s256_b64[:7]
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)

if args.yaml:
yaml.write(' - "%s"\n' % adv_b64)
97 changes: 36 additions & 61 deletions request_reports.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env python2
import os,glob
import datetime, time
import argparse,getpass
import base64,json
import hashlib,hmac
import codecs,struct
Expand All @@ -9,8 +11,6 @@
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 @@ -25,11 +25,14 @@ def sha256(data):
digest.update(data)
return digest.digest()

def decrypt_AES(enc_data, dkey, mode):
cipher = Cipher(algorithms.AES(dkey), mode, default_backend())
decryptor = cipher.decryptor()
def decrypt(enc_data, algorithm_dkey, mode):
decryptor = Cipher(algorithm_dkey, mode, default_backend()).decryptor()
return decryptor.update(enc_data) + decryptor.finalize()

def unpad(paddedBinary, blocksize):
unpadder = PKCS7(blocksize).unpadder()
return unpadder.update(paddedBinary) + unpadder.finalize()

def decode_tag(data):
latitude = struct.unpack(">i", data[0:4])[0] / 10000000.0
longitude = struct.unpack(">i", data[4:8])[0] / 10000000.0
Expand All @@ -41,9 +44,7 @@ def getAppleDSIDandSearchPartyToken(iCloudKey):
# copied from https://github.com/Hsn723/MMeTokenDecrypt
decryption_key = hmac.new("t9s\"lx^awe.580Gj%'ld+0LG<#9xa?>vb)-fkwb92[}", base64.b64decode(iCloudKey), digestmod=hashlib.md5).digest()
mmeTokenFile = glob.glob("%s/Library/Application Support/iCloud/Accounts/[0-9]*" % os.path.expanduser("~"))[0]
decryptedBinary = decrypt_AES(open(mmeTokenFile, 'rb').read(), decryption_key, modes.CBC(b'\00' *16));
unpadder = PKCS7(algorithms.AES.block_size).unpadder()
decryptedBinary = unpadder.update(decryptedBinary) + unpadder.finalize()
decryptedBinary = unpad(decrypt(open(mmeTokenFile, 'rb').read(), algorithms.AES(decryption_key), modes.CBC(b'\00' *16)), algorithms.AES.block_size);
binToPlist = NSData.dataWithBytes_length_(decryptedBinary, len(decryptedBinary))
tokenPlist = NSPropertyListSerialization.propertyListWithData_options_format_error_(binToPlist, 0, None, None)[0]
return tokenPlist["appleAccountInfo"]["dsPrsID"], tokenPlist["tokens"]['searchPartyToken']
Expand All @@ -52,12 +53,10 @@ def getOTPHeaders():
AOSKitBundle = NSBundle.bundleWithPath_('/System/Library/PrivateFrameworks/AOSKit.framework')
objc.loadBundleFunctions(AOSKitBundle, globals(), [("retrieveOTPHeadersForDSID", '')])
util = NSClassFromString('AOSUtilities')

anisette = str(util.retrieveOTPHeadersForDSID_("-2")).replace('"', ' ').replace(';', ' ').split()
return anisette[6], anisette[3]

def getCurrentTimes():
import datetime, time
clientTime = datetime.datetime.utcnow().replace(microsecond=0).isoformat() + 'Z'
clientTimestamp = int(datetime.datetime.now().strftime('%s'))
return clientTime, time.tzname[1], clientTimestamp
Expand All @@ -67,13 +66,10 @@ def getCurrentTimes():
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
parser.add_argument('-k', '--key', help="iCloud decryption key ($ security find-generic-password -ws 'iCloud')")
args = parser.parse_args()
iCloud_decryptionkey = args.key if args.key else getpass.getpass("Enter your iCloud decryption key ($ security find-generic-password -ws 'iCloud'):")

AppleDSID,searchPartyToken = getAppleDSIDandSearchPartyToken(iCloud_decryptionkey)
machineID, oneTimePassword = getOTPHeaders()
UTCTime, Timezone, unixEpoch = getCurrentTimes()
Expand All @@ -92,13 +88,12 @@ def getCurrentTimes():

ids = {}
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]
name = keyfile[len(args.prefix):-5]
for line in f:
key = line.rstrip('\n').split(': ')
if key[0] == 'Private key':
Expand All @@ -109,16 +104,11 @@ 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

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())
startdate = unixEpoch - 60 * 60 * args.hours
data = '{"search": [{"endDate": %d, "startDate": %d, "ids": %s}]}' % ((unixEpoch -978307200) *1000000, (startdate -978307200)*1000000, ids.keys())

# send out the whole thing
import httplib, urllib
Expand All @@ -130,47 +120,32 @@ def getCurrentTimes():
print '%d reports received.' % len(res)

ordered = []
found = set()
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])
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)
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(enc_data, algorithms.AES(decryption_key), modes.GCM(iv, tag))
res = decode_tag(decrypted)
res['timestamp'] = timestamp + 978307200
res['isodatetime'] = datetime.datetime.fromtimestamp(res['timestamp']).isoformat()
res['key'] = names[report['id']]
res['goog'] = 'https://maps.google.com/maps?q=' + str(res['lat']) + ',' + str(res['lon'])
found.add(res['key'])
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)
for rep in ordered: print rep
print 'found: ', list(found)
print 'missing: ', [key for key in names.values() if key not in found]

3 comments on commit 55c3ae5

@olivluca
Copy link
Contributor

@olivluca olivluca commented on 55c3ae5 Sep 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the generate_keys script I used

while i<=args.keys:

instead of

for i in range(args.nkeys):

because if this condition is triggered (and it is from time to time)

if '/' in s256_b64[:7]:

you end up generating less keys than requested

@biemster
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah you'r right, I just hit that after committing this.. I'll put that back later, I don't use this almost ever. It will probably come along with biemster/st17h66_FindMy#2

@olivluca
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also see that the missing/found keys are not sorted. This was just to test if the tag is correctly cycling through all the keys, and it's easier to spot an error if they are sorted.
Oh, and printing the length also helped.
This is a debug option so it's probably best to only show it with a command line switch.

Please sign in to comment.