Skip to content

Instantly share code, notes, and snippets.

@ink-splatters
Forked from airy10/airtag-decryptor.swift
Created May 24, 2024 03:24
Show Gist options
  • Save ink-splatters/4c7779768196a0102436e421c3da67ff to your computer and use it in GitHub Desktop.
Save ink-splatters/4c7779768196a0102436e421c3da67ff to your computer and use it in GitHub Desktop.

Revisions

  1. @airy10 airy10 revised this gist Feb 11, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion airtag-decryptor.swift
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    //
    // airtag-decryptor.swift
    //
    // Decrypt all beacons files from ~/Library/com.apple.icloud.searchpartyd
    // 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
  2. @airy10 airy10 revised this gist Feb 7, 2024. 1 changed file with 22 additions and 70 deletions.
    92 changes: 22 additions & 70 deletions airtag-decryptor.swift
    Original file line number Diff line number Diff line change
    @@ -7,6 +7,7 @@
    // Created by Matus on 28/01/2024. - https://gist.github.com/YeapGuy/f473de53c2a4e8978bc63217359ca1e4
    // Modified by Airy
    //
    import Cocoa
    import Foundation
    import CryptoKit

    @@ -47,36 +48,6 @@ func decryptRecordFile(fileURL: URL, key: SymmetricKey) throws -> [String: Any]
    return decryptedPlist
    }

    func decryptDataFile(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) {
    @@ -110,57 +81,38 @@ func decryptDirectory(filePath: String, outputPath: String, key: SymmetricKey) t
    }
    }

    // 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: "")
    // -> Hex format key from `security find-generic-password -l 'BeaconStore' -w`

    var data = Data(capacity: hexSanitized.count / 2)
    var index = hexSanitized.startIndex
    let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
    kSecAttrLabel as String: "BeaconStore",
    kSecMatchLimit as String: kSecMatchLimitOne,
    kSecReturnAttributes as String: true,
    kSecReturnData as String: true]

    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)
    }
    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 }

    return data
    }
    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)

    func cmd(_ cmd: String, _ args: String...) -> String {
    let task = Process()
    task.launchPath = cmd
    task.arguments = args
    let pipe = Pipe()
    task.standardOutput = pipe
    task.launch()
    task.waitUntilExit()
    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    guard let output: String = String(data: data, encoding: .utf8) else { return "" };
    return output
    let resultURL = URL(filePath: basePath, relativeTo: URL(filePath: outputPath))
    NSWorkspace.shared.open(resultURL)
    }

    // -> Hex format key from `security find-generic-password -l 'BeaconStore' -w`
    let hexKey = cmd("/usr/bin/security", "find-generic-password", "-l", "BeaconStore", "-w")

    // Convert hex key to Data
    let keyData = data(fromHex: hexKey)

    let key = SymmetricKey(data: keyData)

    let basePath = "com.apple.icloud.searchpartyd"
    let outputPath = NSTemporaryDirectory()

    try decryptDirectory(filePath: basePath, outputPath: outputPath, key: key)

    cmd("/usr/bin/open", (outputPath as NSString).appendingPathComponent(basePath))

    enum MyError: Error {
    case invalidFileFormat
    case invalidPlistFormat
    case invalidDecryptedData
    case noPassword
    case invalidItem
    case keychainError(status: OSStatus)
    }
  3. @airy10 airy10 revised this gist Feb 7, 2024. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion airtag-decryptor.swift
    Original file line number Diff line number Diff line change
    @@ -157,7 +157,6 @@ let outputPath = NSTemporaryDirectory()

    try decryptDirectory(filePath: basePath, outputPath: outputPath, key: key)

    let devicesData =
    cmd("/usr/bin/open", (outputPath as NSString).appendingPathComponent(basePath))

    enum MyError: Error {
  4. @airy10 airy10 renamed this gist Feb 7, 2024. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  5. @airy10 airy10 created this gist Feb 7, 2024.
    167 changes: 167 additions & 0 deletions swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,167 @@
    //
    // airtag-decryptor.swift
    //
    // Decrypt all beacons files from ~/Library/com.apple.icloud.searchpartyd
    // 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 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 decryptDataFile(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)
    }
    }
    }

    // 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
    }




    func cmd(_ cmd: String, _ args: String...) -> String {
    let task = Process()
    task.launchPath = cmd
    task.arguments = args
    let pipe = Pipe()
    task.standardOutput = pipe
    task.launch()
    task.waitUntilExit()
    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    guard let output: String = String(data: data, encoding: .utf8) else { return "" };
    return output
    }

    // -> Hex format key from `security find-generic-password -l 'BeaconStore' -w`
    let hexKey = cmd("/usr/bin/security", "find-generic-password", "-l", "BeaconStore", "-w")

    // Convert hex key to Data
    let keyData = data(fromHex: hexKey)

    let key = SymmetricKey(data: keyData)

    let basePath = "com.apple.icloud.searchpartyd"
    let outputPath = NSTemporaryDirectory()

    try decryptDirectory(filePath: basePath, outputPath: outputPath, key: key)

    let devicesData =
    cmd("/usr/bin/open", (outputPath as NSString).appendingPathComponent(basePath))

    enum MyError: Error {
    case invalidFileFormat
    case invalidPlistFormat
    case invalidDecryptedData
    }