การรักษาความปลอดภัยนักวิจัย Jean-Baptiste Bédruneและฌอง Sigwald นำเสนอวิธีการที่จะทำเช่นนี้ที่สับ-in-the-กล่องอัมสเตอร์ดัม 2011
ตั้งแต่นั้นมา Apple ได้เปิดตัวเอกสารรายงานความปลอดภัยของ iOS
พร้อมรายละเอียดเพิ่มเติมเกี่ยวกับคีย์และอัลกอริทึมและ Charlie Miller et al ได้เปิดตัวคู่มือiOS Hacker's Handbookซึ่งครอบคลุมเนื้อหาบางส่วนที่เหมือนกันในรูปแบบวิธีใช้ เมื่อ iOS ของ 10 แรกออกมามีการเปลี่ยนแปลงรูปแบบการสำรองข้อมูลที่แอปเปิ้ลไม่ได้เผยแพร่ในตอนแรก แต่หลายคนกลับวิศวกรรมการเปลี่ยนแปลงรูปแบบ
การสำรองข้อมูลที่เข้ารหัสนั้นยอดเยี่ยมมาก
สิ่งที่ยอดเยี่ยมเกี่ยวกับการสำรองข้อมูล iPhone ที่เข้ารหัสคือมีสิ่งต่างๆเช่นรหัสผ่าน WiFi ที่ไม่ได้อยู่ในการสำรองข้อมูลที่ไม่ได้เข้ารหัสตามปกติ ตามที่กล่าวไว้ในเอกสารรายงานความปลอดภัยของ iOS การสำรองข้อมูลที่เข้ารหัสนั้นถือว่า“ ปลอดภัยกว่า” ดังนั้น Apple จึงพิจารณาว่าควรรวมข้อมูลที่ละเอียดอ่อนไว้ในข้อมูลเหล่านี้
คำเตือนที่สำคัญ: เห็นได้ชัดว่าการถอดรหัสข้อมูลสำรองของอุปกรณ์ iOS ของคุณจะลบการเข้ารหัส เพื่อปกป้องความเป็นส่วนตัวและความปลอดภัยของคุณคุณควรเรียกใช้สคริปต์เหล่านี้บนเครื่องที่มีการเข้ารหัสแบบเต็มดิสก์เท่านั้น แม้ว่าผู้เชี่ยวชาญด้านความปลอดภัยจะสามารถเขียนซอฟต์แวร์ที่ปกป้องคีย์ในหน่วยความจำได้เช่นโดยใช้ฟังก์ชันที่เหมือนกับVirtualLock()
และ
SecureZeroMemory()
เหนือสิ่งอื่นใดสคริปต์ Python เหล่านี้จะจัดเก็บคีย์การเข้ารหัสและรหัสผ่านของคุณไว้ในสตริงเพื่อให้ Python เก็บขยะ ซึ่งหมายความว่าคีย์ลับและรหัสผ่านของคุณจะอยู่ใน RAM สักระยะหนึ่งจากที่ใดสิ่งเหล่านี้จะรั่วไหลเข้าไปในไฟล์ swap ของคุณและลงในดิสก์ของคุณซึ่งฝ่ายตรงข้ามสามารถกู้คืนได้ สิ่งนี้เอาชนะจุดที่มีการสำรองข้อมูลที่เข้ารหัสอย่างสมบูรณ์
วิธีถอดรหัสข้อมูลสำรอง: ในทางทฤษฎี
เอกสารรายงานความปลอดภัยของ iOSอธิบายถึงแนวคิดพื้นฐานของคีย์ต่อไฟล์คลาสการป้องกันคีย์คลาสการป้องกันและคีย์แบ็กที่ดีกว่าที่ฉันทำได้ หากคุณยังไม่คุ้นเคยกับสิ่งเหล่านี้โปรดใช้เวลาสักครู่เพื่ออ่านส่วนที่เกี่ยวข้อง
ตอนนี้คุณรู้แล้วว่าทุกไฟล์ใน iOS ถูกเข้ารหัสด้วยคีย์การเข้ารหัสแบบสุ่มต่อไฟล์ของตัวเองเป็นของระดับการป้องกันและคีย์การเข้ารหัสต่อไฟล์จะถูกเก็บไว้ในข้อมูลเมตาของระบบไฟล์ซึ่งรวมอยู่ในคีย์คลาสการป้องกัน
ในการถอดรหัส:
ถอดรหัส keybag ที่เก็บไว้ในการเข้ามาของBackupKeyBag
Manifest.plist
ภาพรวมระดับสูงของโครงสร้างนี้จะได้รับในเอกสาร iPhone วิกิพีเดีย
อธิบายถึงรูปแบบไบนารี: 4 ไบต์ประเภทสตริงฟิลด์ 4 ไบต์ big-ยาวเขตข้อมูลและแล้วค่าของตัวเอง
ค่าที่สำคัญเป็น PBKDF2 ITER
ations และSALT
เกลือป้องกันคู่DPSL
และซ้ำนับDPIC
แล้วสำหรับแต่ละการป้องกันCLS
ที่WPKY
สำคัญห่อ
การใช้รหัสผ่านสำรองจะได้รับคีย์ 32 ไบต์โดยใช้เกลือ PBKDF2 ที่ถูกต้องและจำนวนการทำซ้ำ ใช้งานครั้งแรกรอบ SHA256 ด้วยDPSL
และ
DPIC
แล้วรอบ SHA1 ด้วยและITER
SALT
แกะห่อที่สำคัญแต่ละชนิดตาม
RFC 3394
ถอดรหัสฐานข้อมูลรายการโดยดึงคลาสการป้องกัน 4 ไบต์และคีย์ที่ยาวกว่าจากManifestKey
ในManifest.plist
และคลายออก ขณะนี้คุณมีฐานข้อมูล SQLite พร้อมข้อมูลเมตาของไฟล์ทั้งหมด
สำหรับไฟล์ที่สนใจแต่ละไฟล์ให้รับคีย์การเข้ารหัสต่อไฟล์ที่เข้ารหัสคลาสและรหัสคลาสการป้องกันโดยดูในFiles.file
คอลัมน์ฐานข้อมูลเพื่อหา plist ไบนารีที่มีEncryptionKey
และ
ProtectionClass
รายการ ตัดแท็กความยาวสี่ไบต์เริ่มต้น
EncryptionKey
ก่อนใช้
จากนั้นรับคีย์การถอดรหัสขั้นสุดท้ายโดยการแกะด้วยคีย์คลาสที่ไม่ได้ใส่รหัสผ่านสำรอง จากนั้นถอดรหัสไฟล์โดยใช้ AES ในโหมด CBC ด้วยศูนย์ IV
วิธีถอดรหัสข้อมูลสำรอง: ในทางปฏิบัติ
ก่อนอื่นคุณจะต้องมีการอ้างอิงไลบรารี หากคุณใช้ Mac โดยใช้ Python 2.7 หรือ 3.7 ที่ติดตั้ง homebrew คุณสามารถติดตั้งการอ้างอิงโดยใช้:
CFLAGS="-I$(brew --prefix)/opt/openssl/include" \
LDFLAGS="-L$(brew --prefix)/opt/openssl/lib" \
pip install biplist fastpbkdf2 pycrypto
ในรูปแบบซอร์สโค้ดที่รันได้นี่คือวิธีถอดรหัสไฟล์การตั้งค่ารายการเดียวจากข้อมูลสำรอง iPhone ที่เข้ารหัส:
from __future__ import print_function
from __future__ import division
import argparse
import getpass
import os.path
import pprint
import random
import shutil
import sqlite3
import string
import struct
import tempfile
from binascii import hexlify
import Crypto.Cipher.AES
import biplist
import fastpbkdf2
from biplist import InvalidPlistException
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--backup-directory', dest='backup_directory',
default='testdata/encrypted')
parser.add_argument('--password-pipe', dest='password_pipe',
help="""\
Keeps password from being visible in system process list.
Typical use: --password-pipe=<(echo -n foo)
""")
parser.add_argument('--no-anonymize-output', dest='anonymize',
action='store_false')
args = parser.parse_args()
global ANONYMIZE_OUTPUT
ANONYMIZE_OUTPUT = args.anonymize
if ANONYMIZE_OUTPUT:
print('Warning: All output keys are FAKE to protect your privacy')
manifest_file = os.path.join(args.backup_directory, 'Manifest.plist')
with open(manifest_file, 'rb') as infile:
manifest_plist = biplist.readPlist(infile)
keybag = Keybag(manifest_plist['BackupKeyBag'])
keybag.printClassKeys()
if args.password_pipe:
password = readpipe(args.password_pipe)
if password.endswith(b'\n'):
password = password[:-1]
else:
password = getpass.getpass('Backup password: ').encode('utf-8')
if not keybag.unlockWithPasscode(password):
raise Exception('Could not unlock keybag; bad password?')
keybag.printClassKeys()
manifest_key = manifest_plist['ManifestKey'][4:]
with open(os.path.join(args.backup_directory, 'Manifest.db'), 'rb') as db:
encrypted_db = db.read()
manifest_class = struct.unpack('<l', manifest_plist['ManifestKey'][:4])[0]
key = keybag.unwrapKeyForClass(manifest_class, manifest_key)
decrypted_data = AESdecryptCBC(encrypted_db, key)
temp_dir = tempfile.mkdtemp()
try:
db_filename = os.path.join(temp_dir, 'db.sqlite3')
with open(db_filename, 'wb') as db_file:
db_file.write(decrypted_data)
conn = sqlite3.connect(db_filename)
conn.row_factory = sqlite3.Row
c = conn.cursor()
c.execute("""
SELECT fileID, domain, relativePath, file
FROM Files
WHERE relativePath LIKE 'Media/PhotoData/MISC/DCIM_APPLE.plist'
ORDER BY domain, relativePath""")
results = c.fetchall()
finally:
shutil.rmtree(temp_dir)
for item in results:
fileID, domain, relativePath, file_bplist = item
plist = biplist.readPlistFromString(file_bplist)
file_data = plist['$objects'][plist['$top']['root'].integer]
size = file_data['Size']
protection_class = file_data['ProtectionClass']
encryption_key = plist['$objects'][
file_data['EncryptionKey'].integer]['NS.data'][4:]
backup_filename = os.path.join(args.backup_directory,
fileID[:2], fileID)
with open(backup_filename, 'rb') as infile:
data = infile.read()
key = keybag.unwrapKeyForClass(protection_class, encryption_key)
decrypted_data = AESdecryptCBC(data, key)[:size]
print('== decrypted data:')
print(wrap(decrypted_data))
print()
print('== pretty-printed plist')
pprint.pprint(biplist.readPlistFromString(decrypted_data))
CLASSKEY_TAGS = [b"CLAS",b"WRAP",b"WPKY", b"KTYP", b"PBKY"]
KEYBAG_TYPES = ["System", "Backup", "Escrow", "OTA (icloud)"]
KEY_TYPES = ["AES", "Curve25519"]
PROTECTION_CLASSES={
1:"NSFileProtectionComplete",
2:"NSFileProtectionCompleteUnlessOpen",
3:"NSFileProtectionCompleteUntilFirstUserAuthentication",
4:"NSFileProtectionNone",
5:"NSFileProtectionRecovery?",
6: "kSecAttrAccessibleWhenUnlocked",
7: "kSecAttrAccessibleAfterFirstUnlock",
8: "kSecAttrAccessibleAlways",
9: "kSecAttrAccessibleWhenUnlockedThisDeviceOnly",
10: "kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly",
11: "kSecAttrAccessibleAlwaysThisDeviceOnly"
}
WRAP_DEVICE = 1
WRAP_PASSCODE = 2
class Keybag(object):
def __init__(self, data):
self.type = None
self.uuid = None
self.wrap = None
self.deviceKey = None
self.attrs = {}
self.classKeys = {}
self.KeyBagKeys = None
self.parseBinaryBlob(data)
def parseBinaryBlob(self, data):
currentClassKey = None
for tag, data in loopTLVBlocks(data):
if len(data) == 4:
data = struct.unpack(">L", data)[0]
if tag == b"TYPE":
self.type = data
if self.type > 3:
print("FAIL: keybag type > 3 : %d" % self.type)
elif tag == b"UUID" and self.uuid is None:
self.uuid = data
elif tag == b"WRAP" and self.wrap is None:
self.wrap = data
elif tag == b"UUID":
if currentClassKey:
self.classKeys[currentClassKey[b"CLAS"]] = currentClassKey
currentClassKey = {b"UUID": data}
elif tag in CLASSKEY_TAGS:
currentClassKey[tag] = data
else:
self.attrs[tag] = data
if currentClassKey:
self.classKeys[currentClassKey[b"CLAS"]] = currentClassKey
def unlockWithPasscode(self, passcode):
passcode1 = fastpbkdf2.pbkdf2_hmac('sha256', passcode,
self.attrs[b"DPSL"],
self.attrs[b"DPIC"], 32)
passcode_key = fastpbkdf2.pbkdf2_hmac('sha1', passcode1,
self.attrs[b"SALT"],
self.attrs[b"ITER"], 32)
print('== Passcode key')
print(anonymize(hexlify(passcode_key)))
for classkey in self.classKeys.values():
if b"WPKY" not in classkey:
continue
k = classkey[b"WPKY"]
if classkey[b"WRAP"] & WRAP_PASSCODE:
k = AESUnwrap(passcode_key, classkey[b"WPKY"])
if not k:
return False
classkey[b"KEY"] = k
return True
def unwrapKeyForClass(self, protection_class, persistent_key):
ck = self.classKeys[protection_class][b"KEY"]
if len(persistent_key) != 0x28:
raise Exception("Invalid key length")
return AESUnwrap(ck, persistent_key)
def printClassKeys(self):
print("== Keybag")
print("Keybag type: %s keybag (%d)" % (KEYBAG_TYPES[self.type], self.type))
print("Keybag version: %d" % self.attrs[b"VERS"])
print("Keybag UUID: %s" % anonymize(hexlify(self.uuid)))
print("-"*209)
print("".join(["Class".ljust(53),
"WRAP".ljust(5),
"Type".ljust(11),
"Key".ljust(65),
"WPKY".ljust(65),
"Public key"]))
print("-"*208)
for k, ck in self.classKeys.items():
if k == 6:print("")
print("".join(
[PROTECTION_CLASSES.get(k).ljust(53),
str(ck.get(b"WRAP","")).ljust(5),
KEY_TYPES[ck.get(b"KTYP",0)].ljust(11),
anonymize(hexlify(ck.get(b"KEY", b""))).ljust(65),
anonymize(hexlify(ck.get(b"WPKY", b""))).ljust(65),
]))
print()
def loopTLVBlocks(blob):
i = 0
while i + 8 <= len(blob):
tag = blob[i:i+4]
length = struct.unpack(">L",blob[i+4:i+8])[0]
data = blob[i+8:i+8+length]
yield (tag,data)
i += 8 + length
def unpack64bit(s):
return struct.unpack(">Q",s)[0]
def pack64bit(s):
return struct.pack(">Q",s)
def AESUnwrap(kek, wrapped):
C = []
for i in range(len(wrapped)//8):
C.append(unpack64bit(wrapped[i*8:i*8+8]))
n = len(C) - 1
R = [0] * (n+1)
A = C[0]
for i in range(1,n+1):
R[i] = C[i]
for j in reversed(range(0,6)):
for i in reversed(range(1,n+1)):
todec = pack64bit(A ^ (n*j+i))
todec += pack64bit(R[i])
B = Crypto.Cipher.AES.new(kek).decrypt(todec)
A = unpack64bit(B[:8])
R[i] = unpack64bit(B[8:])
if A != 0xa6a6a6a6a6a6a6a6:
return None
res = b"".join(map(pack64bit, R[1:]))
return res
ZEROIV = "\x00"*16
def AESdecryptCBC(data, key, iv=ZEROIV, padding=False):
if len(data) % 16:
print("AESdecryptCBC: data length not /16, truncating")
data = data[0:(len(data)/16) * 16]
data = Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_CBC, iv).decrypt(data)
if padding:
return removePadding(16, data)
return data
anon_random = random.Random(0)
memo = {}
def anonymize(s):
if type(s) == str:
s = s.encode('utf-8')
global anon_random, memo
if ANONYMIZE_OUTPUT:
if s in memo:
return memo[s]
possible_alphabets = [
string.digits,
string.digits + 'abcdef',
string.ascii_letters,
"".join(chr(x) for x in range(0, 256)),
]
for a in possible_alphabets:
if all((chr(c) if type(c) == int else c) in a for c in s):
alphabet = a
break
ret = "".join([anon_random.choice(alphabet) for i in range(len(s))])
memo[s] = ret
return ret
else:
return s
def wrap(s, width=78):
"Return a width-wrapped repr(s)-like string without breaking on \’s"
s = repr(s)
quote = s[0]
s = s[1:-1]
ret = []
while len(s):
i = s.rfind('\\', 0, width)
if i <= width - 4:
i = width
ret.append(s[:i])
s = s[i:]
return '\n'.join("%s%s%s" % (quote, line ,quote) for line in ret)
def readpipe(path):
if stat.S_ISFIFO(os.stat(path).st_mode):
with open(path, 'rb') as pipe:
return pipe.read()
else:
raise Exception("Not a pipe: {!r}".format(path))
if __name__ == '__main__':
main()
ซึ่งจะพิมพ์ผลลัพธ์นี้:
Warning: All output keys are FAKE to protect your privacy
== Keybag
Keybag type: Backup keybag (1)
Keybag version: 3
Keybag UUID: dc6486c479e84c94efce4bea7169ef7d
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Class WRAP Type Key WPKY Public key
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
NSFileProtectionComplete 2 AES 4c80b6da07d35d393fc7158e18b8d8f9979694329a71ceedee86b4cde9f97afec197ad3b13c5d12b
NSFileProtectionCompleteUnlessOpen 2 AES 09e8a0a9965f00f213ce06143a52801f35bde2af0ad54972769845d480b5043f545fa9b66a0353a6
NSFileProtectionCompleteUntilFirstUserAuthentication 2 AES e966b6a0742878ce747cec3fa1bf6a53b0d811ad4f1d6147cd28a5d400a8ffe0bbabea5839025cb5
NSFileProtectionNone 2 AES 902f46847302816561e7df57b64beea6fa11b0068779a65f4c651dbe7a1630f323682ff26ae7e577
NSFileProtectionRecovery? 3 AES a3935fed024cd9bc11d0300d522af8e89accfbe389d7c69dca02841df46c0a24d0067dba2f696072
kSecAttrAccessibleWhenUnlocked 2 AES 09a1856c7e97a51a9c2ecedac8c3c7c7c10e7efa931decb64169ee61cb07a0efb115050fd1e33af1
kSecAttrAccessibleAfterFirstUnlock 2 AES 0509d215f2f574efa2f192efc53c460201168b26a175f066b5347fc48bc76c637e27a730b904ca82
kSecAttrAccessibleAlways 2 AES b7ac3c4f1e04896144ce90c4583e26489a86a6cc45a2b692a5767b5a04b0907e081daba009fdbb3c
kSecAttrAccessibleWhenUnlockedThisDeviceOnly 3 AES 417526e67b82e7c6c633f9063120a299b84e57a8ffee97b34020a2caf6e751ec5750053833ab4d45
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly 3 AES b0e17b0cf7111c6e716cd0272de5684834798431c1b34bab8d1a1b5aba3d38a3a42c859026f81ccc
kSecAttrAccessibleAlwaysThisDeviceOnly 3 AES 9b3bdc59ae1d85703aa7f75d49bdc600bf57ba4a458b20a003a10f6e36525fb6648ba70e6602d8b2
== Passcode key
ee34f5bb635830d698074b1e3e268059c590973b0f1138f1954a2a4e1069e612
== Keybag
Keybag type: Backup keybag (1)
Keybag version: 3
Keybag UUID: dc6486c479e84c94efce4bea7169ef7d
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Class WRAP Type Key WPKY Public key
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
NSFileProtectionComplete 2 AES 64e8fc94a7b670b0a9c4a385ff395fe9ba5ee5b0d9f5a5c9f0202ef7fdcb386f 4c80b6da07d35d393fc7158e18b8d8f9979694329a71ceedee86b4cde9f97afec197ad3b13c5d12b
NSFileProtectionCompleteUnlessOpen 2 AES 22a218c9c446fbf88f3ccdc2ae95f869c308faaa7b3e4fe17b78cbf2eeaf4ec9 09e8a0a9965f00f213ce06143a52801f35bde2af0ad54972769845d480b5043f545fa9b66a0353a6
NSFileProtectionCompleteUntilFirstUserAuthentication 2 AES 1004c6ca6e07d2b507809503180edf5efc4a9640227ac0d08baf5918d34b44ef e966b6a0742878ce747cec3fa1bf6a53b0d811ad4f1d6147cd28a5d400a8ffe0bbabea5839025cb5
NSFileProtectionNone 2 AES 2e809a0cd1a73725a788d5d1657d8fd150b0e360460cb5d105eca9c60c365152 902f46847302816561e7df57b64beea6fa11b0068779a65f4c651dbe7a1630f323682ff26ae7e577
NSFileProtectionRecovery? 3 AES 9a078d710dcd4a1d5f70ea4062822ea3e9f7ea034233e7e290e06cf0d80c19ca a3935fed024cd9bc11d0300d522af8e89accfbe389d7c69dca02841df46c0a24d0067dba2f696072
kSecAttrAccessibleWhenUnlocked 2 AES 606e5328816af66736a69dfe5097305cf1e0b06d6eb92569f48e5acac3f294a4 09a1856c7e97a51a9c2ecedac8c3c7c7c10e7efa931decb64169ee61cb07a0efb115050fd1e33af1
kSecAttrAccessibleAfterFirstUnlock 2 AES 6a4b5292661bac882338d5ebb51fd6de585befb4ef5f8ffda209be8ba3af1b96 0509d215f2f574efa2f192efc53c460201168b26a175f066b5347fc48bc76c637e27a730b904ca82
kSecAttrAccessibleAlways 2 AES c0ed717947ce8d1de2dde893b6026e9ee1958771d7a7282dd2116f84312c2dd2 b7ac3c4f1e04896144ce90c4583e26489a86a6cc45a2b692a5767b5a04b0907e081daba009fdbb3c
kSecAttrAccessibleWhenUnlockedThisDeviceOnly 3 AES 80d8c7be8d5103d437f8519356c3eb7e562c687a5e656cfd747532f71668ff99 417526e67b82e7c6c633f9063120a299b84e57a8ffee97b34020a2caf6e751ec5750053833ab4d45
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly 3 AES a875a15e3ff901351c5306019e3b30ed123e6c66c949bdaa91fb4b9a69a3811e b0e17b0cf7111c6e716cd0272de5684834798431c1b34bab8d1a1b5aba3d38a3a42c859026f81ccc
kSecAttrAccessibleAlwaysThisDeviceOnly 3 AES 1e7756695d337e0b06c764734a9ef8148af20dcc7a636ccfea8b2eb96a9e9373 9b3bdc59ae1d85703aa7f75d49bdc600bf57ba4a458b20a003a10f6e36525fb6648ba70e6602d8b2
== decrypted data:
'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD '
'PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist versi'
'on="1.0">\n<dict>\n\t<key>DCIMLastDirectoryNumber</key>\n\t<integer>100</integ'
'er>\n\t<key>DCIMLastFileNumber</key>\n\t<integer>3</integer>\n</dict>\n</plist'
'>\n'
== pretty-printed plist
{'DCIMLastDirectoryNumber': 100, 'DCIMLastFileNumber': 3}
สินเชื่อพิเศษ
รหัสป้องกันข้อมูลiphone ที่โพสต์โดยBédruneและ Sigwald สามารถถอดรหัสพวงกุญแจจากข้อมูลสำรองรวมถึงสิ่งสนุก ๆ เช่น wifi ที่บันทึกไว้และรหัสผ่านเว็บไซต์:
$ python iphone-dataprotection/python_scripts/keychain_tool.py ...
--------------------------------------------------------------------------------------
| Passwords |
--------------------------------------------------------------------------------------
|Service |Account |Data |Access group |Protection class|
--------------------------------------------------------------------------------------
|AirPort |Ed’s Coffee Shop |<3FrenchRoast |apple |AfterFirstUnlock|
...
รหัสดังกล่าวใช้ไม่ได้ในการสำรองข้อมูลจากโทรศัพท์ที่ใช้ iOS ล่าสุดอีกต่อไป แต่มีพอร์ตgolang บางพอร์ตที่ได้รับการอัปเดตอยู่เสมอเพื่อให้สามารถเข้าถึงพวงกุญแจได้