|
|
|
@ -39,6 +39,12 @@ from struct import pack, unpack, unpack_from
|
|
|
|
|
import json
|
|
|
|
|
import getopt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# import sys
|
|
|
|
|
# from IPython.core import ultratb
|
|
|
|
|
# sys.excepthook = ultratb.VerboseTB(color_scheme='NoColor')
|
|
|
|
|
|
|
|
|
|
# Routines common to Mac and PC
|
|
|
|
|
|
|
|
|
|
# Wrap a stream so that output gets flushed immediately
|
|
|
|
@ -92,7 +98,7 @@ def unicode_argv():
|
|
|
|
|
# Remove Python executable and commands if present
|
|
|
|
|
start = argc.value - len(sys.argv)
|
|
|
|
|
return [argv[i] for i in
|
|
|
|
|
xrange(start, argc.value)]
|
|
|
|
|
range(start, argc.value)]
|
|
|
|
|
# if we don't have any arguments at all, just pass back script name
|
|
|
|
|
# this should never happen
|
|
|
|
|
return [u"kindlekey.py"]
|
|
|
|
@ -128,14 +134,14 @@ def SHA256(message):
|
|
|
|
|
def primes(n):
|
|
|
|
|
if n==2: return [2]
|
|
|
|
|
elif n<2: return []
|
|
|
|
|
s=range(3,n+1,2)
|
|
|
|
|
s=list(range(3,n+1,2))
|
|
|
|
|
mroot = n ** 0.5
|
|
|
|
|
half=(n+1)/2-1
|
|
|
|
|
half=(n+1)//2-1
|
|
|
|
|
i=0
|
|
|
|
|
m=3
|
|
|
|
|
while m <= mroot:
|
|
|
|
|
if s[i]:
|
|
|
|
|
j=(m*m-3)/2
|
|
|
|
|
j=(m*m-3)//2
|
|
|
|
|
s[j]=0
|
|
|
|
|
while j<half:
|
|
|
|
|
s[j]=0
|
|
|
|
@ -146,13 +152,12 @@ def primes(n):
|
|
|
|
|
|
|
|
|
|
# Encode the bytes in data with the characters in map
|
|
|
|
|
def encode(data, map):
|
|
|
|
|
result = ''
|
|
|
|
|
result = b''
|
|
|
|
|
for char in data:
|
|
|
|
|
value = ord(char)
|
|
|
|
|
value = char
|
|
|
|
|
Q = (value ^ 0x80) // len(map)
|
|
|
|
|
R = value % len(map)
|
|
|
|
|
result += map[Q]
|
|
|
|
|
result += map[R]
|
|
|
|
|
result += bytes([map[Q], map[R]])
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
# Hash the bytes in data and then encode the digest with the characters in map
|
|
|
|
@ -161,7 +166,7 @@ def encodeHash(data,map):
|
|
|
|
|
|
|
|
|
|
# Decode the string in data with the characters in map. Returns the decoded bytes
|
|
|
|
|
def decode(data,map):
|
|
|
|
|
result = ''
|
|
|
|
|
result = b''
|
|
|
|
|
for i in range (0,len(data)-1,2):
|
|
|
|
|
high = map.find(data[i])
|
|
|
|
|
low = map.find(data[i+1])
|
|
|
|
@ -223,18 +228,18 @@ if iswindows:
|
|
|
|
|
|
|
|
|
|
def xorS(a,b):
|
|
|
|
|
""" XOR two strings """
|
|
|
|
|
assert len(a)==len(b)
|
|
|
|
|
x = []
|
|
|
|
|
for i in range(len(a)):
|
|
|
|
|
x.append( chr(ord(a[i])^ord(b[i])))
|
|
|
|
|
return ''.join(x)
|
|
|
|
|
for i in range(min(len(a),len(b))):
|
|
|
|
|
x.append(a[i]^b[i])
|
|
|
|
|
return bytes(x)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def xor(a,b):
|
|
|
|
|
""" XOR two strings """
|
|
|
|
|
x = []
|
|
|
|
|
for i in range(min(len(a),len(b))):
|
|
|
|
|
x.append( chr(ord(a[i])^ord(b[i])))
|
|
|
|
|
return ''.join(x)
|
|
|
|
|
x.append(a[i]^b[i])
|
|
|
|
|
return bytes(x)
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
Base 'BlockCipher' and Pad classes for cipher instances.
|
|
|
|
@ -253,16 +258,16 @@ if iswindows:
|
|
|
|
|
self.resetDecrypt()
|
|
|
|
|
def resetEncrypt(self):
|
|
|
|
|
self.encryptBlockCount = 0
|
|
|
|
|
self.bytesToEncrypt = ''
|
|
|
|
|
self.bytesToEncrypt = b''
|
|
|
|
|
def resetDecrypt(self):
|
|
|
|
|
self.decryptBlockCount = 0
|
|
|
|
|
self.bytesToDecrypt = ''
|
|
|
|
|
self.bytesToDecrypt = b''
|
|
|
|
|
|
|
|
|
|
def encrypt(self, plainText, more = None):
|
|
|
|
|
""" Encrypt a string and return a binary string """
|
|
|
|
|
self.bytesToEncrypt += plainText # append plainText to any bytes from prior encrypt
|
|
|
|
|
numBlocks, numExtraBytes = divmod(len(self.bytesToEncrypt), self.blockSize)
|
|
|
|
|
cipherText = ''
|
|
|
|
|
cipherText = b''
|
|
|
|
|
for i in range(numBlocks):
|
|
|
|
|
bStart = i*self.blockSize
|
|
|
|
|
ctBlock = self.encryptBlock(self.bytesToEncrypt[bStart:bStart+self.blockSize])
|
|
|
|
@ -271,7 +276,7 @@ if iswindows:
|
|
|
|
|
if numExtraBytes > 0: # save any bytes that are not block aligned
|
|
|
|
|
self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
|
|
|
|
|
else:
|
|
|
|
|
self.bytesToEncrypt = ''
|
|
|
|
|
self.bytesToEncrypt = b''
|
|
|
|
|
|
|
|
|
|
if more == None: # no more data expected from caller
|
|
|
|
|
finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize)
|
|
|
|
@ -289,14 +294,14 @@ if iswindows:
|
|
|
|
|
numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
|
|
|
|
|
if more == None: # no more calls to decrypt, should have all the data
|
|
|
|
|
if numExtraBytes != 0:
|
|
|
|
|
raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt'
|
|
|
|
|
raise DecryptNotBlockAlignedError('Data not block aligned on decrypt')
|
|
|
|
|
|
|
|
|
|
# hold back some bytes in case last decrypt has zero len
|
|
|
|
|
if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
|
|
|
|
|
numBlocks -= 1
|
|
|
|
|
numExtraBytes = self.blockSize
|
|
|
|
|
|
|
|
|
|
plainText = ''
|
|
|
|
|
plainText = b''
|
|
|
|
|
for i in range(numBlocks):
|
|
|
|
|
bStart = i*self.blockSize
|
|
|
|
|
ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize])
|
|
|
|
@ -306,7 +311,7 @@ if iswindows:
|
|
|
|
|
if numExtraBytes > 0: # save any bytes that are not block aligned
|
|
|
|
|
self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
|
|
|
|
|
else:
|
|
|
|
|
self.bytesToEncrypt = ''
|
|
|
|
|
self.bytesToEncrypt = b''
|
|
|
|
|
|
|
|
|
|
if more == None: # last decrypt remove padding
|
|
|
|
|
plainText = self.padding.removePad(plainText, self.blockSize)
|
|
|
|
@ -331,7 +336,7 @@ if iswindows:
|
|
|
|
|
def removePad(self, paddedBinaryString, blockSize):
|
|
|
|
|
""" Remove padding from a binary string """
|
|
|
|
|
if not(0<len(paddedBinaryString)):
|
|
|
|
|
raise DecryptNotBlockAlignedError, 'Expected More Data'
|
|
|
|
|
raise DecryptNotBlockAlignedError( 'Expected More Data')
|
|
|
|
|
return paddedBinaryString[:-ord(paddedBinaryString[-1])]
|
|
|
|
|
|
|
|
|
|
class noPadding(Pad):
|
|
|
|
@ -361,11 +366,11 @@ if iswindows:
|
|
|
|
|
self.blockSize = blockSize # blockSize is in bytes
|
|
|
|
|
self.padding = padding # change default to noPadding() to get normal ECB behavior
|
|
|
|
|
|
|
|
|
|
assert( keySize%4==0 and NrTable[4].has_key(keySize/4)),'key size must be 16,20,24,29 or 32 bytes'
|
|
|
|
|
assert( blockSize%4==0 and NrTable.has_key(blockSize/4)), 'block size must be 16,20,24,29 or 32 bytes'
|
|
|
|
|
assert( keySize%4==0 and (keySize//4) in NrTable[4]),'key size must be 16,20,24,29 or 32 bytes'
|
|
|
|
|
assert( blockSize%4==0 and (blockSize//4) in NrTable), 'block size must be 16,20,24,29 or 32 bytes'
|
|
|
|
|
|
|
|
|
|
self.Nb = self.blockSize/4 # Nb is number of columns of 32 bit words
|
|
|
|
|
self.Nk = keySize/4 # Nk is the key length in 32-bit words
|
|
|
|
|
self.Nb = self.blockSize//4 # Nb is number of columns of 32 bit words
|
|
|
|
|
self.Nk = keySize//4 # Nk is the key length in 32-bit words
|
|
|
|
|
self.Nr = NrTable[self.Nb][self.Nk] # The number of rounds (Nr) is a function of
|
|
|
|
|
# the block (Nb) and key (Nk) sizes.
|
|
|
|
|
if key != None:
|
|
|
|
@ -409,15 +414,15 @@ if iswindows:
|
|
|
|
|
def _toBlock(self, bs):
|
|
|
|
|
""" Convert binary string to array of bytes, state[col][row]"""
|
|
|
|
|
assert ( len(bs) == 4*self.Nb ), 'Rijndarl blocks must be of size blockSize'
|
|
|
|
|
return [[ord(bs[4*i]),ord(bs[4*i+1]),ord(bs[4*i+2]),ord(bs[4*i+3])] for i in range(self.Nb)]
|
|
|
|
|
return [[bs[4*i], bs[4*i+1], bs[4*i+2], bs[4*i+3]] for i in range(self.Nb)]
|
|
|
|
|
|
|
|
|
|
def _toBString(self, block):
|
|
|
|
|
""" Convert block (array of bytes) to binary string """
|
|
|
|
|
l = []
|
|
|
|
|
for col in block:
|
|
|
|
|
for rowElement in col:
|
|
|
|
|
l.append(chr(rowElement))
|
|
|
|
|
return ''.join(l)
|
|
|
|
|
l.append(bytes([rowElement]))
|
|
|
|
|
return b''.join(l)
|
|
|
|
|
#-------------------------------------
|
|
|
|
|
""" Number of rounds Nr = NrTable[Nb][Nk]
|
|
|
|
|
|
|
|
|
@ -432,14 +437,14 @@ if iswindows:
|
|
|
|
|
def keyExpansion(algInstance, keyString):
|
|
|
|
|
""" Expand a string of size keySize into a larger array """
|
|
|
|
|
Nk, Nb, Nr = algInstance.Nk, algInstance.Nb, algInstance.Nr # for readability
|
|
|
|
|
key = [ord(byte) for byte in keyString] # convert string to list
|
|
|
|
|
key = [byte for byte in keyString] # convert string to list
|
|
|
|
|
w = [[key[4*i],key[4*i+1],key[4*i+2],key[4*i+3]] for i in range(Nk)]
|
|
|
|
|
for i in range(Nk,Nb*(Nr+1)):
|
|
|
|
|
temp = w[i-1] # a four byte column
|
|
|
|
|
if (i%Nk) == 0 :
|
|
|
|
|
temp = temp[1:]+[temp[0]] # RotWord(temp)
|
|
|
|
|
temp = [ Sbox[byte] for byte in temp ]
|
|
|
|
|
temp[0] ^= Rcon[i/Nk]
|
|
|
|
|
temp[0] ^= Rcon[i//Nk]
|
|
|
|
|
elif Nk > 6 and i%Nk == 4 :
|
|
|
|
|
temp = [ Sbox[byte] for byte in temp ] # SubWord(temp)
|
|
|
|
|
w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] )
|
|
|
|
@ -639,7 +644,7 @@ if iswindows:
|
|
|
|
|
def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
|
|
|
|
|
""" Initialize AES, keySize is in bytes """
|
|
|
|
|
if not (keySize == 16 or keySize == 24 or keySize == 32) :
|
|
|
|
|
raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes'
|
|
|
|
|
raise BadKeySizeError('Illegal AES key size, must be 16, 24, or 32 bytes')
|
|
|
|
|
|
|
|
|
|
Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
|
|
|
|
|
|
|
|
|
@ -714,7 +719,7 @@ if iswindows:
|
|
|
|
|
if self.encryptBlockCount == 0:
|
|
|
|
|
if self.iv == None:
|
|
|
|
|
# generate IV and use
|
|
|
|
|
self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)])
|
|
|
|
|
self.iv = bytes(self.r.randrange(256) for i in range(self.blockSize))
|
|
|
|
|
self.prior_encr_CT_block = self.iv
|
|
|
|
|
auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic
|
|
|
|
|
else: # application provided IV
|
|
|
|
@ -731,7 +736,7 @@ if iswindows:
|
|
|
|
|
if self.decryptBlockCount == 0: # first call, process IV
|
|
|
|
|
if self.iv == None: # auto decrypt IV?
|
|
|
|
|
self.prior_CT_block = encryptedBlock
|
|
|
|
|
return ''
|
|
|
|
|
return b''
|
|
|
|
|
else:
|
|
|
|
|
assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption"
|
|
|
|
|
self.prior_CT_block = self.iv
|
|
|
|
@ -782,7 +787,7 @@ if iswindows:
|
|
|
|
|
def xorstr( a, b ):
|
|
|
|
|
if len(a) != len(b):
|
|
|
|
|
raise Exception("xorstr(): lengths differ")
|
|
|
|
|
return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b)))
|
|
|
|
|
return bytes((x^y for x, y in zip(a, b)))
|
|
|
|
|
|
|
|
|
|
def prf( h, data ):
|
|
|
|
|
hm = h.copy()
|
|
|
|
@ -800,18 +805,18 @@ if iswindows:
|
|
|
|
|
sha = hashlib.sha1
|
|
|
|
|
digest_size = sha().digest_size
|
|
|
|
|
# l - number of output blocks to produce
|
|
|
|
|
l = keylen / digest_size
|
|
|
|
|
l = keylen // digest_size
|
|
|
|
|
if keylen % digest_size != 0:
|
|
|
|
|
l += 1
|
|
|
|
|
h = hmac.new( passwd, None, sha )
|
|
|
|
|
T = ""
|
|
|
|
|
T = b""
|
|
|
|
|
for i in range(1, l+1):
|
|
|
|
|
T += pbkdf2_F( h, salt, iter, i )
|
|
|
|
|
return T[0: keylen]
|
|
|
|
|
|
|
|
|
|
def UnprotectHeaderData(encryptedData):
|
|
|
|
|
passwdData = 'header_key_data'
|
|
|
|
|
salt = 'HEADER.2011'
|
|
|
|
|
passwdData = b'header_key_data'
|
|
|
|
|
salt = b'HEADER.2011'
|
|
|
|
|
iter = 0x80
|
|
|
|
|
keylen = 0x100
|
|
|
|
|
key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen)
|
|
|
|
@ -824,12 +829,12 @@ if iswindows:
|
|
|
|
|
|
|
|
|
|
# Various character maps used to decrypt kindle info values.
|
|
|
|
|
# Probably supposed to act as obfuscation
|
|
|
|
|
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
|
|
|
|
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
|
|
|
|
charMap2 = b"AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
|
|
|
|
charMap5 = b"AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
|
|
|
|
# New maps in K4PC 1.9.0
|
|
|
|
|
testMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
|
|
|
|
testMap6 = "9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
|
|
|
|
|
testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
|
|
|
|
|
testMap1 = b"n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
|
|
|
|
testMap6 = b"9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
|
|
|
|
|
testMap8 = b"YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
|
|
|
|
|
|
|
|
|
|
# interface with Windows OS Routines
|
|
|
|
|
class DataBlob(Structure):
|
|
|
|
@ -891,9 +896,9 @@ if iswindows:
|
|
|
|
|
# double the buffer size
|
|
|
|
|
buffer = create_unicode_buffer(len(buffer) * 2)
|
|
|
|
|
size.value = len(buffer)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# replace any non-ASCII values with 0xfffd
|
|
|
|
|
for i in xrange(0,len(buffer)):
|
|
|
|
|
for i in range(0,len(buffer)):
|
|
|
|
|
if buffer[i]>u"\u007f":
|
|
|
|
|
#print u"swapping char "+str(i)+" ("+buffer[i]+")"
|
|
|
|
|
buffer[i] = u"\ufffd"
|
|
|
|
@ -969,13 +974,13 @@ if iswindows:
|
|
|
|
|
print ('Could not find the folder in which to look for kinfoFiles.')
|
|
|
|
|
else:
|
|
|
|
|
# Probably not the best. To Fix (shouldn't ignore in encoding) or use utf-8
|
|
|
|
|
print(u'searching for kinfoFiles in ' + path.encode('ascii', 'ignore'))
|
|
|
|
|
print('searching for kinfoFiles in ' + path)
|
|
|
|
|
|
|
|
|
|
# look for (K4PC 1.9.0 and later) .kinf2011 file
|
|
|
|
|
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
|
|
|
|
if os.path.isfile(kinfopath):
|
|
|
|
|
found = True
|
|
|
|
|
print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath.encode('ascii','ignore'))
|
|
|
|
|
print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath)
|
|
|
|
|
kInfoFiles.append(kinfopath)
|
|
|
|
|
|
|
|
|
|
# look for (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
|
|
|
@ -1038,7 +1043,7 @@ if iswindows:
|
|
|
|
|
# the .kinf file uses "/" to separate it into records
|
|
|
|
|
# so remove the trailing "/" to make it easy to use split
|
|
|
|
|
data = data[:-1]
|
|
|
|
|
items = data.split('/')
|
|
|
|
|
items = data.split(b'/')
|
|
|
|
|
|
|
|
|
|
# starts with an encoded and encrypted header blob
|
|
|
|
|
headerblob = items.pop(0)
|
|
|
|
@ -1047,7 +1052,8 @@ if iswindows:
|
|
|
|
|
#print "header cleartext:",cleartext
|
|
|
|
|
# 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):
|
|
|
|
|
|
|
|
|
|
for m in re.finditer(pattern, cleartext.decode('ascii')):
|
|
|
|
|
added_entropy = m.group(2) + m.group(4)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -1063,7 +1069,7 @@ if iswindows:
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
entropy = SHA1(keyhash) + added_entropy.encode('ascii')
|
|
|
|
|
|
|
|
|
|
# the remainder of the first record when decoded with charMap5
|
|
|
|
|
# has the ':' split char followed by the string representation
|
|
|
|
@ -1075,14 +1081,14 @@ if iswindows:
|
|
|
|
|
# read and store in rcnt records of data
|
|
|
|
|
# that make up the contents value
|
|
|
|
|
edlst = []
|
|
|
|
|
for i in xrange(rcnt):
|
|
|
|
|
for i in range(rcnt):
|
|
|
|
|
item = items.pop(0)
|
|
|
|
|
edlst.append(item)
|
|
|
|
|
|
|
|
|
|
# key names now use the new testMap8 encoding
|
|
|
|
|
keyname = "unknown"
|
|
|
|
|
for name in names:
|
|
|
|
|
if encodeHash(name,testMap8) == keyhash:
|
|
|
|
|
if encodeHash(name.encode('ascii'),testMap8) == keyhash:
|
|
|
|
|
keyname = name
|
|
|
|
|
#print "keyname found from hash:",keyname
|
|
|
|
|
break
|
|
|
|
@ -1103,10 +1109,10 @@ if iswindows:
|
|
|
|
|
# move first offsets chars to end to align for decode by testMap8
|
|
|
|
|
# by moving noffset chars from the start of the
|
|
|
|
|
# string to the end of the string
|
|
|
|
|
encdata = "".join(edlst)
|
|
|
|
|
encdata = b"".join(edlst)
|
|
|
|
|
#print "encrypted data:",encdata
|
|
|
|
|
contlen = len(encdata)
|
|
|
|
|
noffset = contlen - primes(int(contlen/3))[-1]
|
|
|
|
|
noffset = contlen - primes(int(contlen//3))[-1]
|
|
|
|
|
pfx = encdata[0:noffset]
|
|
|
|
|
encdata = encdata[noffset:]
|
|
|
|
|
encdata = encdata + pfx
|
|
|
|
@ -1126,9 +1132,9 @@ if iswindows:
|
|
|
|
|
# store values used in decryption
|
|
|
|
|
DB['IDString'] = GetIDString()
|
|
|
|
|
DB['UserName'] = GetUserName()
|
|
|
|
|
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex'))
|
|
|
|
|
print(u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().hex()))
|
|
|
|
|
else:
|
|
|
|
|
print u"Couldn't decrypt file."
|
|
|
|
|
print(u"Couldn't decrypt file.")
|
|
|
|
|
DB = {}
|
|
|
|
|
return DB
|
|
|
|
|
elif isosx:
|
|
|
|
@ -1263,7 +1269,7 @@ elif isosx:
|
|
|
|
|
#print out1
|
|
|
|
|
reslst = out1.split('\n')
|
|
|
|
|
cnt = len(reslst)
|
|
|
|
|
for j in xrange(cnt):
|
|
|
|
|
for j in range(cnt):
|
|
|
|
|
resline = reslst[j]
|
|
|
|
|
pp = resline.find('\"Serial Number\" = \"')
|
|
|
|
|
if pp >= 0:
|
|
|
|
@ -1279,7 +1285,7 @@ elif isosx:
|
|
|
|
|
out1, out2 = p.communicate()
|
|
|
|
|
reslst = out1.split('\n')
|
|
|
|
|
cnt = len(reslst)
|
|
|
|
|
for j in xrange(cnt):
|
|
|
|
|
for j in range(cnt):
|
|
|
|
|
resline = reslst[j]
|
|
|
|
|
if resline.startswith('/dev'):
|
|
|
|
|
(devpart, mpath) = resline.split(' on ')[:2]
|
|
|
|
@ -1300,7 +1306,7 @@ elif isosx:
|
|
|
|
|
#print out1
|
|
|
|
|
reslst = out1.split('\n')
|
|
|
|
|
cnt = len(reslst)
|
|
|
|
|
for j in xrange(cnt):
|
|
|
|
|
for j in range(cnt):
|
|
|
|
|
resline = reslst[j]
|
|
|
|
|
pp = resline.find('\"UUID\" = \"')
|
|
|
|
|
if pp >= 0:
|
|
|
|
@ -1320,7 +1326,7 @@ elif isosx:
|
|
|
|
|
out1, out2 = p.communicate()
|
|
|
|
|
reslst = out1.split('\n')
|
|
|
|
|
cnt = len(reslst)
|
|
|
|
|
for j in xrange(cnt):
|
|
|
|
|
for j in range(cnt):
|
|
|
|
|
resline = reslst[j]
|
|
|
|
|
pp = resline.find('Ethernet Address: ')
|
|
|
|
|
if pp >= 0:
|
|
|
|
@ -1370,8 +1376,8 @@ elif isosx:
|
|
|
|
|
# unprotect the new header blob in .kinf2011
|
|
|
|
|
# used in Kindle for Mac Version >= 1.9.0
|
|
|
|
|
def UnprotectHeaderData(encryptedData):
|
|
|
|
|
passwdData = 'header_key_data'
|
|
|
|
|
salt = 'HEADER.2011'
|
|
|
|
|
passwdData = b'header_key_data'
|
|
|
|
|
salt = b'HEADER.2011'
|
|
|
|
|
iter = 0x80
|
|
|
|
|
keylen = 0x100
|
|
|
|
|
crp = LibCrypto()
|
|
|
|
@ -1482,7 +1488,7 @@ elif isosx:
|
|
|
|
|
try:
|
|
|
|
|
DB = {}
|
|
|
|
|
items = data.split('/')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# the headerblob is the encrypted information needed to build the entropy string
|
|
|
|
|
headerblob = items.pop(0)
|
|
|
|
|
encryptedValue = decode(headerblob, charMap1)
|
|
|
|
@ -1521,7 +1527,7 @@ elif isosx:
|
|
|
|
|
# read and store in rcnt records of data
|
|
|
|
|
# that make up the contents value
|
|
|
|
|
edlst = []
|
|
|
|
|
for i in xrange(rcnt):
|
|
|
|
|
for i in range(rcnt):
|
|
|
|
|
item = items.pop(0)
|
|
|
|
|
edlst.append(item)
|
|
|
|
|
|
|
|
|
@ -1544,7 +1550,7 @@ elif isosx:
|
|
|
|
|
# (in other words split 'about' 2/3rds of the way through)
|
|
|
|
|
|
|
|
|
|
# move first offsets chars to end to align for decode by testMap8
|
|
|
|
|
encdata = ''.join(edlst)
|
|
|
|
|
encdata = b''.join(edlst)
|
|
|
|
|
contlen = len(encdata)
|
|
|
|
|
|
|
|
|
|
# now properly split and recombine
|
|
|
|
@ -1569,11 +1575,11 @@ elif isosx:
|
|
|
|
|
pass
|
|
|
|
|
if len(DB)>6:
|
|
|
|
|
# store values used in decryption
|
|
|
|
|
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName())
|
|
|
|
|
print(u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName()))
|
|
|
|
|
DB['IDString'] = IDString
|
|
|
|
|
DB['UserName'] = GetUserName()
|
|
|
|
|
else:
|
|
|
|
|
print u"Couldn't decrypt file."
|
|
|
|
|
print(u"Couldn't decrypt file.")
|
|
|
|
|
DB = {}
|
|
|
|
|
return DB
|
|
|
|
|
else:
|
|
|
|
@ -1590,7 +1596,12 @@ def kindlekeys(files = []):
|
|
|
|
|
if key:
|
|
|
|
|
# convert all values to hex, just in case.
|
|
|
|
|
for keyname in key:
|
|
|
|
|
key[keyname]=key[keyname].encode('hex')
|
|
|
|
|
v = key[keyname]
|
|
|
|
|
if isinstance(v, str):
|
|
|
|
|
v = v.encode('ascii').hex()
|
|
|
|
|
else:
|
|
|
|
|
v = v.hex()
|
|
|
|
|
key[keyname]=v
|
|
|
|
|
keys.append(key)
|
|
|
|
|
return keys
|
|
|
|
|
|
|
|
|
@ -1601,9 +1612,9 @@ def getkey(outpath, files=[]):
|
|
|
|
|
if len(keys) > 0:
|
|
|
|
|
if not os.path.isdir(outpath):
|
|
|
|
|
outfile = outpath
|
|
|
|
|
with file(outfile, 'w') as keyfileout:
|
|
|
|
|
with open(outfile, 'w') as keyfileout:
|
|
|
|
|
keyfileout.write(json.dumps(keys[0]))
|
|
|
|
|
print u"Saved a key to {0}".format(outfile)
|
|
|
|
|
print(u"Saved a key to {0}".format(outfile))
|
|
|
|
|
else:
|
|
|
|
|
keycount = 0
|
|
|
|
|
for key in keys:
|
|
|
|
@ -1612,31 +1623,31 @@ def getkey(outpath, files=[]):
|
|
|
|
|
outfile = os.path.join(outpath,u"kindlekey{0:d}.k4i".format(keycount))
|
|
|
|
|
if not os.path.exists(outfile):
|
|
|
|
|
break
|
|
|
|
|
with file(outfile, 'w') as keyfileout:
|
|
|
|
|
with open(outfile, 'w') as keyfileout:
|
|
|
|
|
keyfileout.write(json.dumps(key))
|
|
|
|
|
print u"Saved a key to {0}".format(outfile)
|
|
|
|
|
print(u"Saved a key to {0}".format(outfile))
|
|
|
|
|
return True
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def usage(progname):
|
|
|
|
|
print u"Finds, decrypts and saves the default Kindle For Mac/PC encryption keys."
|
|
|
|
|
print u"Keys are saved to the current directory, or a specified output directory."
|
|
|
|
|
print u"If a file name is passed instead of a directory, only the first key is saved, in that file."
|
|
|
|
|
print u"Usage:"
|
|
|
|
|
print u" {0:s} [-h] [-k <kindle.info>] [<outpath>]".format(progname)
|
|
|
|
|
print(u"Finds, decrypts and saves the default Kindle For Mac/PC encryption keys.")
|
|
|
|
|
print(u"Keys are saved to the current directory, or a specified output directory.")
|
|
|
|
|
print(u"If a file name is passed instead of a directory, only the first key is saved, in that file.")
|
|
|
|
|
print(u"Usage:")
|
|
|
|
|
print(u" {0:s} [-h] [-k <kindle.info>] [<outpath>]".format(progname))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cli_main():
|
|
|
|
|
sys.stdout=SafeUnbuffered(sys.stdout)
|
|
|
|
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
|
|
|
|
# sys.stdout=SafeUnbuffered(sys.stdout)
|
|
|
|
|
# sys.stderr=SafeUnbuffered(sys.stderr)
|
|
|
|
|
argv=unicode_argv()
|
|
|
|
|
progname = os.path.basename(argv[0])
|
|
|
|
|
print u"{0} v{1}\nCopyright © 2010-2016 by some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__)
|
|
|
|
|
print(u"{0} v{1}\nCopyright © 2010-2016 by some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__))
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
opts, args = getopt.getopt(argv[1:], "hk:")
|
|
|
|
|
except getopt.GetoptError, err:
|
|
|
|
|
print u"Error in options or arguments: {0}".format(err.args[0])
|
|
|
|
|
except getopt.GetoptError as err:
|
|
|
|
|
print(u"Error in options or arguments: {0}".format(err.args[0]))
|
|
|
|
|
usage(progname)
|
|
|
|
|
sys.exit(2)
|
|
|
|
|
|
|
|
|
@ -1665,7 +1676,7 @@ def cli_main():
|
|
|
|
|
outpath = os.path.realpath(os.path.normpath(outpath))
|
|
|
|
|
|
|
|
|
|
if not getkey(outpath, files):
|
|
|
|
|
print u"Could not retrieve Kindle for Mac/PC key."
|
|
|
|
|
print(u"Could not retrieve Kindle for Mac/PC key.")
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -1709,7 +1720,7 @@ def gui_main():
|
|
|
|
|
keyfileout.write(json.dumps(key))
|
|
|
|
|
success = True
|
|
|
|
|
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
|
|
|
|
except DrmException, e:
|
|
|
|
|
except DrmException as e:
|
|
|
|
|
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
|
|
|
|
except Exception:
|
|
|
|
|
root.wm_state('normal')
|
|
|
|
@ -1722,6 +1733,7 @@ def gui_main():
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
print(1)
|
|
|
|
|
if len(sys.argv) > 1:
|
|
|
|
|
sys.exit(cli_main())
|
|
|
|
|
sys.exit(gui_main())
|
|
|
|
|