Spike, not a feature request. The deliverable is a document saying whether this is possible and how, not working code. Code follows if the answer is yes.
Why this is the last one
Everything this project set out to remove is gone. The macOS VM, the Find My app, reading plists off a disk, the export being tied to one operating system — all replaced by an Apple ID, a passcode and iCloud. The findmy-export specification covers six stages end to end, and the write half works: an implementation built from Stage 4 §4 saves records that Apple's own Find My displays correctly.
One requirement is left, and it is worth naming precisely:
Registering an AirTag requires an iPhone or an iPad. Not a Mac — macOS cannot set up an AirTag at all, at any version. So the remaining dependency is not "an Apple device", it is specifically iOS or iPadOS.
Close this and the project is 100% iOS-free: buy a tag, register it from Android, locate it from Android. The tag is still an Apple product and the account is still an Apple ID — what goes away is needing to own an Apple device to use either.
What is already known
The destination is fully specified. Pairing has to end with a MasterBeaconRecord in the account's Find My zone, and that record's shape is documented field by field:
| Field |
|
privateKey, publicKey |
the beacon key pair |
sharedSecret, sharedSecret2 |
primary and secondary secrets, the ones key derivation runs on |
stableIdentifier |
holds the hardware serial — <prefix>~#<hardware-id>~#<serial> |
productId, vendorId, model, isZeus, systemVersion, pairingDate, batteryLevel |
descriptive |
And the machinery to put one there exists:
- Writing a CloudKit record — Stage 4 §4: endpoint, operation type 210,
saveSemantics 2 to create, the protection-tag dance
- Encrypting the fields so Apple can read them — Stage 5 §6.1, including the layout trap where the GCM tag precedes the ciphertext, so a value this client writes and reads back cleanly is not evidence Apple can read it
- @parawanderer's FindMy.py extensions already write to iCloud successfully
So the account half is not the unknown. The unknown is where the key material comes from.
What is missing
Two things, and only the first is certain to be hard.
1. The accessory half — the wall
The tag has to be handed (or negotiate) its key material over BLE, in a provisioning exchange that never touches CloudKit and therefore never appeared anywhere in the six stages. None of that work watched a pairing; it was all reconstructed from what the account already held.
Apple's public Find My Network Accessory Specification covers MFi third-party accessories rather than AirTags, and those carry factory certificates specifically so that arbitrary software cannot do this.
2. The account half's one unknown
Pairing appears to involve Apple binding the tag's serial to the account server-side. Whether that accepts anything other than an Apple client presenting a factory attestation from the tag was never observed. Writing a well-formed MasterBeaconRecord may simply not be sufficient.
What the spike would actually do
This needs hardware and traffic capture. It cannot be done by reading.
What it takes:
- An iPhone or iPad that can pair a tag
- An AirTag that has never been paired, or one that has been reset (five battery re-seats until the fifth chime)
- A BLE capture. On iOS this is Apple's own Bluetooth logging profile, which produces PacketLogger traces; over-the-air alternatives are Sniffle on an nRF52840 or a similar sniffer, which have the advantage of seeing what the phone's own stack chooses not to log
- Ideally a simultaneous CloudKit capture to correlate the two sides — though note the client pins, so this is the harder half and may need a jailbroken device or may simply not be worth it
The questions it has to answer, in order:
- Does the private key ever cross the air? If the tag generates it and never emits it, no amount of protocol work registers a tag from Android — and the spike ends there with a clean negative.
- What is the GATT service and characteristic layout an unpaired AirTag exposes, and what is the sequence of writes and notifications during setup?
- Is there an authentication step the phone cannot fake — a challenge signed by a factory key in the tag, a certificate presented to Apple, or an attestation the phone forwards?
- What does the phone send to Apple, and when relative to the BLE exchange? Does an
OwnedBeacons/MasterBeaconRecord write appear, and does anything precede it that looks like serial registration?
- Can a well-formed record alone claim a tag? The cheap experiment, and it can be tried before any of the above: take an already-paired tag's material, write a
MasterBeaconRecord for a serial the account has never seen, and see whether Apple accepts it and whether the tag's reports become fetchable.
Question 5 is where to start. It costs an afternoon, needs no sniffer, and if the answer is "Apple refuses a serial it did not bind itself" then questions 1–4 are moot regardless of what the BLE looks like.
Where the answer goes
A capture-derived specification is cleaner than the ones already here, not dirtier. The existing stages were written by someone who had read the reference implementations, precisely because there was no packet capture available — that is what the clean-room split exists to contain. A stage written from direct observation of Apple's own client has no such provenance problem, and the implementer would not be constrained the way the current ones are.
So:
docs/findmy-export/07-pairing.md — findings as a spec stage, in the style of the others, marking [observed] what was actually seen and flagging what was inferred
- Then the implementation, upstream in FindMy.py, where it belongs — this is protocol work, not app work, and both this app and the desktop exporter would consume it
- A negative result is still worth all of the above. "Registration requires an Apple-signed step and here is the evidence" closes the question permanently instead of leaving it open for the next person to re-derive.
Expected value, honestly
There is a real chance the answer is no. Apple has every incentive to make an attestation mandatory here, and third-party Find My accessories carrying factory certificates is direct evidence of the design intent.
The payoff is also narrower than it sounds: anyone who already owns AirTags already paired them, so this only helps someone buying a fresh AirTag having never owned an Apple device — and that person is arguably better served by an OpenHaystack tag, which is built for exactly that and needs none of this.
What it does buy is the last sentence of the README changing from "you need to borrow an iPhone or iPad once" to "you need the tag and an Apple ID" — which is the difference between a project with an asterisk and one without. That is worth someone's curiosity, if not necessarily their month.
Not to be confused with
#48 and #139 (@ubrt's local Bluetooth scanning). That is listening for a tag already yours, so the app can locate one sitting next to its owner's devices where the Find My network never sees it. This issue is writing — provisioning a tag that is nobody's yet. Same radio, unrelated problems.
Written up by Claude Code from a conversation with @parawanderer.
Spike, not a feature request. The deliverable is a document saying whether this is possible and how, not working code. Code follows if the answer is yes.
Why this is the last one
Everything this project set out to remove is gone. The macOS VM, the Find My app, reading plists off a disk, the export being tied to one operating system — all replaced by an Apple ID, a passcode and iCloud. The findmy-export specification covers six stages end to end, and the write half works: an implementation built from Stage 4 §4 saves records that Apple's own Find My displays correctly.
One requirement is left, and it is worth naming precisely:
Close this and the project is 100% iOS-free: buy a tag, register it from Android, locate it from Android. The tag is still an Apple product and the account is still an Apple ID — what goes away is needing to own an Apple device to use either.
What is already known
The destination is fully specified. Pairing has to end with a
MasterBeaconRecordin the account's Find My zone, and that record's shape is documented field by field:privateKey,publicKeysharedSecret,sharedSecret2stableIdentifier<prefix>~#<hardware-id>~#<serial>productId,vendorId,model,isZeus,systemVersion,pairingDate,batteryLevelAnd the machinery to put one there exists:
saveSemantics2 to create, the protection-tag danceSo the account half is not the unknown. The unknown is where the key material comes from.
What is missing
Two things, and only the first is certain to be hard.
1. The accessory half — the wall
The tag has to be handed (or negotiate) its key material over BLE, in a provisioning exchange that never touches CloudKit and therefore never appeared anywhere in the six stages. None of that work watched a pairing; it was all reconstructed from what the account already held.
Apple's public Find My Network Accessory Specification covers MFi third-party accessories rather than AirTags, and those carry factory certificates specifically so that arbitrary software cannot do this.
2. The account half's one unknown
Pairing appears to involve Apple binding the tag's serial to the account server-side. Whether that accepts anything other than an Apple client presenting a factory attestation from the tag was never observed. Writing a well-formed
MasterBeaconRecordmay simply not be sufficient.What the spike would actually do
This needs hardware and traffic capture. It cannot be done by reading.
What it takes:
The questions it has to answer, in order:
OwnedBeacons/MasterBeaconRecordwrite appear, and does anything precede it that looks like serial registration?MasterBeaconRecordfor a serial the account has never seen, and see whether Apple accepts it and whether the tag's reports become fetchable.Question 5 is where to start. It costs an afternoon, needs no sniffer, and if the answer is "Apple refuses a serial it did not bind itself" then questions 1–4 are moot regardless of what the BLE looks like.
Where the answer goes
A capture-derived specification is cleaner than the ones already here, not dirtier. The existing stages were written by someone who had read the reference implementations, precisely because there was no packet capture available — that is what the clean-room split exists to contain. A stage written from direct observation of Apple's own client has no such provenance problem, and the implementer would not be constrained the way the current ones are.
So:
docs/findmy-export/07-pairing.md— findings as a spec stage, in the style of the others, marking[observed]what was actually seen and flagging what was inferredExpected value, honestly
There is a real chance the answer is no. Apple has every incentive to make an attestation mandatory here, and third-party Find My accessories carrying factory certificates is direct evidence of the design intent.
The payoff is also narrower than it sounds: anyone who already owns AirTags already paired them, so this only helps someone buying a fresh AirTag having never owned an Apple device — and that person is arguably better served by an OpenHaystack tag, which is built for exactly that and needs none of this.
What it does buy is the last sentence of the README changing from "you need to borrow an iPhone or iPad once" to "you need the tag and an Apple ID" — which is the difference between a project with an asterisk and one without. That is worth someone's curiosity, if not necessarily their month.
Not to be confused with
#48 and #139 (@ubrt's local Bluetooth scanning). That is listening for a tag already yours, so the app can locate one sitting next to its owner's devices where the Find My network never sees it. This issue is writing — provisioning a tag that is nobody's yet. Same radio, unrelated problems.
Written up by Claude Code from a conversation with @parawanderer.