from __future__ import with_statement
# kindlekey.py
-# Copyright © 2008-2017 Apprentice Harper et al.
+# Copyright © 2008-2020 Apprentice Harper et al.
__license__ = 'GPL v3'
__version__ = '2.6'
# 2.4 - Fix for complex Mac disk setups, thanks to Tibs
# 2.5 - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus
# 2.6 - Start adding support for Kindle 1.25+ .kinf2018 file
+# 2.7 - Finish .kinf2018 support
"""
"""
import sys, os, re
-from struct import pack, unpack, unpack_from
+from struct import pack, unpack
import json
import getopt
Original Version
Copyright (c) 2002 by Paul A. Lambert
Under:
- CryptoPy Artisitic License Version 1.0
+ CryptoPy Artistic License Version 1.0
See the wonderful pure python package cryptopy-1.2.5
and read its LICENSE.txt for complete license details.
"""
DB = {}
with open(kInfoFile, 'rb') as infoReader:
data = infoReader.read()
- # assume newest .kinf2011 style .kinf file
+ # assume .kinf2011 or .kinf2018 style .kinf file
# the .kinf file uses "/" to separate it into records
# so remove the trailing "/" to make it easy to use split
data = data[:-1]
# now extract the pieces that form the added entropy
pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
for m in re.finditer(pattern, cleartext):
- added_entropy = m.group(2) + m.group(4)
-
+ version = int(m.group(1))
+ build = m.group(2)
+ guid = m.group(4)
+
+ if version == 5: # .kinf2011
+ added_entropy = build + guid
+ elif version == 6: # .kinf2018
+ salt = str(0x6d8 * int(build)) + guid
+ sp = GetUserName() + '+@#$%+' + GetIDString()
+ passwd = encode(SHA256(sp), charMap5)
+ key = KeyIVGen().pbkdf2(passwd, salt, 10000, 0x400)[:32] # this is very slow
# loop through the item records until all are processed
while len(items) > 0:
# is the MD5 hash of the key name encoded by charMap5
keyhash = item[0:32]
- # the sha1 of raw keyhash string is used to create entropy along
- # with the added entropy provided above from the headerblob
- entropy = SHA1(keyhash) + added_entropy
-
# the remainder of the first record when decoded with charMap5
# has the ':' split char followed by the string representation
# of the number of records that follow
encdata = encdata + pfx
#print "rearranged data:",encdata
+ if version == 5:
+ # decode using new testMap8 to get the original CryptProtect Data
+ encryptedValue = decode(encdata,testMap8)
+ #print "decoded data:",encryptedValue.encode('hex')
+ entropy = SHA1(keyhash) + added_entropy
+ cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
+ elif version == 6:
+ from Crypto.Cipher import AES
+ from Crypto.Util import Counter
+ # decode using new testMap8 to get IV + ciphertext
+ iv_ciphertext = decode(encdata, testMap8)
+ # pad IV so that we can substitute AES-CTR for GCM
+ iv = iv_ciphertext[:12] + b'\x00\x00\x00\x02'
+ ciphertext = iv_ciphertext[12:]
+ # convert IV to int for use with pycrypto
+ iv_ints = unpack('>QQ', iv)
+ iv = iv_ints[0] << 64 | iv_ints[1]
+ # set up AES-CTR
+ ctr = Counter.new(128, initial_value=iv)
+ cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
+ # decrypt and decode
+ cleartext = decode(cipher.decrypt(ciphertext), charMap5)
- # decode using new testMap8 to get the original CryptProtect Data
- encryptedValue = decode(encdata,testMap8)
- #print "decoded data:",encryptedValue.encode('hex')
- cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
if len(cleartext)>0:
#print "cleartext data:",cleartext,":end data"
DB[keyname] = cleartext