Skip to content

Instantly share code, notes, and snippets.

@airy10
Last active September 30, 2025 02:25
Show Gist options
  • Save airy10/5205dc851fbd0715fcd7a5cdde25e7c8 to your computer and use it in GitHub Desktop.
Save airy10/5205dc851fbd0715fcd7a5cdde25e7c8 to your computer and use it in GitHub Desktop.
Decrypt all beacons files from ~/Library/com.apple.icloud.searchpartyd
//
// airtag-decryptor.swift
//
// Decrypt all beacons files from ~/Library/com.apple.icloud.searchpartyd - updated when FindMy is running
// Results in /tmp/com.apple.icloud.searchpartyd - same file hierarchy
//
// Created by Matus on 28/01/2024. - https://gist.github.com/YeapGuy/f473de53c2a4e8978bc63217359ca1e4
// Modified by Airy
//
import Cocoa
import Foundation
import CryptoKit
extension URL {
var isDirectory: Bool {
(try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true
}
}
// Function to decrypt using AES-GCM
func decryptRecordFile(fileURL: URL, key: SymmetricKey) throws -> [String: Any] {
// Read data from the file
let data = try Data(contentsOf: fileURL)
// Convert data to a property list (plist)
guard let plist = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [Any] else {
throw MyError.invalidFileFormat
}
// Extract nonce, tag, and ciphertext
guard plist.count >= 3,
let nonceData = plist[0] as? Data,
let tagData = plist[1] as? Data,
let ciphertextData = plist[2] as? Data else {
throw MyError.invalidPlistFormat
}
let sealedBox = try AES.GCM.SealedBox(nonce: AES.GCM.Nonce(data: nonceData), ciphertext: ciphertextData, tag: tagData)
// Decrypt using AES-GCM
let decryptedData = try AES.GCM.open(sealedBox, using: key)
// Convert decrypted data to a property list
guard let decryptedPlist = try PropertyListSerialization.propertyList(from: decryptedData, options: [], format: nil) as? [String: Any] else {
throw MyError.invalidDecryptedData
}
return decryptedPlist
}
func decryptDirectory(filePath: String, outputPath: String, key: SymmetricKey) throws {
let baseURL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first
if let contentURL = baseURL?.appending(path: filePath) {
if contentURL.isDirectory {
if let urls = try? FileManager.default.contentsOfDirectory(at: contentURL, includingPropertiesForKeys: nil) {
for url in urls {
let path = (filePath as NSString).appendingPathComponent(url.lastPathComponent)
try decryptDirectory(filePath: path, outputPath: outputPath, key: key)
}
}
} else {
do {
let decryptedPlist = try decryptRecordFile(fileURL: contentURL, key: key)
// Save decrypted plist as a file in the current directory
let name = contentURL.lastPathComponent as NSString
if let outputName = (name.deletingPathExtension as NSString).appendingPathExtension("plist") {
let dir = (filePath as NSString).deletingLastPathComponent
let outputDirPath = (outputPath as NSString).appendingPathComponent(dir)
try FileManager.default.createDirectory(atPath: outputDirPath, withIntermediateDirectories: true)
let outputURL = URL(fileURLWithPath: outputDirPath).appending(path: outputName)
try PropertyListSerialization.data(fromPropertyList: decryptedPlist, format: .xml, options: 0).write(to: outputURL)
}
} catch {
print("Error:", error)
}
print(filePath)
}
}
}
// -> Hex format key from `security find-generic-password -l 'BeaconStore' -w`
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrLabel as String: "BeaconStore",
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnAttributes as String: true,
kSecReturnData as String: true]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status != errSecItemNotFound else { throw MyError.noPassword }
guard status == errSecSuccess else { throw MyError.keychainError(status: status) }
guard let existingItem = item as? [String : Any] else { throw MyError.invalidItem }
if let keyData = existingItem[kSecValueData as String] as? Data {
let key = SymmetricKey(data: keyData)
let basePath = "com.apple.icloud.searchpartyd"
let outputPath = NSTemporaryDirectory()
try decryptDirectory(filePath: basePath, outputPath: outputPath, key: key)
let resultURL = URL(filePath: basePath, relativeTo: URL(filePath: outputPath))
NSWorkspace.shared.open(resultURL)
}
enum MyError: Error {
case invalidFileFormat
case invalidPlistFormat
case invalidDecryptedData
case noPassword
case invalidItem
case keychainError(status: OSStatus)
}
@airy10
Copy link
Author

airy10 commented Sep 17, 2025

The missing part should be :
sudo nvram boot-args="amfi_get_out_of_my_way=1"

@tomck
Copy link

tomck commented Sep 30, 2025

Hi, I'm trying to import my OEM airtags into OpenHaystack (my real end-goal is logging positions because I have a stolen item that I'm trying to keep track of for authorities), but while it seems I need to use this (and I'm on OS 14.5 so it still works), I get a bunch of terminal output but no results in /tmp/, and I don't know what I need to do. I did download and install xcode and allow the permissions it needed, and formed an executable with swiftc and did chmod +x, but t hen I get a bunch of UUIDs in various folders and do not know what to do with it.

Any assistance is appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment