├── .flake8 ├── .github └── dependabot.yml ├── .gitignore ├── .pre-commit-config.yaml ├── 66-pongos.rules ├── LICENSE ├── README.md ├── autodecrypt ├── __init__.py ├── decrypt_img.py ├── fw_utils.py ├── ipsw_dl.py ├── ipsw_utils.py ├── main.py ├── pongo.py ├── scrapkeys.py └── utils.py ├── poetry.lock ├── pyproject.toml └── tests └── autodecrypt.sh /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 80 3 | ignore = 4 | per-file-ignores = __init__.py:F401 5 | exclude = 6 | .git 7 | env 8 | etna_cli.egg-info 9 | LICENSE 10 | poetry.lock 11 | __pycache__ 12 | pyproject.toml 13 | README.md 14 | build 15 | dist 16 | tests/ 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .eggs 3 | 4 | *.img3 5 | *.im4p 6 | *.dec 7 | env 8 | .vscode 9 | *.log 10 | 11 | # folders generated by setup.py 12 | autodecrypt.egg-info 13 | build 14 | dist 15 | 16 | # file downloaded by drone exec 17 | img4lib 18 | kernel* 19 | ibo* 20 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v2.5.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-docstring-first 8 | - repo: https://gitlab.com/pycqa/flake8 9 | rev: '3.8.3' 10 | hooks: 11 | - id: flake8 12 | args: ['--per-file-ignores=__init__.py:F401'] 13 | - repo: https://github.com/asottile/reorder_python_imports 14 | rev: v2.3.6 15 | hooks: 16 | - id: reorder-python-imports 17 | args: [--py3-plus] 18 | - repo: https://github.com/psf/black 19 | rev: 20.8b1 20 | hooks: 21 | - id: black 22 | args: ['-S', '-l 79'] 23 | -------------------------------------------------------------------------------- /66-pongos.rules: -------------------------------------------------------------------------------- 1 | # Handle PongoOS 2 | ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="05ac", ATTR{idProduct}=="4141", OWNER="root", GROUP="plugdev", MODE="0660" 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2021 Mathieu Hautebas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # autodecrypt 2 | [![PyPI version](https://badge.fury.io/py/autodecrypt.svg)](https://badge.fury.io/py/autodecrypt) 3 | 4 | Simple tool to decrypt iOS firmware images. 5 | 6 | Going to the iPhone wiki and copying and pasting firmware keys to your terminal is boring. 7 | 8 | autodecrypt will grab keys for you and decrypt the firmware image you want. 9 | 10 | ## Usage 11 | ``` 12 | Usage: autodecrypt [OPTIONS] 13 | 14 | Options: 15 | -f, --filename TEXT File [required] 16 | -d, --device TEXT Device [required] 17 | -i, --ios_version TEXT iOS version 18 | -b, --build TEXT Build ID of iOS version 19 | -k, --ivkey TEXT IV and key to decrypt file 20 | -l, --local Use path to local file 21 | -D, --download Download file 22 | -B, --beta Specify that it is a beta firmware 23 | -P, --pongo Use PongoOS over USB for decryption 24 | --install-completion [bash|zsh|fish|powershell|pwsh] 25 | Install completion for the specified shell. 26 | --show-completion [bash|zsh|fish|powershell|pwsh] 27 | Show completion for the specified shell, to 28 | copy it or customize the installation. 29 | --help Show this message and exit. 30 | ``` 31 | 32 | ## Dependencies 33 | - [img4](https://github.com/xerub/img4lib) 34 | 35 | To run autodecrypt, use poetry with a virtualenv: 36 | - `virtualenv -p python3 env` 37 | - `pip3 install poetry` 38 | - `poetry install` 39 | 40 | 41 | ## Installation 42 | `pip3 install autodecrypt` 43 | 44 | 45 | ## Examples 46 | 47 | #### Download and decrypt iBSS using keys from theiphonewiki 48 | ``` 49 | » autodecrypt -f iBSS.iphone6.RELEASE.im4p -i 10.3.3 -d iPhone6,2 50 | [i] downloading iBSS.iphone6.RELEASE.im4p 51 | [i] image : ibss 52 | [i] grabbing keys for iPhone6,2/14G60 53 | [x] iv : f2aa35f6e27c409fd57e9b711f416cfe 54 | [x] key : 599d9b18bc51d93f2385fa4e83539a2eec955fce5f4ae960b252583fcbebfe75 55 | [i] decrypting iBSS.iphone6.RELEASE.im4p to iBSS.iphone6.RELEASE.bin... 56 | [x] done 57 | ``` 58 | 59 | #### Download and decrypt SEP firmware by specifying keys 60 | ``` 61 | » autodecrypt -f sep-firmware.n841.RELEASE.im4p -b 17C5053a -d iPhone11,8 -k 9f974f1788e615700fec73006cc2e6b533b0c6c2b8cf653bdbd347bc1897bdd66b11815f036e94c951250c4dda916c00 62 | [i] downloading sep-firmware.n841.RELEASE.im4p 63 | [x] iv : 9f974f1788e615700fec73006cc2e6b5 64 | [x] key : 33b0c6c2b8cf653bdbd347bc1897bdd66b11815f036e94c951250c4dda916c00 65 | [i] decrypting sep-firmware.n841.RELEASE.im4p to sep-firmware.n841.RELEASE.bin... 66 | [x] done 67 | ``` 68 | 69 | #### Use [foreman](https://github.com/GuardianFirewall/foreman) instance to grab firmware keys 70 | ``` 71 | » export FOREMAN_HOST="https://foreman-public.sudosecuritygroup.com" 72 | » autodecrypt -f LLB.n112.RELEASE.im4p -i 13.2.3 -d iPod9,1 73 | [i] downloading LLB.n112.RELEASE.im4p 74 | [i] image : llb 75 | [i] grabbing keys for iPod9,1/17B111 76 | [i] grabbing keys from https://foreman-public.sudosecuritygroup.com 77 | [x] iv : 85784a219eb29bcb1cc862de00a590e7 78 | [x] key : f539c51a7f3403d90c9bdc62490f6b5dab4318f4633269ce3fbbe855b33a4bc7 79 | [i] decrypting LLB.n112.RELEASE.im4p to LLB.n112.RELEASE.bin... 80 | [x] done 81 | ``` 82 | 83 | #### Decrypt keys with PongoOS 84 | > I you wanna use this on Linux, you may have some USB permissions. I recommend copying the file `66-pongos.rules` available on this repository to `/etc/udev/rules.d/`. 85 | 86 | ``` 87 | » autodecrypt -f iBoot.n71.RELEASE.im4p -d iPhone8,1 -i 14.1 -p 88 | [i] downloading iBoot.n71.RELEASE.im4p 89 | [i] grabbing keys from PongoOS device 90 | [i] kbag : 03C9E01CA99FE6475566C791777169C0625B04B7BD4E593DE0F61ABF4E8DB1A987D9D6155C5A1F41D9113694658AC61C 91 | [x] iv : 245a9b52e53a704fe73d7b58734b00ae 92 | [x] key : d3aa3c8cc20fa9d61e466f46aee374a92a125abb5b3f57264025c8c72127e321 93 | [i] decrypting iBoot.n71.RELEASE.im4p to iBoot.n71.RELEASE.bin... 94 | [x] done 95 | ``` 96 | 97 | #### Log 98 | 99 | For debugging purposes you can check `autodecrypt.log` : 100 | ``` 101 | 11/02/2019 21:39:41 Launching "['autodecrypt/autodecrypt.py', '-d', 'iPhone9,3', '-f', 'iBoot.d10.RELEASE.im4p', '-i', '12.3.1']" 102 | 11/02/2019 21:39:41 requesting IPSW's API for iPhone9,3 103 | 11/02/2019 21:39:41 done, now looking for version or build 104 | 11/02/2019 21:39:41 grabbing firmware codename for 16F203 105 | 11/02/2019 21:39:42 codename : PeaceF 106 | 11/02/2019 21:39:42 grabbing IPSW file URL for iPhone9,3/12.3.1 107 | 11/02/2019 21:39:42 downloading iBoot... 108 | 11/02/2019 21:39:43 img4 -i iBoot.d10.RELEASE.im4p iBoot.d10.RELEASE.bin 978fd4680cd4b624b0dfea22a417f51f0ee2b871defed42277fe18885053b1eb5c7ffe82f38ab8cf7772c69a0db5d386 109 | ``` 110 | 111 | 112 | ### Credits 113 | - checkra1n team for AES patches, kbag.m and [PongoOS](https://github.com/checkra1n/pongoos) 114 | - tihmstar for wiki parsing ([my method](https://github.com/matteyeux/ios-tools/blob/master/scrapkeys.py) was pretty bad) 115 | - m1stadev for [PyIMG4](https://github.com/m1stadev/PyIMG4) 116 | -------------------------------------------------------------------------------- /autodecrypt/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matteyeux/autodecrypt/15cb32927c1be8a8ce583e2c0a742561b43f274b/autodecrypt/__init__.py -------------------------------------------------------------------------------- /autodecrypt/decrypt_img.py: -------------------------------------------------------------------------------- 1 | """Module used for decrypting and extracting img4 file format""" 2 | import os 3 | import sys 4 | import subprocess 5 | import pyimg4 6 | from pyimg4 import Compression, Keybag 7 | 8 | 9 | def decrypt_img(infile: str, magic: str, iv: str, key: str) -> int: 10 | """Decrypt IM4P file. This code is mostly copy/pasta from PyIMG4.""" 11 | file = open(infile, 'rb').read() 12 | im4p = pyimg4.IM4P(file) 13 | 14 | if im4p.payload.encrypted is False: 15 | print("[i] payload is not encrypted") 16 | return 0 17 | 18 | if iv is None or key is None: 19 | print("[e] iv or key is None") 20 | return -1 21 | 22 | outfile = infile.replace("im4p", "bin") 23 | print(f"[i] decrypting {infile} to {outfile}...") 24 | 25 | im4p.payload.decrypt(Keybag(key=key, iv=iv)) 26 | if im4p.payload.compression != Compression.NONE: 27 | print('[i] image4 payload data is compressed, decompressing...') 28 | im4p.payload.decompress() 29 | 30 | open(outfile, 'wb').write(im4p.payload.output().data) 31 | return 0 32 | 33 | 34 | def get_image_type(filename: str): 35 | """Check if it is IM4P format.""" 36 | if not os.path.isfile(filename): 37 | print("[e] %s : file not found" % filename) 38 | sys.exit(-1) 39 | with open(filename, "rb") as file: 40 | file.seek(7, 0) 41 | magic = file.read(4).decode() 42 | if "M4P" in magic: 43 | if magic != "IM4P": 44 | file.seek(-1, os.SEEK_CUR) 45 | magic = "img4" 46 | else: 47 | return None 48 | file.seek(2, os.SEEK_CUR) 49 | img_type = file.read(4) 50 | return magic, img_type 51 | 52 | 53 | def get_kbag(firmware_image: str) -> str: 54 | """Return kbag of img4 file.""" 55 | out = subprocess.check_output(["img4", "-i", firmware_image, "-b"]) 56 | kbag = out.split()[0].decode("UTF-8") 57 | return kbag 58 | -------------------------------------------------------------------------------- /autodecrypt/fw_utils.py: -------------------------------------------------------------------------------- 1 | """Module to deal with ipsw files.""" 2 | import os 3 | import shutil 4 | import sys 5 | 6 | import requests 7 | from bs4 import BeautifulSoup 8 | from remotezip import RemoteZip 9 | 10 | 11 | def grab_file(url: str, filename: str) -> str: 12 | """Partialzip file from remote server.""" 13 | if "developer.apple.com" in url: 14 | return None 15 | with RemoteZip(url) as zipfile: 16 | filenames = zipfile.namelist() 17 | for fname in filenames: 18 | zinfo = zipfile.getinfo(fname) 19 | if filename in zinfo.filename and ".plist" not in zinfo.filename: 20 | filename = zinfo.filename.split("/")[-1] 21 | print("[i] downloading %s" % filename) 22 | extract_and_clean(zipfile, zinfo.filename, filename) 23 | return filename 24 | return filename 25 | 26 | 27 | def extract_and_clean(zipper: str, zip_path: str, filename: str): 28 | """ 29 | Clean partialziped file and put it at 30 | the root of the current dir. 31 | """ 32 | zipper.extract(zip_path) 33 | if "/" in zip_path: 34 | os.rename(zip_path, filename) 35 | shutil.rmtree(zip_path.split("/")[0]) 36 | 37 | 38 | def get_json_data(model: str, fw_type: str = None) -> dict: 39 | """Setup json data to parse.""" 40 | url = f"https://api.ipsw.me/v4/device/{model}" 41 | if fw_type == "ota": 42 | url += "?type=ota" 43 | resp = requests.get(url=url) 44 | return resp.json() 45 | 46 | 47 | def get_firmware_url(json_data: dict, buildid: str) -> str: 48 | """Return URL of IPSW file.""" 49 | for firmware in json_data["firmwares"]: 50 | if firmware["buildid"] == buildid: 51 | return firmware["url"] 52 | return None 53 | 54 | 55 | def get_board_config(json_data: dict) -> str: 56 | """Return boardconfig of said device""" 57 | return json_data["boardconfig"] 58 | 59 | 60 | def get_build_id(json_data: dict, ios_vers: str, fw_type: str = "ipsw") -> str: 61 | """Return build ID of iOS version.""" 62 | release = "" 63 | if ios_vers is None: 64 | print("[e] no iOS version specified") 65 | return sys.exit(1) 66 | 67 | for firmware in json_data["firmwares"]: 68 | curent_vers = firmware["version"] 69 | if fw_type == "ota": 70 | release = firmware["releasetype"] 71 | 72 | if ios_vers == curent_vers and release == "": 73 | return firmware["buildid"] 74 | 75 | 76 | def get_ios_vers(json_data: dict, buildid: str) -> str: 77 | """Return iOS version of build ID.""" 78 | if json_data is None: 79 | return None 80 | for firmware in json_data["firmwares"]: 81 | if firmware["buildid"] == buildid: 82 | return firmware["version"] 83 | return None 84 | 85 | 86 | def get_build_list(json_data: dict) -> list: 87 | """Return a list of build IDs for a device""" 88 | builds = [] 89 | for firmware in json_data["firmwares"]: 90 | builds.append(firmware["buildid"]) 91 | return builds 92 | 93 | 94 | def get_device(model: str) -> list: 95 | """Get device according to its model. 96 | This function returns a list because there are multiple section for iPads. 97 | """ 98 | device = [] 99 | if "AppleTV" in model: 100 | device = "Apple_TV" 101 | elif "Watch" in model: 102 | device = "Apple_Watch" 103 | elif "AudioAccessory" in model: 104 | device = "HomePod" 105 | elif "Mac" in model: 106 | device = "Mac" 107 | elif "iPad" in model: 108 | device = ["iPad", "iPad_Air", "iPad_Pro", "iPad_mini"] 109 | elif "iPod" in model: 110 | device = "iPod_touch" 111 | return device 112 | 113 | 114 | def get_beta_url(model: str, build: str, version: str): 115 | """Get iOS beta URL from theiphonewiki.""" 116 | 117 | major = f"{version.split('.')[0]}.x" 118 | matches = [build, 'Restore'] 119 | 120 | device = get_device(model) 121 | for dev in device: 122 | # URL should look like : 123 | # https://www.theiphonewiki.com/wiki/Beta_Firmware/iPhone/14.x 124 | wiki_url = ( 125 | f"https://www.theiphonewiki.com/wiki/Beta_Firmware/{dev}/{major}" 126 | ) 127 | html_text = requests.get(wiki_url).text 128 | soup = BeautifulSoup(html_text, "html.parser") 129 | found = False 130 | for link in soup.find_all('a'): 131 | href = link.get("href") 132 | if href is None: 133 | continue 134 | if model in href: 135 | found = True 136 | 137 | if found is True and all(x in href for x in matches): 138 | return href 139 | return None 140 | -------------------------------------------------------------------------------- /autodecrypt/ipsw_dl.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import requests 4 | import json 5 | from urllib.request import urlopen 6 | from clint.textui import progress 7 | 8 | 9 | def dl(url, filename, sizeofile=0): 10 | """download IPSW file""" 11 | if sizeofile == 0: 12 | dl_file = urlopen(url) 13 | with open(filename, 'wb') as output: 14 | output.write(dl_file.read()) 15 | else: 16 | dl_file = requests.get(url, stream=True) 17 | with open(filename, 'wb') as output: 18 | for chunk in progress.bar( 19 | dl_file.iter_content(chunk_size=1024), 20 | expected_size=(sizeofile / 1024) + 1, 21 | ): 22 | if chunk: 23 | output.write(chunk) 24 | output.flush() 25 | 26 | 27 | def get_filename(url): 28 | """extract IPSW filename from URL""" 29 | for i in range(len(url)): 30 | if url[i] == '/': 31 | position = i + 1 32 | return url[position:] 33 | 34 | 35 | class IpswDownloader: 36 | def parse_json(self, model, version, build=None, isbeta=False): 37 | """download and parse json file""" 38 | json_file = model + ".json" 39 | i = 0 40 | size = 0 41 | # if it's a beta firmware use OTA json from api.ipsw.me 42 | if isbeta is True: 43 | dl("https://api.ipsw.me/v4/ota/" + version, json_file) 44 | data = json.load(open(json_file)) 45 | 46 | with open(json_file): 47 | while True: 48 | i += 1 49 | buildid = data[i]["buildid"] 50 | device = data[i]["identifier"] 51 | if buildid == build and model == device: 52 | url = data[i]["url"] 53 | break 54 | else: 55 | dl("https://api.ipsw.me/v4/device/" + model, json_file) 56 | data = json.load(open(json_file)) 57 | 58 | ios_version = data["firmwares"][i]["version"] 59 | with open(json_file): 60 | while ios_version != version: 61 | i += 1 62 | ios_version = data["firmwares"][i]["version"] 63 | buildid = data["firmwares"][i]["buildid"] 64 | url = data["firmwares"][i]["url"] 65 | size = data["firmwares"][i]["filesize"] 66 | 67 | ipswfile = get_filename(url) 68 | os.remove(json_file) 69 | return url, ipswfile, size 70 | 71 | def recursive_rm(self, folder="ipsw"): 72 | """remove folder""" 73 | for files in os.listdir(folder): 74 | file_path = os.path.join(folder, files) 75 | try: 76 | if os.path.isfile(file_path): 77 | os.unlink(file_path) 78 | elif os.path.isdir(file_path): 79 | shutil.rmtree(file_path) 80 | except Exception as error: 81 | print(error) 82 | -------------------------------------------------------------------------------- /autodecrypt/ipsw_utils.py: -------------------------------------------------------------------------------- 1 | """Module to deal with ipsw files.""" 2 | import sys 3 | import os 4 | import shutil 5 | import requests 6 | from remotezip import RemoteZip 7 | 8 | 9 | def grab_file(url: str, filename: str) -> str: 10 | """Partialzip file from remote server.""" 11 | with RemoteZip(url) as zipfile: 12 | filenames = zipfile.namelist() 13 | for fname in filenames: 14 | zinfo = zipfile.getinfo(fname) 15 | if filename in zinfo.filename and ".plist" not in zinfo.filename: 16 | filename = zinfo.filename.split("/")[-1] 17 | print("[i] downloading %s" % filename) 18 | extract_and_clean(zipfile, zinfo.filename, filename) 19 | return filename 20 | return filename 21 | 22 | 23 | def extract_and_clean(zipper: str, zip_path: str, filename: str): 24 | """ 25 | Clean partialziped file and put it at 26 | the root of the current dir. 27 | """ 28 | zipper.extract(zip_path) 29 | if "/" in zip_path: 30 | os.rename(zip_path, filename) 31 | shutil.rmtree(zip_path.split("/")[0]) 32 | 33 | 34 | IMAGE_TYPES = [ 35 | ["ogol", "logo", "applelogo"], 36 | ["0ghc", "chg0", "batterycharging0"], 37 | ["1ghc", "chg1", "batterycharging1"], 38 | ["Ftab", "batF", "Ftab"], 39 | ["Ftab", "batF", "batteryfull"], 40 | ["0tab", "bat0", "batterylow0"], 41 | ["1tab", "bat1", "batterylow1"], 42 | ["ertd", "dtre", "devicetree"], 43 | ["Cylg", "glyC", "glyphcharging"], 44 | ["Pylg", "glyP", "glyphplugin"], 45 | ["tobi", "ibot", "iboot"], 46 | ["blli", "illb", "llb"], 47 | ["ssbi", "ibss", "ibss"], 48 | ["cebi", "ibec", "ibec"], 49 | ["lnrk", "krnl", "kernelcache"], 50 | ["sepi", "sepi", "sepfirmware"], 51 | ] 52 | 53 | 54 | def get_image_type_name(image: str) -> str: 55 | """Get image name.""" 56 | image = image.decode("utf-8") 57 | for i, _ in enumerate(IMAGE_TYPES): 58 | if image in (IMAGE_TYPES[i][0], IMAGE_TYPES[i][1]): 59 | img_type = str(IMAGE_TYPES[i][2]) 60 | return img_type 61 | return None 62 | 63 | 64 | def get_json_data(model: str, fw_type: str = "ota") -> dict: 65 | """Setup json data to parse.""" 66 | url = "https://api.ipsw.me/v4/device/" + model 67 | if fw_type == "ota": 68 | url += "?type=ota" 69 | resp = requests.get(url=url) 70 | return resp.json() 71 | 72 | 73 | def get_firmware_url(json_data: dict, buildid: str) -> str: 74 | """Return URL of IPSW file.""" 75 | for i in range(0, len(json_data["firmwares"])): 76 | if json_data["firmwares"][i]["buildid"] == buildid: 77 | return json_data["firmwares"][i]["url"] 78 | return None 79 | 80 | 81 | def get_board_config(json_data: dict) -> str: 82 | """Return boardconfig of said device""" 83 | return json_data["boardconfig"] 84 | 85 | 86 | def get_build_id(json_data: dict, ios_vers: str, fw_type: str = "ota") -> str: 87 | """Return build ID of iOS version.""" 88 | release = "" 89 | 90 | if ios_vers is None: 91 | print("[e] no iOS version specified") 92 | return sys.exit(1) 93 | 94 | for i in range(0, len(json_data["firmwares"])): 95 | curent_vers = json_data["firmwares"][i]["version"] 96 | if fw_type == "ota": 97 | release = json_data["firmwares"][i]["releasetype"] 98 | 99 | if ios_vers in curent_vers and release == "": 100 | return json_data["firmwares"][i]["buildid"] 101 | return None 102 | 103 | 104 | def get_ios_vers(json_data: dict, buildid) -> str: 105 | """ "Return iOS version of build ID.""" 106 | if json_data is None: 107 | return None 108 | for i in range(0, len(json_data["firmwares"])): 109 | if json_data["firmwares"][i]["buildid"] == buildid: 110 | return json_data["firmwares"][i]["version"] 111 | return None 112 | 113 | 114 | def get_build_list(json_data: dict) -> list: 115 | """Return a list of build IDs for a device""" 116 | builds = [] 117 | for i in range(len(json_data["firmwares"])): 118 | builds.append(json_data["firmwares"][i]["buildid"]) 119 | return builds 120 | -------------------------------------------------------------------------------- /autodecrypt/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """Main module for autodecrypt.""" 4 | import typer 5 | from rich import traceback 6 | from typing import Optional 7 | 8 | from autodecrypt import decrypt_img 9 | from autodecrypt import fw_utils 10 | from autodecrypt import utils 11 | 12 | __author__ = "matteyeux" 13 | 14 | 15 | traceback.install() 16 | 17 | 18 | def main( 19 | file: str = typer.Option( 20 | ..., 21 | "--filename", 22 | "-f", 23 | help="File", 24 | ), 25 | device: str = typer.Option( 26 | ..., 27 | "--device", 28 | "-d", 29 | help="Device", 30 | ), 31 | ios_version: str = typer.Option( 32 | None, 33 | "--ios_version", 34 | "-i", 35 | help="iOS version", 36 | ), 37 | build: str = typer.Option( 38 | None, 39 | "--build", 40 | "-b", 41 | help="Build ID of iOS version", 42 | ), 43 | ivkey: str = typer.Option( 44 | None, 45 | "--ivkey", 46 | "-k", 47 | help="IV and key to decrypt file", 48 | ), 49 | local: Optional[bool] = typer.Option( 50 | False, 51 | "--local", 52 | "-l", 53 | help="Use path to local file", 54 | ), 55 | download: Optional[bool] = typer.Option( 56 | False, 57 | "--download", 58 | "-D", 59 | help="Download file", 60 | ), 61 | beta: Optional[bool] = typer.Option( 62 | False, 63 | "--beta", 64 | "-B", 65 | help="Specify that it is a beta firmware", 66 | is_flag=True, 67 | show_default=False, 68 | ), 69 | pongo: Optional[bool] = typer.Option( 70 | False, 71 | "--pongo", 72 | "-P", 73 | help="Use PongoOS over USB for decryption", 74 | is_flag=True, 75 | show_default=False, 76 | ), 77 | ) -> int: 78 | img_file: str = None 79 | if build is None and ios_version is None: 80 | msg = typer.style( 81 | "Please specify iOS version or build ID", 82 | fg=typer.colors.WHITE, 83 | bg=typer.colors.RED, 84 | ) 85 | typer.echo(msg) 86 | return -1 87 | 88 | json_data = fw_utils.get_json_data(device) 89 | 90 | if build is None: 91 | build = fw_utils.get_build_id(json_data, ios_version) 92 | 93 | if local is False: 94 | if beta is True: 95 | img_file = utils.download_beta_file( 96 | file, device, build, ios_version, json_data 97 | ) 98 | else: 99 | img_file = utils.download_file( 100 | file, device, build, ios_version, json_data 101 | ) 102 | 103 | if img_file is None: 104 | print("[e] could not grab file") 105 | return 1 106 | 107 | # if you only need to download file, 108 | # you can stop here 109 | if download is True: 110 | return 0 111 | 112 | magic, image_type = decrypt_img.get_image_type(img_file) 113 | 114 | if pongo is True: 115 | ivkey = utils.grab_key_from_pongo(img_file) 116 | 117 | if ivkey is None and pongo is False: 118 | ivkey = utils.get_firmware_keys(device, build, img_file, image_type) 119 | if ivkey is None: 120 | return 121 | 122 | iv, key = None, None 123 | 124 | if ivkey != "0": 125 | iv, key = utils.split_key(ivkey) 126 | print("[x] iv : %s" % iv) 127 | print("[x] key : %s" % key) 128 | 129 | decrypt_img.decrypt_img(img_file, magic, iv, key) 130 | print("[x] done") 131 | return 0 132 | 133 | 134 | app = typer.run(main) 135 | 136 | # if __name__ == "__main__": 137 | # typer.run(main) 138 | -------------------------------------------------------------------------------- /autodecrypt/pongo.py: -------------------------------------------------------------------------------- 1 | import usb.core 2 | 3 | 4 | def pongo_send_command(command: str): 5 | """Send command to Pongo device.""" 6 | dev = usb.core.find(idVendor=0x05AC, idProduct=0x4141) 7 | if dev is None: 8 | return None 9 | dev.set_configuration() 10 | 11 | dev.ctrl_transfer(0x21, 4, 0, 0, 0) 12 | dev.ctrl_transfer(0x21, 3, 0, 0, command + "\n") 13 | 14 | 15 | def pongo_get_key() -> str: 16 | """Grab key from Pongo device.""" 17 | dev = usb.core.find(idVendor=0x05AC, idProduct=0x4141) 18 | if dev is None: 19 | return None 20 | dev.set_configuration() 21 | output = dev.ctrl_transfer(0xA1, 1, 0, 0, 512).tobytes() 22 | key = output.decode('utf-8').split()[-2] 23 | return key 24 | -------------------------------------------------------------------------------- /autodecrypt/scrapkeys.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Module to scrapkeys and deal with foreman.""" 3 | import re 4 | 5 | import requests 6 | from pyquery import PyQuery 7 | 8 | 9 | def get_fw_keys_page(device: str, build: str) -> str: 10 | """Return the URL of theiphonewiki to parse.""" 11 | wiki = "https://www.theiphonewiki.com" 12 | data = {"search": build + " " + device} 13 | response = requests.get(wiki + "/w/index.php", params=data) 14 | html = response.text 15 | link = re.search(r"\/wiki\/.*_" + build + r"_\(" + device + r"\)", html) 16 | if link is not None: 17 | pagelink = wiki + link.group() 18 | else: 19 | pagelink = None 20 | return pagelink 21 | 22 | 23 | def getkeys(device: str, build: str, img_file: str = None) -> str: 24 | """Return a json or str.""" 25 | pagelink = get_fw_keys_page(device, build) 26 | if pagelink is None: 27 | return None 28 | 29 | oldname = None 30 | html = requests.get(pagelink).text 31 | query = PyQuery(html) 32 | 33 | for span in query.items("span.mw-headline"): 34 | name = span.text().lower() 35 | # on some pages like 36 | # https://www.theiphonewiki.com/wiki/Genoa_13G36_(iPhone8,1) 37 | # the name of file comes with a non-breaking 38 | # space in Latin1 (ISO 8859-1) 39 | # I can either replace or split. 40 | name = name.split("\xa0")[0] 41 | 42 | if name == "sep-firmware": 43 | name = "sepfirmware" 44 | 45 | # Name is the same for both files except 46 | # that the id has a "2" for the second file: 47 | # n71m : keypage-ibec-key 48 | # n71 : keypage-ibec2-key 49 | if oldname == name: 50 | name += "2" 51 | 52 | fname = span.parent().next("* > span.keypage-filename").text() 53 | code = "*>*>code#keypage-" 54 | ivkey = span.parent().siblings(code + name + "-iv").text() 55 | ivkey += span.parent().siblings(code + name + "-key").text() 56 | 57 | if fname == img_file and img_file is not None: 58 | return ivkey 59 | oldname = name 60 | return None 61 | 62 | 63 | def foreman_get_json(foreman_host: str, device: str, build: str) -> dict: 64 | """Get json file from foreman host""" 65 | url = foreman_host + "/api/find/combo/" + device + "/" + build 66 | resp = requests.get(url=url).json() 67 | return resp 68 | 69 | 70 | def foreman_get_keys(json_data: dict, img_file: str) -> str: 71 | """Return key from json data for a specify file""" 72 | try: 73 | images = json_data["images"] 74 | except KeyError: 75 | return None 76 | 77 | for key in images.keys(): 78 | if img_file.split(".")[0] in key: 79 | return json_data["images"][key] 80 | return None 81 | -------------------------------------------------------------------------------- /autodecrypt/utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from typing import Tuple 4 | 5 | from autodecrypt import decrypt_img 6 | from autodecrypt import fw_utils 7 | from autodecrypt import pongo 8 | from autodecrypt import scrapkeys 9 | 10 | logging.basicConfig( 11 | filename="/tmp/autodecrypt.log", 12 | format="%(asctime)s %(message)s", 13 | datefmt="%m/%d/%Y %H:%M:%S", 14 | level=logging.INFO, 15 | ) 16 | 17 | 18 | def split_key(ivkey: str) -> Tuple[str, str]: 19 | """Split IV and key.""" 20 | iv = ivkey[:32] 21 | key = ivkey[-64:] 22 | return iv, key 23 | 24 | 25 | def get_firmware_keys(device: str, build: str, img_file: str, image_type: str): 26 | """ 27 | Get firmware keys using the key scrapper 28 | or by requesting of foreman instance. 29 | If one is set in env. 30 | """ 31 | logging.info("grabbing keys") 32 | foreman_host = os.getenv("FOREMAN_HOST") 33 | 34 | print("[i] grabbing keys for {}/{}".format(device, build)) 35 | if foreman_host is not None and foreman_host != "": 36 | print("[i] grabbing keys from %s" % foreman_host) 37 | foreman_json = scrapkeys.foreman_get_json(foreman_host, device, build) 38 | ivkey = scrapkeys.foreman_get_keys(foreman_json, img_file) 39 | else: 40 | ivkey = scrapkeys.getkeys(device, build, img_file) 41 | 42 | if ivkey is None: 43 | print("[e] unable to get keys for {}/{}".format(device, build)) 44 | return None 45 | return ivkey 46 | 47 | 48 | def grab_key_from_pongo(img_file: str): 49 | """Send command and grab PongoOS output.""" 50 | print("[i] grabbing keys from PongoOS device") 51 | kbag = decrypt_img.get_kbag(img_file) 52 | print("[i] kbag : {}".format(kbag)) 53 | pongo.pongo_send_command(f"aes cbc dec 256 gid0 {kbag}") 54 | ivkey = pongo.pongo_get_key() 55 | return ivkey 56 | 57 | 58 | def get_ipsw_url(device, ios_version, build): 59 | """Get URL of IPSW by specifying device and iOS version.""" 60 | json_data = fw_utils.get_json_data(device, "ipsw") 61 | 62 | if build is None: 63 | build = fw_utils.get_build_id(json_data, ios_version, "ipsw") 64 | 65 | fw_url = fw_utils.get_firmware_url(json_data, build) 66 | 67 | if fw_url is None: 68 | print("[w] could not get IPSW url, exiting...") 69 | return fw_url 70 | 71 | 72 | def download_file( 73 | file: str, device: str, build: str, ios_version, json_data: dict 74 | ) -> str: 75 | """Download file from IPSW or OTA.""" 76 | fw_url = get_ipsw_url(device, ios_version, build) 77 | 78 | if fw_url is None: 79 | return None 80 | 81 | filename = fw_utils.grab_file(fw_url, file) 82 | return filename 83 | 84 | 85 | def download_beta_file( 86 | file: str, device: str, build: str, ios_version, json_data: dict 87 | ) -> str: 88 | """Download file from beta firmware.""" 89 | if ios_version is None: 90 | print("[i] please specify iOS version") 91 | return None 92 | 93 | fw_url = fw_utils.get_beta_url(device, build, ios_version) 94 | if fw_url is None: 95 | print("[e] could not get Beta firmware URL") 96 | return None 97 | img_file = fw_utils.grab_file(fw_url, file) 98 | return img_file 99 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "asn1" 3 | version = "2.6.0" 4 | description = "Python-ASN1 is a simple ASN.1 encoder and decoder for Python 2.7+ and 3.5+." 5 | category = "main" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [package.dependencies] 10 | enum-compat = "*" 11 | 12 | [[package]] 13 | name = "beautifulsoup4" 14 | version = "4.11.1" 15 | description = "Screen-scraping library" 16 | category = "main" 17 | optional = false 18 | python-versions = ">=3.6.0" 19 | 20 | [package.dependencies] 21 | soupsieve = ">1.2" 22 | 23 | [package.extras] 24 | html5lib = ["html5lib"] 25 | lxml = ["lxml"] 26 | 27 | [[package]] 28 | name = "certifi" 29 | version = "2022.12.7" 30 | description = "Python package for providing Mozilla's CA Bundle." 31 | category = "main" 32 | optional = false 33 | python-versions = ">=3.6" 34 | 35 | [[package]] 36 | name = "cfgv" 37 | version = "3.3.1" 38 | description = "Validate configuration and produce human readable error messages." 39 | category = "dev" 40 | optional = false 41 | python-versions = ">=3.6.1" 42 | 43 | [[package]] 44 | name = "charset-normalizer" 45 | version = "2.1.1" 46 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 47 | category = "main" 48 | optional = false 49 | python-versions = ">=3.6.0" 50 | 51 | [package.extras] 52 | unicode-backport = ["unicodedata2"] 53 | 54 | [[package]] 55 | name = "click" 56 | version = "8.1.3" 57 | description = "Composable command line interface toolkit" 58 | category = "main" 59 | optional = false 60 | python-versions = ">=3.7" 61 | 62 | [package.dependencies] 63 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 64 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 65 | 66 | [[package]] 67 | name = "colorama" 68 | version = "0.4.6" 69 | description = "Cross-platform colored terminal text." 70 | category = "main" 71 | optional = false 72 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 73 | 74 | [[package]] 75 | name = "commonmark" 76 | version = "0.9.1" 77 | description = "Python parser for the CommonMark Markdown spec" 78 | category = "main" 79 | optional = false 80 | python-versions = "*" 81 | 82 | [package.extras] 83 | test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] 84 | 85 | [[package]] 86 | name = "cssselect" 87 | version = "1.2.0" 88 | description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0" 89 | category = "main" 90 | optional = false 91 | python-versions = ">=3.7" 92 | 93 | [[package]] 94 | name = "distlib" 95 | version = "0.3.6" 96 | description = "Distribution utilities" 97 | category = "dev" 98 | optional = false 99 | python-versions = "*" 100 | 101 | [[package]] 102 | name = "enum-compat" 103 | version = "0.0.3" 104 | description = "enum/enum34 compatibility package" 105 | category = "main" 106 | optional = false 107 | python-versions = "*" 108 | 109 | [[package]] 110 | name = "filelock" 111 | version = "3.8.2" 112 | description = "A platform independent file lock." 113 | category = "dev" 114 | optional = false 115 | python-versions = ">=3.7" 116 | 117 | [package.extras] 118 | docs = ["furo (>=2022.9.29)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] 119 | testing = ["covdefaults (>=2.2.2)", "coverage (>=6.5)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] 120 | 121 | [[package]] 122 | name = "identify" 123 | version = "2.5.11" 124 | description = "File identification library for Python" 125 | category = "dev" 126 | optional = false 127 | python-versions = ">=3.7" 128 | 129 | [package.extras] 130 | license = ["ukkonen"] 131 | 132 | [[package]] 133 | name = "idna" 134 | version = "3.4" 135 | description = "Internationalized Domain Names in Applications (IDNA)" 136 | category = "main" 137 | optional = false 138 | python-versions = ">=3.5" 139 | 140 | [[package]] 141 | name = "importlib-metadata" 142 | version = "5.2.0" 143 | description = "Read metadata from Python packages" 144 | category = "main" 145 | optional = false 146 | python-versions = ">=3.7" 147 | 148 | [package.dependencies] 149 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 150 | zipp = ">=0.5" 151 | 152 | [package.extras] 153 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 154 | perf = ["ipython"] 155 | testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] 156 | 157 | [[package]] 158 | name = "lxml" 159 | version = "4.9.2" 160 | description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." 161 | category = "main" 162 | optional = false 163 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" 164 | 165 | [package.extras] 166 | cssselect = ["cssselect (>=0.7)"] 167 | html5 = ["html5lib"] 168 | htmlsoup = ["BeautifulSoup4"] 169 | source = ["Cython (>=0.29.7)"] 170 | 171 | [[package]] 172 | name = "nodeenv" 173 | version = "1.7.0" 174 | description = "Node.js virtual environment builder" 175 | category = "dev" 176 | optional = false 177 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" 178 | 179 | [package.dependencies] 180 | setuptools = "*" 181 | 182 | [[package]] 183 | name = "platformdirs" 184 | version = "2.6.0" 185 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 186 | category = "dev" 187 | optional = false 188 | python-versions = ">=3.7" 189 | 190 | [package.extras] 191 | docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] 192 | test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] 193 | 194 | [[package]] 195 | name = "pre-commit" 196 | version = "2.21.0" 197 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 198 | category = "dev" 199 | optional = false 200 | python-versions = ">=3.7" 201 | 202 | [package.dependencies] 203 | cfgv = ">=2.0.0" 204 | identify = ">=1.0.0" 205 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 206 | nodeenv = ">=0.11.1" 207 | pyyaml = ">=5.1" 208 | virtualenv = ">=20.10.0" 209 | 210 | [[package]] 211 | name = "pycryptodome" 212 | version = "3.16.0" 213 | description = "Cryptographic library for Python" 214 | category = "main" 215 | optional = false 216 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 217 | 218 | [[package]] 219 | name = "pygments" 220 | version = "2.13.0" 221 | description = "Pygments is a syntax highlighting package written in Python." 222 | category = "main" 223 | optional = false 224 | python-versions = ">=3.6" 225 | 226 | [package.extras] 227 | plugins = ["importlib-metadata"] 228 | 229 | [[package]] 230 | name = "pyimg4" 231 | version = "0.6.3" 232 | description = "A Python library/CLI tool for parsing Apple's Image4 format." 233 | category = "main" 234 | optional = false 235 | python-versions = ">=3.6,<4.0" 236 | 237 | [package.dependencies] 238 | asn1 = ">=2.5.0,<3.0.0" 239 | click = ">=8.0.0,<9.0.0" 240 | pycryptodome = ">=3.14.1,<4.0.0" 241 | pyliblzfse = ">=0.4.1,<0.5.0" 242 | pylzss = ">=0.3.1,<0.4.0" 243 | 244 | [[package]] 245 | name = "pyliblzfse" 246 | version = "0.4.1" 247 | description = "Python bindings for the LZFSE reference implementation" 248 | category = "main" 249 | optional = false 250 | python-versions = "*" 251 | 252 | [[package]] 253 | name = "pylzss" 254 | version = "0.3.1" 255 | description = "LZSS compression algorithm" 256 | category = "main" 257 | optional = false 258 | python-versions = "*" 259 | 260 | [[package]] 261 | name = "pyquery" 262 | version = "1.4.3" 263 | description = "A jquery-like library for python" 264 | category = "main" 265 | optional = false 266 | python-versions = "*" 267 | 268 | [package.dependencies] 269 | cssselect = ">0.7.9" 270 | lxml = ">=2.1" 271 | 272 | [[package]] 273 | name = "pyusb" 274 | version = "1.2.1" 275 | description = "Python USB access module" 276 | category = "main" 277 | optional = false 278 | python-versions = ">=3.6.0" 279 | 280 | [[package]] 281 | name = "pyyaml" 282 | version = "6.0" 283 | description = "YAML parser and emitter for Python" 284 | category = "dev" 285 | optional = false 286 | python-versions = ">=3.6" 287 | 288 | [[package]] 289 | name = "remotezip" 290 | version = "0.10.0" 291 | description = "Access zip file content hosted remotely without downloading the full file." 292 | category = "main" 293 | optional = false 294 | python-versions = "*" 295 | 296 | [package.dependencies] 297 | requests = "*" 298 | tabulate = "*" 299 | 300 | [[package]] 301 | name = "requests" 302 | version = "2.28.1" 303 | description = "Python HTTP for Humans." 304 | category = "main" 305 | optional = false 306 | python-versions = ">=3.7, <4" 307 | 308 | [package.dependencies] 309 | certifi = ">=2017.4.17" 310 | charset-normalizer = ">=2,<3" 311 | idna = ">=2.5,<4" 312 | urllib3 = ">=1.21.1,<1.27" 313 | 314 | [package.extras] 315 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 316 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 317 | 318 | [[package]] 319 | name = "rich" 320 | version = "12.6.0" 321 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 322 | category = "main" 323 | optional = false 324 | python-versions = ">=3.6.3,<4.0.0" 325 | 326 | [package.dependencies] 327 | commonmark = ">=0.9.0,<0.10.0" 328 | pygments = ">=2.6.0,<3.0.0" 329 | typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} 330 | 331 | [package.extras] 332 | jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] 333 | 334 | [[package]] 335 | name = "setuptools" 336 | version = "65.6.3" 337 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 338 | category = "dev" 339 | optional = false 340 | python-versions = ">=3.7" 341 | 342 | [package.extras] 343 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] 344 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] 345 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] 346 | 347 | [[package]] 348 | name = "soupsieve" 349 | version = "2.3.2.post1" 350 | description = "A modern CSS selector implementation for Beautiful Soup." 351 | category = "main" 352 | optional = false 353 | python-versions = ">=3.6" 354 | 355 | [[package]] 356 | name = "tabulate" 357 | version = "0.9.0" 358 | description = "Pretty-print tabular data" 359 | category = "main" 360 | optional = false 361 | python-versions = ">=3.7" 362 | 363 | [package.extras] 364 | widechars = ["wcwidth"] 365 | 366 | [[package]] 367 | name = "typer" 368 | version = "0.7.0" 369 | description = "Typer, build great CLIs. Easy to code. Based on Python type hints." 370 | category = "main" 371 | optional = false 372 | python-versions = ">=3.6" 373 | 374 | [package.dependencies] 375 | click = ">=7.1.1,<9.0.0" 376 | 377 | [package.extras] 378 | all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"] 379 | dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] 380 | doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] 381 | test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"] 382 | 383 | [[package]] 384 | name = "typing-extensions" 385 | version = "4.4.0" 386 | description = "Backported and Experimental Type Hints for Python 3.7+" 387 | category = "main" 388 | optional = false 389 | python-versions = ">=3.7" 390 | 391 | [[package]] 392 | name = "urllib3" 393 | version = "1.26.13" 394 | description = "HTTP library with thread-safe connection pooling, file post, and more." 395 | category = "main" 396 | optional = false 397 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 398 | 399 | [package.extras] 400 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] 401 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] 402 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 403 | 404 | [[package]] 405 | name = "virtualenv" 406 | version = "20.17.1" 407 | description = "Virtual Python Environment builder" 408 | category = "dev" 409 | optional = false 410 | python-versions = ">=3.6" 411 | 412 | [package.dependencies] 413 | distlib = ">=0.3.6,<1" 414 | filelock = ">=3.4.1,<4" 415 | importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} 416 | platformdirs = ">=2.4,<3" 417 | 418 | [package.extras] 419 | docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] 420 | testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] 421 | 422 | [[package]] 423 | name = "zipp" 424 | version = "3.11.0" 425 | description = "Backport of pathlib-compatible object wrapper for zip files" 426 | category = "main" 427 | optional = false 428 | python-versions = ">=3.7" 429 | 430 | [package.extras] 431 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] 432 | testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 433 | 434 | [metadata] 435 | lock-version = "1.1" 436 | python-versions = "^3.7" 437 | content-hash = "e53c4e04e4cc06631d8465d20220e4d061ff63b306017f42dcd5a520cd11626f" 438 | 439 | [metadata.files] 440 | asn1 = [ 441 | {file = "asn1-2.6.0-py2.py3-none-any.whl", hash = "sha256:48b68ebfc898ac4ebd1dc0ced15604e6ca79bb7f0ef17e9bfd059b3c48b2dffb"}, 442 | {file = "asn1-2.6.0.tar.gz", hash = "sha256:ec33c5ab6a73a21e2f5b998c1af3b06a4011bc334c9cb1f664d2d5b2222bb1d5"}, 443 | ] 444 | beautifulsoup4 = [ 445 | {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, 446 | {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, 447 | ] 448 | certifi = [ 449 | {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, 450 | {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, 451 | ] 452 | cfgv = [ 453 | {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, 454 | {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, 455 | ] 456 | charset-normalizer = [ 457 | {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, 458 | {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, 459 | ] 460 | click = [ 461 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 462 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 463 | ] 464 | colorama = [ 465 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 466 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 467 | ] 468 | commonmark = [ 469 | {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, 470 | {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, 471 | ] 472 | cssselect = [ 473 | {file = "cssselect-1.2.0-py2.py3-none-any.whl", hash = "sha256:da1885f0c10b60c03ed5eccbb6b68d6eff248d91976fcde348f395d54c9fd35e"}, 474 | {file = "cssselect-1.2.0.tar.gz", hash = "sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc"}, 475 | ] 476 | distlib = [ 477 | {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, 478 | {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, 479 | ] 480 | enum-compat = [ 481 | {file = "enum-compat-0.0.3.tar.gz", hash = "sha256:3677daabed56a6f724451d585662253d8fb4e5569845aafa8bb0da36b1a8751e"}, 482 | {file = "enum_compat-0.0.3-py3-none-any.whl", hash = "sha256:88091b617c7fc3bbbceae50db5958023c48dc40b50520005aa3bf27f8f7ea157"}, 483 | ] 484 | filelock = [ 485 | {file = "filelock-3.8.2-py3-none-any.whl", hash = "sha256:8df285554452285f79c035efb0c861eb33a4bcfa5b7a137016e32e6a90f9792c"}, 486 | {file = "filelock-3.8.2.tar.gz", hash = "sha256:7565f628ea56bfcd8e54e42bdc55da899c85c1abfe1b5bcfd147e9188cebb3b2"}, 487 | ] 488 | identify = [ 489 | {file = "identify-2.5.11-py2.py3-none-any.whl", hash = "sha256:e7db36b772b188099616aaf2accbee122949d1c6a1bac4f38196720d6f9f06db"}, 490 | {file = "identify-2.5.11.tar.gz", hash = "sha256:14b7076b29c99b1b0b8b08e96d448c7b877a9b07683cd8cfda2ea06af85ffa1c"}, 491 | ] 492 | idna = [ 493 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 494 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 495 | ] 496 | importlib-metadata = [ 497 | {file = "importlib_metadata-5.2.0-py3-none-any.whl", hash = "sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f"}, 498 | {file = "importlib_metadata-5.2.0.tar.gz", hash = "sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd"}, 499 | ] 500 | lxml = [ 501 | {file = "lxml-4.9.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2"}, 502 | {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892"}, 503 | {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a"}, 504 | {file = "lxml-4.9.2-cp27-cp27m-win32.whl", hash = "sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de"}, 505 | {file = "lxml-4.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3"}, 506 | {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50"}, 507 | {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975"}, 508 | {file = "lxml-4.9.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c"}, 509 | {file = "lxml-4.9.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a"}, 510 | {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4"}, 511 | {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4"}, 512 | {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7"}, 513 | {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184"}, 514 | {file = "lxml-4.9.2-cp310-cp310-win32.whl", hash = "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda"}, 515 | {file = "lxml-4.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab"}, 516 | {file = "lxml-4.9.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9"}, 517 | {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf"}, 518 | {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380"}, 519 | {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92"}, 520 | {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1"}, 521 | {file = "lxml-4.9.2-cp311-cp311-win32.whl", hash = "sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33"}, 522 | {file = "lxml-4.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd"}, 523 | {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0"}, 524 | {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e"}, 525 | {file = "lxml-4.9.2-cp35-cp35m-win32.whl", hash = "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df"}, 526 | {file = "lxml-4.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5"}, 527 | {file = "lxml-4.9.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53"}, 528 | {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7"}, 529 | {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe"}, 530 | {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c"}, 531 | {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1"}, 532 | {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e"}, 533 | {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74"}, 534 | {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38"}, 535 | {file = "lxml-4.9.2-cp36-cp36m-win32.whl", hash = "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5"}, 536 | {file = "lxml-4.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3"}, 537 | {file = "lxml-4.9.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03"}, 538 | {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941"}, 539 | {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726"}, 540 | {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b"}, 541 | {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894"}, 542 | {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45"}, 543 | {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e"}, 544 | {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b"}, 545 | {file = "lxml-4.9.2-cp37-cp37m-win32.whl", hash = "sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe"}, 546 | {file = "lxml-4.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9"}, 547 | {file = "lxml-4.9.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8"}, 548 | {file = "lxml-4.9.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24"}, 549 | {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889"}, 550 | {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f"}, 551 | {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03"}, 552 | {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c"}, 553 | {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f"}, 554 | {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457"}, 555 | {file = "lxml-4.9.2-cp38-cp38-win32.whl", hash = "sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b"}, 556 | {file = "lxml-4.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7"}, 557 | {file = "lxml-4.9.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1"}, 558 | {file = "lxml-4.9.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140"}, 559 | {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4"}, 560 | {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf"}, 561 | {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947"}, 562 | {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5"}, 563 | {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5"}, 564 | {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2"}, 565 | {file = "lxml-4.9.2-cp39-cp39-win32.whl", hash = "sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1"}, 566 | {file = "lxml-4.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f"}, 567 | {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c"}, 568 | {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a"}, 569 | {file = "lxml-4.9.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419"}, 570 | {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05"}, 571 | {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f"}, 572 | {file = "lxml-4.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9"}, 573 | {file = "lxml-4.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5"}, 574 | {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746"}, 575 | {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7"}, 576 | {file = "lxml-4.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409"}, 577 | {file = "lxml-4.9.2.tar.gz", hash = "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67"}, 578 | ] 579 | nodeenv = [ 580 | {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, 581 | {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, 582 | ] 583 | platformdirs = [ 584 | {file = "platformdirs-2.6.0-py3-none-any.whl", hash = "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca"}, 585 | {file = "platformdirs-2.6.0.tar.gz", hash = "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"}, 586 | ] 587 | pre-commit = [ 588 | {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, 589 | {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, 590 | ] 591 | pycryptodome = [ 592 | {file = "pycryptodome-3.16.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e061311b02cefb17ea93d4a5eb1ad36dca4792037078b43e15a653a0a4478ead"}, 593 | {file = "pycryptodome-3.16.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:dab9359cc295160ba96738ba4912c675181c84bfdf413e5c0621cf00b7deeeaa"}, 594 | {file = "pycryptodome-3.16.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:0198fe96c22f7bc31e7a7c27a26b2cec5af3cf6075d577295f4850856c77af32"}, 595 | {file = "pycryptodome-3.16.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:58172080cbfaee724067a3c017add6a1a3cc167bbc8478dc5f2e5f45fa658763"}, 596 | {file = "pycryptodome-3.16.0-cp27-cp27m-win32.whl", hash = "sha256:4d950ed2a887905b3fa709b86be5a163e26e1b174703ed59d34eb6832f213222"}, 597 | {file = "pycryptodome-3.16.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c69e19afc734b2a17b9d78b7bcb544aabd5a52ff628e14283b6e9404d27d0517"}, 598 | {file = "pycryptodome-3.16.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:1fc16c80a5da8231fd1f953a7b8dfeb415f68120248e8d68383c5c2c4b18708c"}, 599 | {file = "pycryptodome-3.16.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5df582f2112dd72331de7e567837e136a9629181a8ab69ef8949e4bc294a0b99"}, 600 | {file = "pycryptodome-3.16.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:2bf2a270906a02b7b255e1a0d7b3aea4f06b3983c51ddec1673c380e0dff5b30"}, 601 | {file = "pycryptodome-3.16.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b12a88566a98617b1a34b4e5a805dff2da98d83fc74262aff3c3d724d0f525d6"}, 602 | {file = "pycryptodome-3.16.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:69adf32522b75968e1cbf25b5d83e87c04cd9a55610ce1e4a19012e58e7e4023"}, 603 | {file = "pycryptodome-3.16.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d67a2d2fe344953e4572a7d30668cceb516b04287b8638170d562065e53ee2e0"}, 604 | {file = "pycryptodome-3.16.0-cp35-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e750a21d8a265b1f9bfb1a28822995ea33511ba7db5e2b55f41fb30781d0d073"}, 605 | {file = "pycryptodome-3.16.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:47c71a0347847b747ba1349767b16cde049bc36f21654eb09cc82306ef5fdcf8"}, 606 | {file = "pycryptodome-3.16.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:856ebf822d08d754af62c22e2b93626509a72773214f92db1551e2b68d9e2a1b"}, 607 | {file = "pycryptodome-3.16.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6016269bb56caf0327f6d42e7bad1247e08b78407446dff562240c65f85d5a5e"}, 608 | {file = "pycryptodome-3.16.0-cp35-abi3-win32.whl", hash = "sha256:1047ac2b9847ae84ea454e6e20db7dcb755a81c1b1631a879213d2b0ad835ff2"}, 609 | {file = "pycryptodome-3.16.0-cp35-abi3-win_amd64.whl", hash = "sha256:13b3e610a2f8938c61a90b20625069ab7a77ccea20d65a9a0f926cc0cc1314b1"}, 610 | {file = "pycryptodome-3.16.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:265bfcbbf20d58e6871ce695a7a08aac9b41a0553060d9c05363abd6f3391bdd"}, 611 | {file = "pycryptodome-3.16.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:54d807314c66785c69cd25425933d4bd4c23547a593cdcf49d962fa3e0081336"}, 612 | {file = "pycryptodome-3.16.0-pp27-pypy_73-win32.whl", hash = "sha256:63165fbdc247450017eb9ef04cfe15cb3a72ca48ffcc3a3b75b08c0340bf3647"}, 613 | {file = "pycryptodome-3.16.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:95069fd9e2813668a2713a1efcc65cc26d2c7e741401ac46628f1ec957511f1b"}, 614 | {file = "pycryptodome-3.16.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d1daec4d31bb00918e4e178297ac6ca6f86ec4c851ba584770533ece554d29e2"}, 615 | {file = "pycryptodome-3.16.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:48d99869d58f3979d72f6fa0c50f48d16f14973bc4a3adb0ce3b8325fdd7e223"}, 616 | {file = "pycryptodome-3.16.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:c82e3bc1e70dde153b0956bffe20a15715a1fe3e00bc23e88d6973eda4505944"}, 617 | {file = "pycryptodome-3.16.0.tar.gz", hash = "sha256:0e45d2d852a66ecfb904f090c3f87dc0dfb89a499570abad8590f10d9cffb350"}, 618 | ] 619 | pygments = [ 620 | {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, 621 | {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, 622 | ] 623 | pyimg4 = [ 624 | {file = "pyimg4-0.6.3-py3-none-any.whl", hash = "sha256:9c03ab7b57c4d790780eedb934578844380cbd8fe7b923703619da42bf08337a"}, 625 | {file = "pyimg4-0.6.3.tar.gz", hash = "sha256:dc5bdba410bab2e0ae9d6b09191aa6ef01f5bb4a6e9057fd09475d1feac5a41f"}, 626 | ] 627 | pyliblzfse = [ 628 | {file = "pyliblzfse-0.4.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:69439a557a2979f18b816f7ccfcfdd2dc2021612811213ab569d0b35f1a04c22"}, 629 | {file = "pyliblzfse-0.4.1-cp36-cp36m-win32.whl", hash = "sha256:9b18209a8a8450ca5c01e18686b4df1796f9711cc8069897b3c8876e4cecb3ca"}, 630 | {file = "pyliblzfse-0.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:3c11008725ea6ca272c55950fee0a7424909bb4e8f07594651e3e46ba8075b59"}, 631 | {file = "pyliblzfse-0.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:de694b03164288fba37701aaecbdfd7cc6356cf3155e5bf69a44d4f73c8903ba"}, 632 | {file = "pyliblzfse-0.4.1-cp37-cp37m-win32.whl", hash = "sha256:b42e8dc723c83833c5708044a3490c216aaf2a63c6509739e3ec628fa75b8e6a"}, 633 | {file = "pyliblzfse-0.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df61b02ef4bfdddf40ad4aa153df4afaf06b4e8a4f174231f6252cce7656c315"}, 634 | {file = "pyliblzfse-0.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd59ba6e1261b8e81a84008b2207a1019695c42d6c7e66e0abd96970e2b4fa55"}, 635 | {file = "pyliblzfse-0.4.1-cp38-cp38-win32.whl", hash = "sha256:269e8d568b8c35ae145e4b938308c17938df90201be04514efc9ea3fb4a9cdc9"}, 636 | {file = "pyliblzfse-0.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0c2d164869d01253569f1a9f835d27857d7bd209e3d225d8e3b2097aab2df4fe"}, 637 | {file = "pyliblzfse-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1b255da10393541c54d0b6d0c6a0c68691075736a5ac7c921add1c03c0488ffa"}, 638 | {file = "pyliblzfse-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:b661e91ad6f84c1fbaa07f8c7d1c493cd85fd839a7a6c2d0c0713574b7347eed"}, 639 | {file = "pyliblzfse-0.4.1.tar.gz", hash = "sha256:bb0b899b3830c02fdf3dbde48ea59611833f366fef836e5c32cf8145134b7d3d"}, 640 | ] 641 | pylzss = [ 642 | {file = "pylzss-0.3.1.tar.gz", hash = "sha256:80bb3ac7be39f64848e711fa4ec239814963897f198eb9f764045d75b5bed29e"}, 643 | ] 644 | pyquery = [ 645 | {file = "pyquery-1.4.3-py3-none-any.whl", hash = "sha256:1fc33b7699455ed25c75282bc8f80ace1ac078b0dda5a933dacbd8b1c1f83963"}, 646 | {file = "pyquery-1.4.3.tar.gz", hash = "sha256:a388eefb6bc4a55350de0316fbd97cda999ae669b6743ae5b99102ba54f5aa72"}, 647 | ] 648 | pyusb = [ 649 | {file = "pyusb-1.2.1-py3-none-any.whl", hash = "sha256:2b4c7cb86dbadf044dfb9d3a4ff69fd217013dbe78a792177a3feb172449ea36"}, 650 | {file = "pyusb-1.2.1.tar.gz", hash = "sha256:a4cc7404a203144754164b8b40994e2849fde1cfff06b08492f12fff9d9de7b9"}, 651 | ] 652 | pyyaml = [ 653 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 654 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 655 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 656 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 657 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 658 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 659 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 660 | {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, 661 | {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, 662 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, 663 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, 664 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, 665 | {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, 666 | {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, 667 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 668 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 669 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 670 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 671 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 672 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 673 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 674 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 675 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 676 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 677 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 678 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 679 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 680 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 681 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 682 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 683 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 684 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 685 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 686 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 687 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 688 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 689 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 690 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 691 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 692 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 693 | ] 694 | remotezip = [ 695 | {file = "remotezip-0.10.0.tar.gz", hash = "sha256:451558348ef005d310e54a8a0f6cefbfed364f5dc52d798503314812ba8cc012"}, 696 | ] 697 | requests = [ 698 | {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, 699 | {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, 700 | ] 701 | rich = [ 702 | {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"}, 703 | {file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"}, 704 | ] 705 | setuptools = [ 706 | {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, 707 | {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, 708 | ] 709 | soupsieve = [ 710 | {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, 711 | {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, 712 | ] 713 | tabulate = [ 714 | {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, 715 | {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, 716 | ] 717 | typer = [ 718 | {file = "typer-0.7.0-py3-none-any.whl", hash = "sha256:b5e704f4e48ec263de1c0b3a2387cd405a13767d2f907f44c1a08cbad96f606d"}, 719 | {file = "typer-0.7.0.tar.gz", hash = "sha256:ff797846578a9f2a201b53442aedeb543319466870fbe1c701eab66dd7681165"}, 720 | ] 721 | typing-extensions = [ 722 | {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, 723 | {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, 724 | ] 725 | urllib3 = [ 726 | {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, 727 | {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, 728 | ] 729 | virtualenv = [ 730 | {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"}, 731 | {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"}, 732 | ] 733 | zipp = [ 734 | {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, 735 | {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, 736 | ] 737 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "autodecrypt" 3 | version = "2.3.2" 4 | description = "Tool to decrypt iOS firmware images" 5 | authors = ["matteyeux"] 6 | license = "MIT" 7 | readme = "README.md" 8 | homepage = "https://github.com/matteyeux/autodecrypt" 9 | repository = "https://github.com/matteyeux/autodecrypt" 10 | keywords = ['autodecrypt', 'iOS', 'iBoot', 'checkm8'] 11 | classifiers=[ 12 | 'Development Status :: 5 - Production/Stable', 13 | 'Intended Audience :: Developers', 14 | 'Intended Audience :: Information Technology', 15 | 'Topic :: Utilities', 16 | 'License :: OSI Approved :: MIT License', 17 | 'Programming Language :: Python :: 3.7', 18 | 'Programming Language :: Python :: 3.8', 19 | 'Programming Language :: Python :: 3.9', 20 | 'Programming Language :: Python :: 3.10', 21 | ] 22 | 23 | [tool.poetry.dependencies] 24 | python = "^3.7" 25 | remotezip = ">=0.9.2,<0.11.0" 26 | pyquery = "^1.4.1" 27 | pyusb = "^1.0.2" 28 | beautifulsoup4 = "^4.9.3" 29 | rich = "^12.4.4" 30 | typer = ">=0.4.1,<0.8.0" 31 | pyimg4 = "^0.6.2" 32 | 33 | [tool.poetry.dev-dependencies] 34 | pre-commit = "^2.21.0" 35 | 36 | [tool.poetry.scripts] 37 | autodecrypt = "autodecrypt.main:app" 38 | 39 | [build-system] 40 | requires = ["poetry>=0.12"] 41 | build-backend = "poetry.masonry.api" 42 | -------------------------------------------------------------------------------- /tests/autodecrypt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "=== print help ===" 6 | autodecrypt --help 7 | 8 | echo "=== normal behavior ===" 9 | autodecrypt -f iBSS.iphone6.RELEASE.im4p -i 10.3.3 -d iPhone6,2 10 | 11 | echo "=== specify key ===" 12 | autodecrypt -d iPhone11,8 -i 13.4 -f sep-firmware.n104.RELEASE.im4p -k 46d48ecd42ae0b76a698eacf8446f8226688e0f54a1263ab31c045d5a89ffc92f880965c4ca29f93bd395f35d80033e6 13 | 14 | echo "=== beta firmware ===" 15 | autodecrypt -d iPhone9,3 -b 18D5043d -f iBoot.d10.RELEASE.im4p -i 14.4 --beta 16 | 17 | echo "=== beta firmware iPad ===" 18 | autodecrypt -d iPad13,1 -b 19A5325f -i 15.0 -f iBoot.j307.RELEASE.im4p --beta 19 | --------------------------------------------------------------------------------