如何使用python IOS 10.1.1解密和加密iphone备份
根据苹果和MDM对备份强制加密的要求,有没有办法对iPhone备份进行解密和加密 我们需要访问保存在备份中的原始文件,以便对其中的几个plist文件进行轻微更改,但问题是,我们需要在代码中执行此操作,因为手动执行此操作将永远需要1000多部手机。我的大部分代码都是用python编写的,编辑plist似乎工作得很好,直到我们加入mdm配置文件,导致手机从那时起需要加密备份 以下是我到目前为止发现的:但无法做到这一点,因为10>将文件从mbdb更改为db文件。从其他人那里读到,这似乎也改变了文件的加密方式。这方面的任何帮助或更新都将非常有用如何使用python IOS 10.1.1解密和加密iphone备份,python,ios,iphone,encryption,Python,Ios,Iphone,Encryption,根据苹果和MDM对备份强制加密的要求,有没有办法对iPhone备份进行解密和加密 我们需要访问保存在备份中的原始文件,以便对其中的几个plist文件进行轻微更改,但问题是,我们需要在代码中执行此操作,因为手动执行此操作将永远需要1000多部手机。我的大部分代码都是用python编写的,编辑plist似乎工作得很好,直到我们加入mdm配置文件,导致手机从那时起需要加密备份 以下是我到目前为止发现的:但无法做到这一点,因为10>将文件从mbdb更改为db文件。从其他人那里读到,这似乎也改变了文件的加
#!/usr/bin/env python2.7
# coding: UTF-8
# default to True to avoid leaking secrets
ANONYMIZE_OUTPUT = True
import PBKDF2 # http://iphone-dataprotection.googlecode.com/hg-history/tip/python_scripts/crypto/PBKDF2.py
import bplist # https://github.com/farcaller/bplist-python/raw/master/bplist.py
import Crypto.Cipher.AES # https://www.dlitz.net/software/pycrypto/
import hashlib
import os.path
import pprint
import sys
BACKUP_DIR = "data/encrypted"
def main():
with open(os.path.join(BACKUP_DIR, 'Manifest.plist'), 'rb') as infile:
manifest_plist = bplist.BPlistReader.plistWithString(infile.read())
keybag = Keybag(manifest_plist['BackupKeyBag'])
# the actual keys are unknown, but the wrapped keys are known
keybag.printClassKeys()
if not keybag.unlockWithPasscode('test'):
raise Exception('Could not unlock keybag; bad password?')
# now the keys are known too
keybag.printClassKeys()
for item in process_mbdb_file(
os.path.join(BACKUP_DIR, 'Manifest.mbdb')).values():
filename = item['filename']
if not filename.endswith('calculator.plist'):
continue
encryption_key = item['unknown1'][4:]
protection_class = item['flag']
backup_filename = os.path.join(
BACKUP_DIR,
hashlib.sha1(item['domain'] + '-' + item['filename']).hexdigest())
with open(backup_filename, 'rb') as infile:
data = infile.read()
print '== encrypted data:'
print wrap(data)
print
key = keybag.unwrapKeyForClass(protection_class, encryption_key)
# truncate to actual length, because encryption may introduce padding
decrypted_data = AESdecryptCBC(data, key)[:item['filelen']]
print '== decrypted data:'
print wrap(decrypted_data)
print
print '== pretty-printed calculator preferences'
pprint.pprint(bplist.BPlistReader.plistWithString(decrypted_data))
##
# this section is mostly copied from parts of iphone-dataprotection
# http://code.google.com/p/iphone-dataprotection/
import struct
CLASSKEY_TAGS = ["CLAS","WRAP","WPKY", "KTYP", "PBKY"] #UUID
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 #DATASIGN blob
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 == "TYPE":
self.type = data
if self.type > 3:
print "FAIL: keybag type > 3 : %d" % self.type
elif tag == "UUID" and self.uuid is None:
self.uuid = data
elif tag == "WRAP" and self.wrap is None:
self.wrap = data
elif tag == "UUID":
if currentClassKey:
self.classKeys[currentClassKey["CLAS"]] = currentClassKey
currentClassKey = {"UUID": data}
elif tag in CLASSKEY_TAGS:
currentClassKey[tag] = data
else:
self.attrs[tag] = data
if currentClassKey:
self.classKeys[currentClassKey["CLAS"]] = currentClassKey
def unlockWithPasscode(self, passcode):
passcodekey = PBKDF2.PBKDF2(passcode, self.attrs["SALT"],
iterations=self.attrs["ITER"]).read(32)
for classkey in self.classKeys.values():
if not classkey.has_key("WPKY"):
continue
k = classkey["WPKY"]
if classkey["WRAP"] & WRAP_PASSCODE:
k = AESUnwrap(passcodekey, classkey["WPKY"])
if not k:
return False
classkey["KEY"] = k
return True
def unwrapKeyForClass(self, protection_class, persistent_key):
ck = self.classKeys[protection_class]["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["VERS"]
print "Keybag iterations: %d, iv=%s" % (
self.attrs["ITER"], anonymize(self.attrs["SALT"].encode('hex')))
print "Keybag UUID: %s" % anonymize(self.uuid.encode("hex"))
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("WRAP","")).ljust(5),
KEY_TYPES[ck.get("KTYP",0)].ljust(11),
anonymize(ck.get("KEY", "").encode("hex")).ljust(65),
anonymize(ck.get("WPKY", "").encode("hex")).ljust(65),
ck.get("PBKY", "").encode("hex")])
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 xrange(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 xrange(1,n+1):
R[i] = C[i]
for j in reversed(xrange(0,6)):
for i in reversed(xrange(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 = "".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
##
# this .mbdb-parsing code is from http://stackoverflow.com/q/3085153/14558:
def getint(data, offset, intsize):
"""Retrieve an integer (big-endian) and new offset from the current offset"""
value = 0
while intsize > 0:
value = (value<<8) + ord(data[offset])
offset = offset + 1
intsize = intsize - 1
return value, offset
def getstring(data, offset):
"""Retrieve a string and new offset from the current offset into the data"""
if data[offset] == chr(0xFF) and data[offset+1] == chr(0xFF):
return '', offset+2 # Blank string
length, offset = getint(data, offset, 2) # 2-byte length
value = data[offset:offset+length]
return value, (offset + length)
def process_mbdb_file(filename):
mbdb = {} # Map offset of info in this file => file info
data = open(filename).read()
if data[0:4] != "mbdb": raise Exception("This does not look like an MBDB file")
offset = 4
offset = offset + 2 # value x05 x00, not sure what this is
while offset < len(data):
fileinfo = {}
fileinfo['start_offset'] = offset
fileinfo['domain'], offset = getstring(data, offset)
fileinfo['filename'], offset = getstring(data, offset)
fileinfo['linktarget'], offset = getstring(data, offset)
fileinfo['datahash'], offset = getstring(data, offset)
fileinfo['unknown1'], offset = getstring(data, offset)
fileinfo['mode'], offset = getint(data, offset, 2)
fileinfo['unknown2'], offset = getint(data, offset, 4)
fileinfo['unknown3'], offset = getint(data, offset, 4)
fileinfo['userid'], offset = getint(data, offset, 4)
fileinfo['groupid'], offset = getint(data, offset, 4)
fileinfo['mtime'], offset = getint(data, offset, 4)
fileinfo['atime'], offset = getint(data, offset, 4)
fileinfo['ctime'], offset = getint(data, offset, 4)
fileinfo['filelen'], offset = getint(data, offset, 8)
fileinfo['flag'], offset = getint(data, offset, 1)
fileinfo['numprops'], offset = getint(data, offset, 1)
fileinfo['properties'] = {}
for ii in range(fileinfo['numprops']):
propname, offset = getstring(data, offset)
propval, offset = getstring(data, offset)
fileinfo['properties'][propname] = propval
mbdb[fileinfo['start_offset']] = fileinfo
return mbdb
##
# and here are some utility functions, one making sure I don’t leak my
# secret keys when posting the output on Stack Exchange
if ANONYMIZE_OUTPUT:
memo = {}
def anonymize(s):
global memo
if s in memo:
return memo[s]
import random
import string
r = random.Random(0)
possible_alphabets = [
string.digits,
string.digits + 'abcdef',
"".join(chr(x) for x in range(0, 256)),
]
for a in possible_alphabets:
if all(c in a for c in s):
alphabet = a
break
ret = "".join([r.choice(alphabet) for i in range(len(s))])
memo[s] = ret
return ret
else:
def anonymize(s): 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: # "\x??" is four characters
i = width
ret.append(s[:i])
s = s[i:]
return '\n'.join("%s%s%s" % (quote, line ,quote) for line in ret)
if __name__ == '__main__':
main()
#/usr/bin/env python2.7
#编码:UTF-8
#默认为True以避免泄漏机密
匿名化_输出=True
导入PBKDF2#http://iphone-dataprotection.googlecode.com/hg-history/tip/python_scripts/crypto/PBKDF2.py
导入bplist#https://github.com/farcaller/bplist-python/raw/master/bplist.py
导入Crypto.Cipher.AES#https://www.dlitz.net/software/pycrypto/
导入hashlib
导入操作系统路径
导入pprint
导入系统
备份\u DIR=“数据/加密”
def main():
使用open(os.path.join(BACKUP_DIR,'Manifest.plist'),'rb')作为内嵌:
manifest_plist=bplist.BPlistReader.plistWithString(infle.read())
keybag=keybag(manifest_plist['BackupKeyBag'])
#实际关键点未知,但包装的关键点已知
keybag.printClassKeys()
如果不是钥匙袋,则使用密码(“测试”)解锁:
引发异常('无法解锁钥匙袋;密码错误?')
#现在钥匙也知道了
keybag.printClassKeys()
对于进程中的项目\u mbdb\u文件(
join(BACKUP_DIR,'Manifest.mbdb')).values():
filename=项['filename']
如果不是filename.endswith('calculator.plist'):
持续
加密密钥=项['unknown1'][4:]
保护等级=项目['flag']
备份\u文件名=os.path.join(
后备主任,
hashlib.sha1(项['domain']+'-'+项['filename'])。hexdigest()
打开(备份文件名为“rb”)作为填充:
data=infle.read()
打印'==加密数据:'
打印包装(数据)
打印
key=keybag.unwrapKeyForClass(保护类、加密类、密钥)
#截断为实际长度,因为加密可能会引入填充
decrypted_data=AES DecryptedCBC(数据,密钥)[:item['filelen']]
打印'==解密数据:'
打印换行(解密的_数据)
打印
打印“==漂亮打印的计算器首选项”
pprint.pprint(bplist.BPlistReader.plistWithString(解密的_数据))
##
#本节主要是从iphone数据保护部分复制而来
# http://code.google.com/p/iphone-dataprotection/
导入结构
CLASSKEY_标签=[“CLAS”、“WRAP”、“WPKY”、“KTYP”、“PBKY”]#UUID
密钥包类型=[“系统”、“备份”、“托管”、“OTA(icloud)”]
键类型=[“AES”,“曲线25519”]
保护等级={
1:“NSFileProtectionComplete”,
2:“NSFileProtectionCompleteUnlessOpen”,
3:“NSFileProtectionCompleteUntilFirstUserAuthentication”,
4:“NSFileProtectionNone”,
5:“NSFileProtectionRecovery?”,
6:“未锁定时可访问ksecattracible”,
7:“kSecAttrAccessibleAfterFirstUnlock”,
8:“KSecAttracibleAllways”,
9:“仅当未锁定此设备时,才能访问KSecatac”,
10:“ksecattracibleafterfirst仅解锁此设备”,
11:“仅限此设备可访问的KSecattraceallways”
}
包装设备=1
包裹\u密码=2
类密钥包(对象):
定义初始化(自身,数据):
self.type=None
self.uuid=None
self.wrap=无
self.deviceKey=None
self.attrs={}
self.classKeys={}
self.KeyBagKeys=None#DATASIGN blob
self.parseBinaryBlob(数据)
def parseBinaryBlob(自身,数据):
currentClassKey=None
对于标记,LoopTlvBlock中的数据(数据):
如果len(数据)==4:
数据=结构解包(“>L”,数据)[0]
如果标记==“类型”:
self.type=数据
如果self.type>3:
打印“失败:钥匙袋类型>3:%d”%self.type
elif标记==“UUID”且self.UUID为无:
self.uuid=数据
elif标记==“WRAP”且self.WRAP为无:
self.wrap=数据
elif标记==“UUID”:
如果currentClassKey:
self.classkey[currentClassKey[“CLAS”]=currentClassKey
currentClassKey={“UUID”:数据}
CLASSKEY_标记中的elif标记:
currentClassKey[tag]=数据
其他:
self.attrs[tag]=数据
如果currentClassKey:
self.classkey[currentClassKey[“CLAS”]=currentClassKey
def解锁密码(self,passcode):
passcodekey=PBKDF2.PBKDF2(密码,self.attrs[“SALT”],
迭代次数=self.attrs[“ITER”]).read(32)
对于self.classKeys.values()中的类键:
如果不是classkey.has_key(“WPKY”):
持续
k=类键[“WPKY”]
如果classkey[“WRAP”]&WRAP_密码:
k=AESWUNRAP(密码键,类键[“WPKY”])
如果不是k:
返回错误
classkey[“KEY”]=k
返回真值
def unwrapKeyForClass(自我保护类、永久性密钥):
ck=self.classKeys[protection_class][“KEY”]
如果len(持续_键)!=0x28:
引发异常(“无效密钥长度”)
返回展开(ck,持续_键)
def printClassKeys(自):
打印“==钥匙袋”
打印“钥匙袋类型:%s钥匙袋(%d)”%(钥匙袋类型[self.type],self.type)
打印“钥匙袋版本:%d”%self.attrs[“VERS”]
打印“钥匙袋迭代”