From e29b62b7b6388114e9bef07992caacaa10e53ebf Mon Sep 17 00:00:00 2001 From: Philip Magyar Date: Tue, 19 Nov 2024 13:18:30 +0100 Subject: [PATCH 1/2] clean code, add status and horizontal_acc, move confidance to real confidance, fix for 89 byte message --- AirTagCrypto/AirTagCrypto.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/AirTagCrypto/AirTagCrypto.py b/AirTagCrypto/AirTagCrypto.py index 0b97f5d..482a991 100644 --- a/AirTagCrypto/AirTagCrypto.py +++ b/AirTagCrypto/AirTagCrypto.py @@ -1,6 +1,5 @@ import base64 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes @@ -13,14 +12,30 @@ def __init__(self, private_key: str = None): else: self._private_key = self.__generate_new_private_key() + # Get the hashed advertisement key (hash of the public key) def get_advertisement_key(self) -> str: digest = hashes.Hash(hashes.SHA256()) - digest.update(self - .__derive_elliptic_curve_private_key(self._private_key, ec.SECP224R1()) - .public_key() - .public_bytes(Encoding.X962, PublicFormat.CompressedPoint)[1:]) + digest.update(self.__get_advertisement_key_bytes()) return base64.b64encode(digest.finalize()).decode() + # Get the public key + def get_public_key(self) -> str: + return base64.b64encode(self.__get_advertisement_key_bytes()).decode("ascii") + + # This function works if you follow the standard of the AirTag, as used in OpenHaystack + def get_mac_address(self) -> str: + adv_key = self.__get_advertisement_key_bytes() + first_hex = adv_key[0] | 0b11000000 + return self.format_byte(first_hex) + ":" + ":".join([self.format_byte(x) for x in adv_key[1:6]]) + + @staticmethod + def format_byte(byte): + return f'{byte:02x}'.upper() + + # Get the X of the public key in bytes + def __get_advertisement_key_bytes(self) -> bytes: + return self.__derive_elliptic_curve_private_key(self._private_key, ec.SECP224R1()).public_key().public_numbers().x.to_bytes(28, 'big') + @staticmethod def __derive_elliptic_curve_private_key(private_key: bytes, curve: ec.EllipticCurve): return ec.derive_private_key(int.from_bytes(private_key, 'big'), curve, default_backend()) @@ -54,8 +69,9 @@ def __decrypt_payload(enc_data: bytes, symmetric_key: bytes, tag: bytes): def __decode_tag(data: bytes): latitude = int.from_bytes(data[0:4], 'big', signed=True) / 10000000.0 longitude = int.from_bytes(data[4:8], 'big', signed=True) / 10000000.0 - confidence = int.from_bytes(data[8:9], 'big') - return {'lat': latitude, 'lon': longitude, 'conf': confidence} + horizontal_acc = int.from_bytes(data[8:9], 'big') + status = data[9] + return {'lat': latitude, 'lon': longitude, 'horizontal_acc': horizontal_acc, 'status': status} @staticmethod def __generate_new_private_key(): @@ -64,7 +80,9 @@ def __generate_new_private_key(): def decrypt_message(self, payload): data = base64.b64decode(payload) + if len(data) > 88: data = data[0:4] + data[5:] timestamp = int.from_bytes(data[0:4], 'big') + confidence = data[4] eph_key = data[5:62] shared_key = self.__derive_shared_key_from_private_key_and_eph_key(eph_key) derived_key = self.__kdf(shared_key, eph_key) @@ -74,4 +92,5 @@ def decrypt_message(self, payload): ret = self.__decode_tag(decrypted) ret['timestamp'] = timestamp + 978307200 # 978307200 is delta between unix and cocoa timestamps + ret['confidence'] = confidence return ret From d05fe3f301bb96d8c8877a69cf249f1432e7b0df Mon Sep 17 00:00:00 2001 From: Philip Magyar Date: Tue, 19 Nov 2024 13:31:46 +0100 Subject: [PATCH 2/2] fix #3 --- AirTagCrypto/AirTagCrypto.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AirTagCrypto/AirTagCrypto.py b/AirTagCrypto/AirTagCrypto.py index 482a991..31f1862 100644 --- a/AirTagCrypto/AirTagCrypto.py +++ b/AirTagCrypto/AirTagCrypto.py @@ -67,8 +67,8 @@ def __decrypt_payload(enc_data: bytes, symmetric_key: bytes, tag: bytes): @staticmethod def __decode_tag(data: bytes): - latitude = int.from_bytes(data[0:4], 'big', signed=True) / 10000000.0 - longitude = int.from_bytes(data[4:8], 'big', signed=True) / 10000000.0 + latitude = int.from_bytes(data[0:4], 'big') / 10000000.0 + longitude = int.from_bytes(data[4:8], 'big') / 10000000.0 horizontal_acc = int.from_bytes(data[8:9], 'big') status = data[9] return {'lat': latitude, 'lon': longitude, 'horizontal_acc': horizontal_acc, 'status': status}