├── .gitignore ├── README.md ├── constants.py ├── decrypto.py ├── drm.py ├── img ├── book.png ├── calibre.png ├── command.png ├── content_and_device.png ├── download.png ├── file_list.png ├── python.png └── serial_number.png └── mobi.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | __pycache__/ 4 | 5 | *.pyc 6 | 7 | *.azw3 8 | *.mobi 9 | *.azw 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 破除Kindle电子书的DRM 2 | 3 | 由于Mac系统的升级,不再兼容老版本的Kindle for Mac,而新版本的下载的ebook的格式为kcr,kindle上的ebook格式为kfx,导致无法在Mac上对已经购买的ebook破除DRM限制,从而不能利用calibre进行格式转换。 4 | 5 | ### 人生苦短,我用Python 6 | 7 | 不能将在kindle上购买的ebook转换成pdf格式,这大大的限制了对书籍的可操作性,于是在网上查找相关方法,可始终没有找到Mac上的正确方案,而Windows上只需要退回老版本即可。 8 | 9 | 根据之前的格式转换流程来看,最重要的步骤就是去除DRM(Digital Rights Management),既然没有现成的方案,那就用代码去实现吧,Java太臃肿,C太难,那就Python吧,毕竟人生苦短啊! 10 | 11 | ![python](./img/python.png) 12 | 13 | ### 欲求PDF,必得AZW(3) 14 | 15 | * 下载azw3文件 16 | 17 | 登录[亚马逊](https://www.z.cn),进入`管理我的内容和设备`页面,如下图所示。 18 | 19 | ![content_and_device](./img/content_and_device.png) 20 | 21 | 在`内容`标签下,选择需要转换的书籍,并点击`通过电脑下载USB传输`,如下图所示。 22 | 23 | ![book](./img/book.png) 24 | 25 | 先从下拉列表中选择kindle设备,然后点击`下载`按钮,如下图所示。 26 | 27 | ![download](./img/download.png) 28 | 29 | * 获取序列号 30 | 31 | 经过上一步,已经得到了azw3文件,接着需要获取kindle设备的序列号。先将标签从`内容`切换到`设备`,并选择kindle设备,复制序列号,如下图所示。 32 | 33 | ![serial_number](./img/serial_number.png) 34 | 35 | ### 命令破壳,界面转换 36 | 37 | * 命令运行 38 | 39 | | 参数 | 说明 | 使用 | 40 | | :---: | :---: | :---: | 41 | | file | azw3文件路径 | -f \*.azw3 或者 --file=\*.azw3 | 42 | | serial | kindle序列号 | -s xxx 或者 --serial=xxx | 43 | | out | 输出目录(可选,默认当前目录) | -o xxx 或者 --out=xxx | 44 | 45 | 该脚本采用Python3编写,入口文件为`drm.py`,下图演示了对`红楼梦 (古典名著普及文库).azw3`去除DRM的命令,其中serial number为之前复制的kindle设备序列号。 46 | 47 | ![command](./img/command.png) 48 | 49 | 运行完命令后,在当前目录下得到了名为`红楼梦 (古典名著普及文库)-nodrm.azw3`的文件,结果如下图所示。 50 | 51 | ![file_list](./img/file_list.png) 52 | 53 | * calibre转换 54 | 55 | 打开`Calibre`,将得到的`红楼梦 (古典名著普及文库)-nodrm.azw3`的文件拖拽到应用中(或者通过`Add books`进行添加),点击`Convert books`按钮,即可转换为包含PDF在内的多种电子书格式,下图是一个简单示例。 56 | 57 | ![calibre](./img/calibre.png) -------------------------------------------------------------------------------- /constants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # mobi文件的头信息中指定的格式 5 | BOOK_FORMAT = ("BOOKMOBI", "TEXtREAd") 6 | BOOK_MOBI = "BOOKMOBI" 7 | TEXTREAD = "TEXtREAd" 8 | 9 | # 文件头信息大小 10 | HEADER_SIZE = 68 11 | HEADER_START = 60 12 | 13 | # 解决不可见字符编码 14 | ENCODING = "latin-1" 15 | # 解决中文字符编码(书名) 16 | UTF8 = "utf-8" 17 | 18 | # 进度条长度 19 | BAR_LENGTH = 32 20 | 21 | # pid char map 22 | CHAR_MAP3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -------------------------------------------------------------------------------- /decrypto.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import hashlib, struct 5 | 6 | from constants import CHAR_MAP3, ENCODING 7 | 8 | # Implementation of Pukall Cipher 1 9 | # return str 10 | def PC1(key, src, decryption=True): 11 | sum1 = 0 12 | sum2 = 0 13 | keyXorVal = 0 14 | if len(key)!=16: 15 | print("Bad key length!") 16 | return None 17 | wkey = [] 18 | for i in range(8): 19 | wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) 20 | 21 | dst = "" 22 | for i in range(len(src)): 23 | temp1 = 0 24 | byteXorVal = 0 25 | for j in range(8): 26 | temp1 ^= wkey[j] 27 | sum2 = (sum2+j)*20021 + sum1 28 | sum1 = (temp1*346)&0xFFFF 29 | sum2 = (sum2+sum1)&0xFFFF 30 | temp1 = (temp1*20021+1)&0xFFFF 31 | byteXorVal ^= temp1 ^ sum2 32 | 33 | curByte = ord(src[i:i+1]) 34 | if not decryption: 35 | keyXorVal = curByte * 257 36 | curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF 37 | if decryption: 38 | keyXorVal = curByte * 257 39 | for j in range(8): 40 | wkey[j] ^= keyXorVal 41 | dst+=chr(curByte) 42 | return dst 43 | 44 | # Returns two bit at offset from a bit field 45 | def get_two_bits_from_bit_field(bit_field, offset): 46 | byte_num = int(offset / 4) 47 | bit_pos = 6 - 2*(offset % 4) 48 | return bit_field[byte_num] >> bit_pos & 3 49 | 50 | # Returns the six bits at offset from a bit field 51 | def get_six_bits_from_bit_field(bit_field, offset): 52 | offset *= 3 53 | value = (get_two_bits_from_bit_field(bit_field,offset) <<4) + (get_two_bits_from_bit_field(bit_field,offset+1) << 2) +get_two_bits_from_bit_field(bit_field,offset+2) 54 | return value 55 | 56 | def encode_pid(hash): 57 | pid = '' 58 | for position in range(8): 59 | pid += CHAR_MAP3[get_six_bits_from_bit_field(hash, position)] 60 | return pid 61 | 62 | # Parse the EXTH header records and use the Kindle serial number to calculate the book pid. 63 | def get_kindle_pid(tamper_proof_key, token, serialnum): 64 | str = bytes(serialnum, ENCODING) + tamper_proof_key + bytes(token, ENCODING) 65 | pidhash = hashlib.sha1(str).digest() 66 | bookpid = encode_pid(pidhash) 67 | return bookpid 68 | 69 | # ptr 70 | # size 71 | # flags 72 | def get_size_of_trailing_data_entries(ptr, size, flags): 73 | def get_size_of_trailing_data_entry(ptr, size): 74 | bitpos, result = 0, 0 75 | if size <= 0: 76 | return result 77 | 78 | while True: 79 | v = ord(ptr[size-1]) 80 | result |= (v & 0x7F) << bitpos 81 | bitpos += 7 82 | size -= 1 83 | if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0): 84 | return result 85 | 86 | num = 0 87 | testflags = flags >> 1 88 | while testflags: 89 | if testflags & 1: 90 | num += get_size_of_trailing_data_entry(ptr, size - num) 91 | testflags >>= 1 92 | 93 | return num -------------------------------------------------------------------------------- /drm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import getopt, os, sys 5 | from constants import BOOK_FORMAT, ENCODING, HEADER_SIZE, HEADER_START 6 | from mobi import MobiBook 7 | 8 | """ 9 | 从命令行获取参数 10 | -f or --file= 11 | -s or --serial= 12 | """ 13 | def read_args(argv): 14 | file, serial, out = "", "", "" 15 | try: 16 | opts, _ = getopt.getopt(argv, "hf:s:o:", ["file=", "serial=", "out="]) 17 | except getopt.GetoptError: 18 | print("bad option!!!") 19 | print("use -h to print options") 20 | sys.exit(2) 21 | for opt, arg in opts: 22 | if opt == "-h": 23 | print("Usage:") 24 | print("-f the file to decrypt") 25 | print("-s serial number of your kindle device") 26 | sys.exit() 27 | elif opt in ("-f", "--file"): 28 | file = arg 29 | elif opt in ("-s", "--serial"): 30 | serial = arg 31 | elif opt in ("-o", "--out"): 32 | out = arg 33 | return file, serial, out 34 | 35 | """ 36 | 检查参数合法性 37 | file kindle文件路径 38 | serial kindle设备序列号(16位) 39 | out 输出文件路径 40 | """ 41 | def check_args(file, serial, out): 42 | if file == "": 43 | print("Pls. input the file name") 44 | return False 45 | if serial == "": 46 | print("Pls. input the kindle serial number") 47 | return False 48 | if len(serial) != 16: 49 | print("Illegal serial number") 50 | return False 51 | if not os.path.isfile(file): 52 | print("No such file: " + file) 53 | return False 54 | if out != "": 55 | if not os.path.isdir(out): 56 | print(out + " is not dir") 57 | return False 58 | if not os.path.exists(out): 59 | print("No such dir:" + out) 60 | return False 61 | return True 62 | 63 | """ 64 | 去除drm 65 | 以-nodrm.mobi为文件名后缀重新生成文件 66 | """ 67 | def remove_drm(argv): 68 | # 获取文件路径和设备序列号 69 | file, serial, out = read_args(argv) 70 | 71 | # 校验参数是否合法 72 | success = check_args(file, serial, out) 73 | if not success: 74 | sys.exit() 75 | 76 | print("Processing book file: " + file) 77 | 78 | # 以二进制只读方式打开文件 79 | rf = open(file, mode='rb') 80 | # 读取文件头信息 81 | header = rf.read(HEADER_SIZE) 82 | # 关闭文件 83 | rf.close 84 | 85 | format = header[HEADER_START:].decode(ENCODING) 86 | # 判断是否为mobi格式文件 87 | if not format in BOOK_FORMAT: 88 | print("Illegal file format") 89 | sys.exit() 90 | 91 | mb = MobiBook(file, format) 92 | 93 | # 获取文件名称 94 | title = mb.get_book_title() 95 | print("Book's title: %s" % title) 96 | 97 | # 去除DRM 98 | if not mb.process_book(serial): 99 | sys.exit() 100 | 101 | # 求文件后缀名 102 | _, temp = os.path.split(file) 103 | suffix = temp.split(".")[1] 104 | 105 | new_path = title + "-nodrm." + suffix 106 | if out != "": 107 | new_path = os.path.join(out, new_path) 108 | 109 | # 写入新文件 110 | wf = open(new_path, mode='wb+') 111 | wf.write(mb.get_result()) 112 | print("Congratulations! DRM success!") 113 | print("New file is %s" % new_path) 114 | 115 | def main(argv): 116 | remove_drm(argv) 117 | 118 | if __name__ == "__main__": 119 | main(sys.argv[1:]) -------------------------------------------------------------------------------- /img/book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SubLuLu/drm_remover/04e86789466488353115abf9ac983286695b7efa/img/book.png -------------------------------------------------------------------------------- /img/calibre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SubLuLu/drm_remover/04e86789466488353115abf9ac983286695b7efa/img/calibre.png -------------------------------------------------------------------------------- /img/command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SubLuLu/drm_remover/04e86789466488353115abf9ac983286695b7efa/img/command.png -------------------------------------------------------------------------------- /img/content_and_device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SubLuLu/drm_remover/04e86789466488353115abf9ac983286695b7efa/img/content_and_device.png -------------------------------------------------------------------------------- /img/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SubLuLu/drm_remover/04e86789466488353115abf9ac983286695b7efa/img/download.png -------------------------------------------------------------------------------- /img/file_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SubLuLu/drm_remover/04e86789466488353115abf9ac983286695b7efa/img/file_list.png -------------------------------------------------------------------------------- /img/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SubLuLu/drm_remover/04e86789466488353115abf9ac983286695b7efa/img/python.png -------------------------------------------------------------------------------- /img/serial_number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SubLuLu/drm_remover/04e86789466488353115abf9ac983286695b7efa/img/serial_number.png -------------------------------------------------------------------------------- /mobi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import os, struct, sys 5 | import decrypto 6 | from constants import BAR_LENGTH, BOOK_MOBI, TEXTREAD, ENCODING, UTF8 7 | 8 | class MobiBook(object): 9 | 10 | def __init__(self, path, format): 11 | self.__path = path 12 | self.__format = format 13 | self.__sections = [] 14 | self.__exth_record = {} 15 | self.__extra_data_flags = 0 16 | 17 | rf = open(self.__path, mode='rb') 18 | size = os.path.getsize(self.__path) 19 | data = rf.read(size) 20 | rf.close() 21 | 22 | self.__data = data 23 | self.__num, = struct.unpack('>H', data[76:78]) 24 | 25 | for i in range(self.__num): 26 | offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', data[78+i*8:78+i*8+8]) 27 | flags, val = a1, a2<<16|a3<<8|a4 28 | self.__sections.append((offset, flags, val)) 29 | 30 | self.__sect = self.__load_section(0) 31 | 32 | # parse info from section[0] 33 | self.__records, = struct.unpack('>H', self.__sect[0x8:0x8+2]) 34 | compression, = struct.unpack('>H', self.__sect[0x0:0x0+2]) 35 | self.__mobi_header_len, = struct.unpack('>L', self.__sect[0x14:0x18]) 36 | # mobi_codepage, = struct.unpack('>L', self.__sect[0x1C:0x20]) 37 | self.__mobi_version, = struct.unpack('>L', self.__sect[0x68:0x6C]) 38 | 39 | print("MOBI header version = %d, length = %d" % (self.__mobi_version, self.__mobi_header_len)) 40 | 41 | if self.__mobi_header_len >= 0xE4 and self.__mobi_version >= 5: 42 | self.__extra_data_flags, = struct.unpack('>H', self.__sect[0xF2:0xF4]) 43 | 44 | if compression != 17480: 45 | self.__extra_data_flags &= 0xFFFE 46 | 47 | exth_flag, = struct.unpack('>I', self.__sect[0x80:0x84]) 48 | exth = None 49 | if exth_flag & 0x40: # bit6 50 | print("There's an EXTH record!") 51 | # The EXTH header follows immediately after the MOBI header. 52 | exth = self.__sect[16 + self.__mobi_header_len:] 53 | 54 | # exth[0:4] is the EXTH identifier 55 | if (len(exth) >= 4) and (exth[0:4].decode(ENCODING) == 'EXTH'): 56 | # The number of records in the EXTH header. 57 | nitems, = struct.unpack('>I', exth[8:12]) 58 | # EXTH record start 59 | pos = 12 60 | for i in range(nitems): 61 | type, size = struct.unpack('>2I', exth[pos:pos + 8]) 62 | content = exth[pos + 8:pos + size] 63 | self.__exth_record[type] = content 64 | if type == 401 and size == 9: # clippinglimit : nteger percentage of the text allowed to be clipped. Usually 10. 65 | self.__patch_section(0, "\144", 16 + self.__mobi_header_len + pos + 8) 66 | elif type == 404 and size == 9: # ttsflag 67 | self.__patch_section(0, "\0", 16 + self.__mobi_header_len + pos + 8) 68 | pos += size 69 | 70 | def __load_section(self, section): 71 | if (section + 1 == self.__num): 72 | endoff = len(self.__data) 73 | else: 74 | endoff = self.__sections[section + 1][0] 75 | 76 | off = self.__sections[section][0] 77 | return self.__data[off:endoff] 78 | 79 | def __patch_section(self, section, new, in_off = 0): 80 | if (section + 1 == self.__num): 81 | endoff = len(self.__data) 82 | else: 83 | endoff = self.__sections[section + 1][0] 84 | 85 | off = self.__sections[section][0] 86 | assert off + in_off + len(new) <= endoff 87 | 88 | self.__patch(off + in_off, new) 89 | 90 | def __patch(self, off, new): 91 | self.__data = self.__data[:off] + bytes(new, ENCODING) + self.__data[off+len(new):] 92 | 93 | def get_book_title(self): 94 | title = '' 95 | if self.__format == BOOK_MOBI: 96 | if 503 in self.__exth_record: 97 | title = self.__exth_record[503] 98 | else: 99 | toff, tlen = struct.unpack('>2I', self.__sect[0x54:0x5c]) 100 | tend = toff + tlen 101 | title = self.__sect[toff:tend] 102 | 103 | if title == '': 104 | _, temp = os.path.split(self.__path) 105 | title = temp.split(".")[0] 106 | return title 107 | 108 | return title.decode(UTF8) 109 | 110 | def __get_pid_meta_info(self): 111 | tamper_proof_key = '' 112 | token = '' 113 | if 209 in self.__exth_record: 114 | # It is used by the Kindle for generating book-specific PIDs. 115 | tamper_proof_key = self.__exth_record[209] 116 | data = tamper_proof_key 117 | length = len(data) 118 | #The 209 data comes in five byte groups. 119 | #Interpret the last four bytes of each group 120 | #as a big endian unsigned integer to get a key value 121 | #if that key exists in the exth_record, 122 | #append its contents to the token 123 | for i in range(length): 124 | if i+5 <= length: 125 | val, = struct.unpack('>I', data[i+1:i+5]) 126 | sval = self.__exth_record.get(val, '') 127 | if isinstance(sval, str): 128 | token += sval 129 | else: 130 | token += sval.decode(ENCODING) 131 | return tamper_proof_key, token 132 | 133 | def process_book(self, serial): 134 | # Only type 0, 1, 2 are valid. 135 | crypto_type, = struct.unpack('>H', self.__sect[0xC:0xC+2]) 136 | if crypto_type == 0: 137 | print("This book is not encrypted!") 138 | return False 139 | 140 | if not crypto_type in (1, 2): 141 | print("Unknown encryption type:%d" % crypto_type) 142 | return False 143 | 144 | if 406 in self.__exth_record: 145 | rent_expiration_date, = struct.unpack('>Q', self.__exth_record[406]) 146 | if rent_expiration_date != 0: 147 | print("Cannot decode library or rented ebooks!") 148 | return False 149 | 150 | pid = '00000000' 151 | book_key_data = None 152 | found_key = '' 153 | 154 | if crypto_type == 1: 155 | print("Old Mobipocket Encryptioin") 156 | t1_keyvec = "QDCVEPMU675RUBSZ" 157 | if self.__format == TEXTREAD: 158 | book_key_data = self.__sect[0x0E:0x0E+16] 159 | elif self.__mobi_version < 0: 160 | book_key_data = self.__sect[0x90:0x90+16] 161 | else: 162 | book_key_data = self.__sect[self.__mobi_header_len+16:self.__mobi_header_len+32] 163 | 164 | found_key = decrypto.PC1(t1_keyvec, book_key_data) 165 | 166 | print("File has default encryption, no specific PID.") 167 | else: # crypto_type == 2 168 | print("Mobipocket Encryption") 169 | 170 | md1, md2 = self.__get_pid_meta_info() 171 | pid = decrypto.get_kindle_pid(md1, md2, serial) 172 | 173 | if len(pid) != 8: 174 | print("Error: PID %s is incorrect." % pid) 175 | return False 176 | 177 | print("File is encoded with PID %s." % pid) 178 | 179 | # drm_offset : offset to DRM key info in DRMed file. 180 | # 0xffffffff if no DRM 181 | # drm_count : numbers of entries in DRM info. 182 | # 0xffffffff if no DRM 183 | # drm_size : Numbers of bytes in DRM info 184 | # _(ignore) : Some flags concerning the DRM info 185 | # calculate the keys 186 | drm_offset, drm_count, drm_size, _ = struct.unpack('>LLLL', self.__sect[0xA8:0xA8+16]) 187 | if drm_count == 0: 188 | print("No PIDs found in this file") 189 | return False 190 | 191 | found_key = self.__parse_drm(self.__sect[drm_offset:drm_offset+drm_size], drm_count, pid) 192 | if found_key == '': 193 | print("No key found. maybe the PID is incorrect") 194 | return False 195 | 196 | # kill the drm keys 197 | self.__patch_section(0, "\0" * drm_size, drm_offset) 198 | # kill the drm pointers 199 | self.__patch_section(0, "\xff" * 4 + "\0" * 12, 0xA8) 200 | 201 | # clear the crypto type 202 | self.__patch_section(0, "\0" * 2, 0xC) 203 | 204 | # decrypt sections 205 | print("Decrypting. Please wait ...") 206 | 207 | new_data = self.__data[0:self.__sections[1][0]] 208 | 209 | for i in range(1, self.__records+1): 210 | data = self.__load_section(i) 211 | ptr = data.decode(ENCODING) 212 | extra_size = decrypto.get_size_of_trailing_data_entries(ptr, len(data), self.__extra_data_flags) 213 | 214 | # progress bar 215 | percent = i / self.__records 216 | hashes = '#' * int(percent * BAR_LENGTH) 217 | spaces = ' ' * (BAR_LENGTH - len(hashes)) 218 | sys.stdout.write("\rPercent: [%s] %d%%" % (hashes + spaces, percent*100)) 219 | sys.stdout.flush() 220 | 221 | pc_data = decrypto.PC1(found_key, data[0:len(data) - extra_size]) 222 | new_data += bytes(pc_data, ENCODING) 223 | 224 | if extra_size > 0: 225 | new_data += data[-extra_size:] 226 | 227 | print() # new line 228 | 229 | if self.__num > self.__records+1: 230 | new_data += self.__data[self.__sections[self.__records+1][0]:] 231 | 232 | self.__data = new_data 233 | return True 234 | 235 | def __parse_drm(self, data, count, pid): 236 | keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96" 237 | #keyascii = [114, 56, 51, 176, 180, 242, 227, 202, 223, 9, 1, 214, 226, 224, 63, 150] 238 | pid = pid.ljust(16,'\0') 239 | temp_key = decrypto.PC1(keyvec1, pid, False) 240 | temp_key_sum = sum(map(ord, temp_key)) & 0xff 241 | 242 | found_key = None 243 | for i in range(count): 244 | verification, _, _, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) 245 | cookie = decrypto.PC1(temp_key, cookie) 246 | 247 | ver, flags = struct.unpack('>LL', bytes(cookie[0:8], ENCODING)) 248 | finalkey = cookie[8:24] 249 | 250 | if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1: 251 | found_key = finalkey 252 | break 253 | 254 | if not found_key: 255 | # Then try the default encoding that doesn't require a PID 256 | temp_key = keyvec1 257 | temp_key_sum = sum(map(ord, temp_key)) & 0xff 258 | for i in range(count): 259 | verification, _, _, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) 260 | cookie = decrypto.PC1(temp_key, cookie) 261 | 262 | ver, _ = struct.unpack('>LL', bytes(cookie[0:8], ENCODING)) 263 | finalkey = cookie[8:24] 264 | 265 | if verification == ver and cksum == temp_key_sum: 266 | found_key = finalkey 267 | break 268 | 269 | return found_key 270 | 271 | def get_result(self): 272 | return self.__data --------------------------------------------------------------------------------