如何使用python IOS 10.1.1解密和加密iphone备份

如何使用python IOS 10.1.1解密和加密iphone备份,python,ios,iphone,encryption,Python,Ios,Iphone,Encryption,根据苹果和MDM对备份强制加密的要求,有没有办法对iPhone备份进行解密和加密 我们需要访问保存在备份中的原始文件,以便对其中的几个plist文件进行轻微更改,但问题是,我们需要在代码中执行此操作,因为手动执行此操作将永远需要1000多部手机。我的大部分代码都是用python编写的,编辑plist似乎工作得很好,直到我们加入mdm配置文件,导致手机从那时起需要加密备份 以下是我到目前为止发现的:但无法做到这一点,因为10>将文件从mbdb更改为db文件。从其他人那里读到,这似乎也改变了文件的加

根据苹果和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”]
打印“钥匙袋迭代”