Skip to content

Instantly share code, notes, and snippets.

@YeapGuy
Last active February 12, 2024 16:28
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save YeapGuy/f473de53c2a4e8978bc63217359ca1e4 to your computer and use it in GitHub Desktop.
Save YeapGuy/f473de53c2a4e8978bc63217359ca1e4 to your computer and use it in GitHub Desktop.
Decrypt macOS Find My .record file
//
// airtag-decryptor.swift
//
//
// Created by Matus on 28/01/2024.
//
import Foundation
import CryptoKit
// 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
}
// Function to convert hex string to Data
func data(fromHex hex: String) -> Data {
var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
hexSanitized = hexSanitized.replacingOccurrences(of: "0x", with: "")
var data = Data(capacity: hexSanitized.count / 2)
var index = hexSanitized.startIndex
while index < hexSanitized.endIndex {
let byteString = hexSanitized[index ..< hexSanitized.index(index, offsetBy: 2)]
let byte = UInt8(byteString, radix: 16)!
data.append(byte)
index = hexSanitized.index(index, offsetBy: 2)
}
return data
}
// -> Hex format key from `security find-generic-password -l 'FindMyAccessories' -g` "gena" attribute value
let hexKey = "0xKEYINHEXFORMAT"
// Convert hex key to Data
let keyData = data(fromHex: hexKey)
// Usage
let fileURL = URL(fileURLWithPath: "/Users/matus/Library/com.apple.icloud.searchpartyd/OwnedBeacons/REPLACE.record")
let key = SymmetricKey(data: keyData)
do {
let decryptedPlist = try decryptRecordFile(fileURL: fileURL, key: key)
// Save decrypted plist as a file in the current directory
let outputURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent("decrypted.plist")
try PropertyListSerialization.data(fromPropertyList: decryptedPlist, format: .xml, options: 0).write(to: outputURL)
print("Decrypted plist saved at:", outputURL.path)
} catch {
print("Error:", error)
}
enum MyError: Error {
case invalidFileFormat
case invalidPlistFormat
case invalidDecryptedData
}
@biemster
Copy link

biemster commented Feb 12, 2024

The security find-generic-password -l 'FindMyAccessories' -g could be automated as well right? I've got some python code somewhere that reads those generic-passwords from the keychain file.
edit: nvm it seems @airy10 already did that: https://gist.github.com/airy10/5205dc851fbd0715fcd7a5cdde25e7c8

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