changed for android support - in progress
This commit is contained in:
parent
4c9aacd01e
commit
0837482686
|
@ -5,7 +5,7 @@ from __future__ import with_statement
|
|||
|
||||
# androidkindlekey.py
|
||||
# Copyright © 2013-15 by Thom
|
||||
# Some portions Copyright © 2011-15 by Apprentice Alf and Apprentice Harper
|
||||
# Some portions Copyright © 2010-15 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
#
|
||||
|
||||
# Revision history:
|
||||
|
@ -14,14 +14,14 @@ from __future__ import with_statement
|
|||
# 1.2 - Changed to be callable from AppleScript by returning only serial number
|
||||
# - and changed name to androidkindlekey.py
|
||||
# - and added in unicode command line support
|
||||
|
||||
# 1.3 - added in TkInter interface, output to a file and attempt to get backup from a connected android device.
|
||||
|
||||
"""
|
||||
Retrieve Kindle for Android Serial Number.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.2'
|
||||
__version__ = '1.3'
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
@ -272,7 +272,32 @@ def get_serials(path=STORAGE):
|
|||
|
||||
return serials
|
||||
|
||||
__all__ = [ 'get_serials' ]
|
||||
__all__ = [ 'get_serials', 'getkey']
|
||||
|
||||
# interface for Python DeDRM
|
||||
# returns single key or multiple keys, depending on path or file passed in
|
||||
def getkey(outpath, inpath):
|
||||
keys = get_serials(inpath)
|
||||
if len(keys) > 0:
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(outpath,u"kindlekey{0:d}.k4a".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def usage(progname):
|
||||
print u"{0} v{1}\nCopyright © 2013-2015 Thom and Apprentice Harper".format(progname,__version__)
|
||||
|
@ -283,7 +308,7 @@ def usage(progname):
|
|||
print u""
|
||||
print u"Serial number is written to standard output."
|
||||
print u"Usage:"
|
||||
print u" {0:s} [-h] <inputfile>".format(progname)
|
||||
print u" {0:s} [-h] [-b <backup.ab>] [<outpath>]".format(progname)
|
||||
|
||||
|
||||
def cli_main():
|
||||
|
@ -291,47 +316,131 @@ def cli_main():
|
|||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2010-2015 Thom, some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "h")
|
||||
opts, args = getopt.getopt(argv[1:], "hb:")
|
||||
except getopt.GetoptError, err:
|
||||
usage(progname)
|
||||
print u"\nError in options or arguments: {0}".format(err.args[0])
|
||||
return 2
|
||||
|
||||
files = []
|
||||
inpath = ""
|
||||
for o, a in opts:
|
||||
if o == "-h":
|
||||
usage(progname)
|
||||
return 0
|
||||
if o == "-b":
|
||||
inpath = a
|
||||
|
||||
if len(args) > 1:
|
||||
usage(progname)
|
||||
return 2
|
||||
|
||||
inpath = args[0]
|
||||
if not os.path.isabs(inpath):
|
||||
inpath = os.path.abspath(inpath)
|
||||
if len(args) == 1:
|
||||
# save to the specified file or directory
|
||||
outpath = args[0]
|
||||
if not os.path.isabs(outpath):
|
||||
outpath = os.path.join(os.path.dirname(argv[0]),outpath)
|
||||
outpath = os.path.abspath(outpath)
|
||||
else:
|
||||
# save to the same directory as the script
|
||||
outpath = os.path.dirname(argv[0])
|
||||
|
||||
inpath = os.path.realpath(os.path.normpath(inpath))
|
||||
# make sure the outpath is OK
|
||||
outpath = os.path.realpath(os.path.normpath(outpath))
|
||||
|
||||
if not os.path.isfile(inpath):
|
||||
usage(progname)
|
||||
print u"\n{0:s} file not found".format(inpath)
|
||||
return 2
|
||||
|
||||
serials = get_serials(inpath)
|
||||
if not getkey(outpath, inpath):
|
||||
print u"Could not retrieve Kindle for Android key."
|
||||
return 0
|
||||
|
||||
if len(serials) == 0:
|
||||
print u"No keys found in {0:s}".format(inpath)
|
||||
return 2
|
||||
|
||||
for serial in serials:
|
||||
print serial
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import tkFileDialog
|
||||
import traceback
|
||||
except:
|
||||
print "Tkinter not installed"
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"Select backup.ab file")
|
||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||
body = Tkinter.Frame(self)
|
||||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
Tkinter.Label(body, text=u"Backup file").grid(row=0, column=0)
|
||||
self.keypath = Tkinter.Entry(body, width=40)
|
||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||
self.keypath.insert(2, u"backup.ab")
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||
button.grid(row=0, column=2)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
button2 = Tkinter.Button(
|
||||
buttons, text=u"Extract", width=10, command=self.generate)
|
||||
button2.pack(side=Tkconstants.LEFT)
|
||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||
button3 = Tkinter.Button(
|
||||
buttons, text=u"Quit", width=10, command=self.quit)
|
||||
button3.pack(side=Tkconstants.RIGHT)
|
||||
|
||||
def get_keypath(self):
|
||||
keypath = tkFileDialog.askopenfilename(
|
||||
parent=None, title=u"Select backup.ab file",
|
||||
defaultextension=u".ab",
|
||||
filetypes=[('adb backup com.amazon.kindle', '.ab'),
|
||||
('All Files', '.*')])
|
||||
if keypath:
|
||||
keypath = os.path.normpath(keypath)
|
||||
self.keypath.delete(0, Tkconstants.END)
|
||||
self.keypath.insert(0, keypath)
|
||||
return
|
||||
|
||||
def generate(self):
|
||||
inpath = self.keypath.get()
|
||||
self.status['text'] = u"Getting key..."
|
||||
try:
|
||||
keys = get_serials(inpath)
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(progpath,u"kindlekey{0:d}.k4a".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||
return
|
||||
self.status['text'] = u"Select backup.ab file"
|
||||
|
||||
argv=unicode_argv()
|
||||
progpath, progname = os.path.split(argv[0])
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Kindle for Android Key Extraction v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
sys.exit(cli_main())
|
||||
usage(os.path.basename(unicode_argv()[0]))
|
||||
sys.exit(0);
|
||||
sys.exit(gui_main())
|
||||
|
|
|
@ -12,8 +12,9 @@
|
|||
# 6.0.4 - Fix for other potential unicode problems
|
||||
# 6.0.5 - Fix typo
|
||||
# 6.2.0 - Update to match plugin and AppleScript
|
||||
# 6.3.0 - Add in Android support
|
||||
|
||||
__version__ = '6.2.0'
|
||||
__version__ = '6.3.0'
|
||||
|
||||
import sys
|
||||
import os, os.path
|
||||
|
@ -158,83 +159,107 @@ class PrefsDialog(Toplevel):
|
|||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
|
||||
Tkinter.Label(body, text='Adobe Key file (adeptkey.der)').grid(row=0, sticky=Tkconstants.E)
|
||||
|
||||
cur_row = 0
|
||||
Tkinter.Label(body, text='Adobe Key file (adeptkey.der)').grid(row=cur_row, sticky=Tkconstants.E)
|
||||
self.adkpath = Tkinter.Entry(body, width=50)
|
||||
self.adkpath.grid(row=0, column=1, sticky=sticky)
|
||||
self.adkpath.grid(row=cur_row, column=1, sticky=sticky)
|
||||
prefdir = self.prefs_array['dir']
|
||||
keyfile = os.path.join(prefdir,'adeptkey.der')
|
||||
if os.path.isfile(keyfile):
|
||||
path = keyfile
|
||||
self.adkpath.insert(0, path)
|
||||
self.adkpath.insert(cur_row, path)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_adkpath)
|
||||
button.grid(row=0, column=2)
|
||||
button.grid(row=cur_row, column=2)
|
||||
|
||||
Tkinter.Label(body, text='Kindle Key file (kindlekey.k4i)').grid(row=1, sticky=Tkconstants.E)
|
||||
cur_row = cur_row + 1
|
||||
Tkinter.Label(body, text='Kindle Key file (kindlekey.k4i)').grid(row=cur_row, sticky=Tkconstants.E)
|
||||
self.kkpath = Tkinter.Entry(body, width=50)
|
||||
self.kkpath.grid(row=1, column=1, sticky=sticky)
|
||||
self.kkpath.grid(row=cur_row, column=1, sticky=sticky)
|
||||
prefdir = self.prefs_array['dir']
|
||||
keyfile = os.path.join(prefdir,'kindlekey.k4i')
|
||||
if os.path.isfile(keyfile):
|
||||
path = keyfile
|
||||
self.kkpath.insert(1, path)
|
||||
self.kkpath.insert(0, path)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_kkpath)
|
||||
button.grid(row=1, column=2)
|
||||
button.grid(row=cur_row, column=2)
|
||||
|
||||
Tkinter.Label(body, text='Barnes and Noble Key file (bnepubkey.b64)').grid(row=2, sticky=Tkconstants.E)
|
||||
cur_row = cur_row + 1
|
||||
Tkinter.Label(body, text='Android Kindle Key file (kindlekey.k4a)').grid(row=cur_row, sticky=Tkconstants.E)
|
||||
self.akkpath = Tkinter.Entry(body, width=50)
|
||||
self.akkpath.grid(row=cur_row, column=1, sticky=sticky)
|
||||
prefdir = self.prefs_array['dir']
|
||||
keyfile = os.path.join(prefdir,'kindlekey.k4a')
|
||||
if os.path.isfile(keyfile):
|
||||
path = keyfile
|
||||
self.akkpath.insert(0, path)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_akkpath)
|
||||
button.grid(row=cur_row, column=2)
|
||||
|
||||
cur_row = cur_row + 1
|
||||
Tkinter.Label(body, text='Barnes and Noble Key file (bnepubkey.b64)').grid(row=cur_row, sticky=Tkconstants.E)
|
||||
self.bnkpath = Tkinter.Entry(body, width=50)
|
||||
self.bnkpath.grid(row=2, column=1, sticky=sticky)
|
||||
self.bnkpath.grid(row=cur_row, column=1, sticky=sticky)
|
||||
prefdir = self.prefs_array['dir']
|
||||
keyfile = os.path.join(prefdir,'bnepubkey.b64')
|
||||
if os.path.isfile(keyfile):
|
||||
path = keyfile
|
||||
self.bnkpath.insert(2, path)
|
||||
self.bnkpath.insert(0, path)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_bnkpath)
|
||||
button.grid(row=2, column=2)
|
||||
button.grid(row=cur_row, column=2)
|
||||
|
||||
Tkinter.Label(body, text='Mobipocket PID list\n(8 or 10 characters, comma separated)').grid(row=3, sticky=Tkconstants.E)
|
||||
cur_row = cur_row + 1
|
||||
Tkinter.Label(body, text='Mobipocket PID list\n(8 or 10 characters, comma separated)').grid(row=cur_row, sticky=Tkconstants.E)
|
||||
self.pidnums = Tkinter.StringVar()
|
||||
self.pidinfo = Tkinter.Entry(body, width=50, textvariable=self.pidnums)
|
||||
if 'pids' in self.prefs_array:
|
||||
self.pidnums.set(self.prefs_array['pids'])
|
||||
self.pidinfo.grid(row=3, column=1, sticky=sticky)
|
||||
self.pidinfo.grid(row=cur_row, column=1, sticky=sticky)
|
||||
|
||||
Tkinter.Label(body, text='eInk Kindle Serial Number list\n(16 characters, comma separated)').grid(row=4, sticky=Tkconstants.E)
|
||||
cur_row = cur_row + 1
|
||||
Tkinter.Label(body, text='eInk Kindle Serial Number list\n(16 characters, comma separated)').grid(row=cur_row, sticky=Tkconstants.E)
|
||||
self.sernums = Tkinter.StringVar()
|
||||
self.serinfo = Tkinter.Entry(body, width=50, textvariable=self.sernums)
|
||||
if 'serials' in self.prefs_array:
|
||||
self.sernums.set(self.prefs_array['serials'])
|
||||
self.serinfo.grid(row=4, column=1, sticky=sticky)
|
||||
self.serinfo.grid(row=cur_row, column=1, sticky=sticky)
|
||||
|
||||
Tkinter.Label(body, text='eReader data list\n(name:last 8 digits on credit card, comma separated)').grid(row=5, sticky=Tkconstants.E)
|
||||
cur_row = cur_row + 1
|
||||
Tkinter.Label(body, text='eReader data list\n(name:last 8 digits on credit card, comma separated)').grid(row=cur_row, sticky=Tkconstants.E)
|
||||
self.sdrmnums = Tkinter.StringVar()
|
||||
self.sdrminfo = Tkinter.Entry(body, width=50, textvariable=self.sdrmnums)
|
||||
if 'sdrms' in self.prefs_array:
|
||||
self.sdrmnums.set(self.prefs_array['sdrms'])
|
||||
self.sdrminfo.grid(row=5, column=1, sticky=sticky)
|
||||
self.sdrminfo.grid(row=cur_row, column=1, sticky=sticky)
|
||||
|
||||
Tkinter.Label(body, text="Output Folder (if blank, use input ebook's folder)").grid(row=6, sticky=Tkconstants.E)
|
||||
cur_row = cur_row + 1
|
||||
Tkinter.Label(body, text="Output Folder (if blank, use input ebook's folder)").grid(row=cur_row, sticky=Tkconstants.E)
|
||||
self.outpath = Tkinter.Entry(body, width=50)
|
||||
self.outpath.grid(row=6, column=1, sticky=sticky)
|
||||
self.outpath.grid(row=cur_row, column=1, sticky=sticky)
|
||||
if 'outdir' in self.prefs_array:
|
||||
dpath = self.prefs_array['outdir']
|
||||
self.outpath.insert(0, dpath)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_outpath)
|
||||
button.grid(row=6, column=2)
|
||||
button.grid(row=cur_row, column=2)
|
||||
|
||||
Tkinter.Label(body, text='').grid(row=7, column=0, columnspan=2, sticky=Tkconstants.N)
|
||||
cur_row = cur_row + 1
|
||||
Tkinter.Label(body, text='').grid(row=cur_row, column=0, columnspan=2, sticky=Tkconstants.N)
|
||||
|
||||
Tkinter.Label(body, text='Alternatively Process an eBook').grid(row=8, column=0, columnspan=2, sticky=Tkconstants.N)
|
||||
cur_row = cur_row + 1
|
||||
Tkinter.Label(body, text='Alternatively Process an eBook').grid(row=cur_row, column=0, columnspan=2, sticky=Tkconstants.N)
|
||||
|
||||
Tkinter.Label(body, text='Select an eBook to Process*').grid(row=9, sticky=Tkconstants.E)
|
||||
cur_row = cur_row + 1
|
||||
Tkinter.Label(body, text='Select an eBook to Process*').grid(row=cur_row, sticky=Tkconstants.E)
|
||||
self.bookpath = Tkinter.Entry(body, width=50)
|
||||
self.bookpath.grid(row=9, column=1, sticky=sticky)
|
||||
self.bookpath.grid(row=cur_row, column=1, sticky=sticky)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_bookpath)
|
||||
button.grid(row=9, column=2)
|
||||
button.grid(row=cur_row, column=2)
|
||||
|
||||
Tkinter.Label(body, font=("Helvetica", "10", "italic"), text='*To DeDRM multiple ebooks simultaneously, set your preferences and quit.\nThen drag and drop ebooks or folders onto the DeDRM_Drop_Target').grid(row=10, column=1, sticky=Tkconstants.E)
|
||||
cur_row = cur_row + 1
|
||||
Tkinter.Label(body, font=("Helvetica", "10", "italic"), text='*To DeDRM multiple ebooks simultaneously, set your preferences and quit.\nThen drag and drop ebooks or folders onto the DeDRM_Drop_Target').grid(row=cur_row, column=1, sticky=Tkconstants.E)
|
||||
|
||||
Tkinter.Label(body, text='').grid(row=11, column=0, columnspan=2, sticky=Tkconstants.E)
|
||||
cur_row = cur_row + 1
|
||||
Tkinter.Label(body, text='').grid(row=cur_row, column=0, columnspan=2, sticky=Tkconstants.E)
|
||||
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
|
@ -304,6 +329,24 @@ class PrefsDialog(Toplevel):
|
|||
self.kkpath.insert(0, kkpath)
|
||||
return
|
||||
|
||||
def get_akkpath(self):
|
||||
akkbpath = tkFileDialog.askopenfilename(parent=None, title='Select Android for Kindle backup file',
|
||||
defaultextension='.ab', filetypes=[('Kindle for Android backup file', '.ab'), ('All Files', '.*')])
|
||||
if akkbpath:
|
||||
# call androidkindlekey here
|
||||
prefdir = self.prefs_array['dir']
|
||||
androidkindlekeyfile = os.path.join(prefdir,'kindlekey.k4a')
|
||||
import androidkindlekey
|
||||
try:
|
||||
androidkindlekey.getkey(androidkindlekeyfile, akkbpath)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
pass
|
||||
if os.path.isfile(androidkindlekeyfile):
|
||||
self.akkpath.delete(0, Tkconstants.END)
|
||||
self.akkpath.insert(0, androidkindlekeyfile)
|
||||
return
|
||||
|
||||
def get_bnkpath(self):
|
||||
cpath = self.bnkpath.get()
|
||||
bnkpath = tkFileDialog.askopenfilename(initialdir = cpath, parent=None, title='Select Barnes and Noble Key file',
|
||||
|
|
|
@ -5,7 +5,7 @@ from __future__ import with_statement
|
|||
|
||||
# androidkindlekey.py
|
||||
# Copyright © 2013-15 by Thom
|
||||
# Some portions Copyright © 2011-15 by Apprentice Alf and Apprentice Harper
|
||||
# Some portions Copyright © 2010-15 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
#
|
||||
|
||||
# Revision history:
|
||||
|
@ -14,14 +14,14 @@ from __future__ import with_statement
|
|||
# 1.2 - Changed to be callable from AppleScript by returning only serial number
|
||||
# - and changed name to androidkindlekey.py
|
||||
# - and added in unicode command line support
|
||||
|
||||
# 1.3 - added in TkInter interface, output to a file and attempt to get backup from a connected android device.
|
||||
|
||||
"""
|
||||
Retrieve Kindle for Android Serial Number.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.2'
|
||||
__version__ = '1.3'
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
@ -272,7 +272,32 @@ def get_serials(path=STORAGE):
|
|||
|
||||
return serials
|
||||
|
||||
__all__ = [ 'get_serials' ]
|
||||
__all__ = [ 'get_serials', 'getkey']
|
||||
|
||||
# interface for Python DeDRM
|
||||
# returns single key or multiple keys, depending on path or file passed in
|
||||
def getkey(outpath, inpath):
|
||||
keys = get_serials(inpath)
|
||||
if len(keys) > 0:
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(outpath,u"kindlekey{0:d}.k4a".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def usage(progname):
|
||||
print u"{0} v{1}\nCopyright © 2013-2015 Thom and Apprentice Harper".format(progname,__version__)
|
||||
|
@ -283,7 +308,7 @@ def usage(progname):
|
|||
print u""
|
||||
print u"Serial number is written to standard output."
|
||||
print u"Usage:"
|
||||
print u" {0:s} [-h] <inputfile>".format(progname)
|
||||
print u" {0:s} [-h] [-b <backup.ab>] [<outpath>]".format(progname)
|
||||
|
||||
|
||||
def cli_main():
|
||||
|
@ -291,47 +316,131 @@ def cli_main():
|
|||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2010-2015 Thom, some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "h")
|
||||
opts, args = getopt.getopt(argv[1:], "hb:")
|
||||
except getopt.GetoptError, err:
|
||||
usage(progname)
|
||||
print u"\nError in options or arguments: {0}".format(err.args[0])
|
||||
return 2
|
||||
|
||||
files = []
|
||||
inpath = ""
|
||||
for o, a in opts:
|
||||
if o == "-h":
|
||||
usage(progname)
|
||||
return 0
|
||||
if o == "-b":
|
||||
inpath = a
|
||||
|
||||
if len(args) > 1:
|
||||
usage(progname)
|
||||
return 2
|
||||
|
||||
inpath = args[0]
|
||||
if not os.path.isabs(inpath):
|
||||
inpath = os.path.abspath(inpath)
|
||||
if len(args) == 1:
|
||||
# save to the specified file or directory
|
||||
outpath = args[0]
|
||||
if not os.path.isabs(outpath):
|
||||
outpath = os.path.join(os.path.dirname(argv[0]),outpath)
|
||||
outpath = os.path.abspath(outpath)
|
||||
else:
|
||||
# save to the same directory as the script
|
||||
outpath = os.path.dirname(argv[0])
|
||||
|
||||
inpath = os.path.realpath(os.path.normpath(inpath))
|
||||
# make sure the outpath is OK
|
||||
outpath = os.path.realpath(os.path.normpath(outpath))
|
||||
|
||||
if not os.path.isfile(inpath):
|
||||
usage(progname)
|
||||
print u"\n{0:s} file not found".format(inpath)
|
||||
return 2
|
||||
|
||||
serials = get_serials(inpath)
|
||||
if not getkey(outpath, inpath):
|
||||
print u"Could not retrieve Kindle for Android key."
|
||||
return 0
|
||||
|
||||
if len(serials) == 0:
|
||||
print u"No keys found in {0:s}".format(inpath)
|
||||
return 2
|
||||
|
||||
for serial in serials:
|
||||
print serial
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import tkFileDialog
|
||||
import traceback
|
||||
except:
|
||||
print "Tkinter not installed"
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"Select backup.ab file")
|
||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||
body = Tkinter.Frame(self)
|
||||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
Tkinter.Label(body, text=u"Backup file").grid(row=0, column=0)
|
||||
self.keypath = Tkinter.Entry(body, width=40)
|
||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||
self.keypath.insert(2, u"backup.ab")
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||
button.grid(row=0, column=2)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
button2 = Tkinter.Button(
|
||||
buttons, text=u"Extract", width=10, command=self.generate)
|
||||
button2.pack(side=Tkconstants.LEFT)
|
||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||
button3 = Tkinter.Button(
|
||||
buttons, text=u"Quit", width=10, command=self.quit)
|
||||
button3.pack(side=Tkconstants.RIGHT)
|
||||
|
||||
def get_keypath(self):
|
||||
keypath = tkFileDialog.askopenfilename(
|
||||
parent=None, title=u"Select backup.ab file",
|
||||
defaultextension=u".ab",
|
||||
filetypes=[('adb backup com.amazon.kindle', '.ab'),
|
||||
('All Files', '.*')])
|
||||
if keypath:
|
||||
keypath = os.path.normpath(keypath)
|
||||
self.keypath.delete(0, Tkconstants.END)
|
||||
self.keypath.insert(0, keypath)
|
||||
return
|
||||
|
||||
def generate(self):
|
||||
inpath = self.keypath.get()
|
||||
self.status['text'] = u"Getting key..."
|
||||
try:
|
||||
keys = get_serials(inpath)
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(progpath,u"kindlekey{0:d}.k4a".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||
return
|
||||
self.status['text'] = u"Select backup.ab file"
|
||||
|
||||
argv=unicode_argv()
|
||||
progpath, progname = os.path.split(argv[0])
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Kindle for Android Key Extraction v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
sys.exit(cli_main())
|
||||
usage(os.path.basename(unicode_argv()[0]))
|
||||
sys.exit(0);
|
||||
sys.exit(gui_main())
|
||||
|
|
|
@ -158,6 +158,14 @@ def decryptk4mobi(infile, outdir, rscpath):
|
|||
serialstr = serialstr.strip()
|
||||
if serialstr != '':
|
||||
serialnums = serialstr.split(',')
|
||||
files = os.listdir(rscpath)
|
||||
filefilter = re.compile("\.k4a$", re.IGNORECASE)
|
||||
files = filter(filefilter.search, files)
|
||||
if files:
|
||||
for filename in files:
|
||||
dpath = os.path.join(rscpath,filename)
|
||||
androidserial = open(keypath,'r').read()
|
||||
serialnums.append(androidserial)
|
||||
kDatabaseFiles = []
|
||||
files = os.listdir(rscpath)
|
||||
filefilter = re.compile("\.k4i$", re.IGNORECASE)
|
||||
|
|
|
@ -5,7 +5,7 @@ from __future__ import with_statement
|
|||
|
||||
# androidkindlekey.py
|
||||
# Copyright © 2013-15 by Thom
|
||||
# Some portions Copyright © 2011-15 by Apprentice Alf and Apprentice Harper
|
||||
# Some portions Copyright © 2010-15 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
#
|
||||
|
||||
# Revision history:
|
||||
|
@ -14,14 +14,14 @@ from __future__ import with_statement
|
|||
# 1.2 - Changed to be callable from AppleScript by returning only serial number
|
||||
# - and changed name to androidkindlekey.py
|
||||
# - and added in unicode command line support
|
||||
|
||||
# 1.3 - added in TkInter interface, output to a file and attempt to get backup from a connected android device.
|
||||
|
||||
"""
|
||||
Retrieve Kindle for Android Serial Number.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.2'
|
||||
__version__ = '1.3'
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
@ -272,7 +272,32 @@ def get_serials(path=STORAGE):
|
|||
|
||||
return serials
|
||||
|
||||
__all__ = [ 'get_serials' ]
|
||||
__all__ = [ 'get_serials', 'getkey']
|
||||
|
||||
# interface for Python DeDRM
|
||||
# returns single key or multiple keys, depending on path or file passed in
|
||||
def getkey(outpath, inpath):
|
||||
keys = get_serials(inpath)
|
||||
if len(keys) > 0:
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(outpath,u"kindlekey{0:d}.k4a".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def usage(progname):
|
||||
print u"{0} v{1}\nCopyright © 2013-2015 Thom and Apprentice Harper".format(progname,__version__)
|
||||
|
@ -283,7 +308,7 @@ def usage(progname):
|
|||
print u""
|
||||
print u"Serial number is written to standard output."
|
||||
print u"Usage:"
|
||||
print u" {0:s} [-h] <inputfile>".format(progname)
|
||||
print u" {0:s} [-h] [-b <backup.ab>] [<outpath>]".format(progname)
|
||||
|
||||
|
||||
def cli_main():
|
||||
|
@ -291,47 +316,131 @@ def cli_main():
|
|||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2010-2015 Thom, some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "h")
|
||||
opts, args = getopt.getopt(argv[1:], "hb:")
|
||||
except getopt.GetoptError, err:
|
||||
usage(progname)
|
||||
print u"\nError in options or arguments: {0}".format(err.args[0])
|
||||
return 2
|
||||
|
||||
files = []
|
||||
inpath = ""
|
||||
for o, a in opts:
|
||||
if o == "-h":
|
||||
usage(progname)
|
||||
return 0
|
||||
if o == "-b":
|
||||
inpath = a
|
||||
|
||||
if len(args) > 1:
|
||||
usage(progname)
|
||||
return 2
|
||||
|
||||
inpath = args[0]
|
||||
if not os.path.isabs(inpath):
|
||||
inpath = os.path.abspath(inpath)
|
||||
if len(args) == 1:
|
||||
# save to the specified file or directory
|
||||
outpath = args[0]
|
||||
if not os.path.isabs(outpath):
|
||||
outpath = os.path.join(os.path.dirname(argv[0]),outpath)
|
||||
outpath = os.path.abspath(outpath)
|
||||
else:
|
||||
# save to the same directory as the script
|
||||
outpath = os.path.dirname(argv[0])
|
||||
|
||||
inpath = os.path.realpath(os.path.normpath(inpath))
|
||||
# make sure the outpath is OK
|
||||
outpath = os.path.realpath(os.path.normpath(outpath))
|
||||
|
||||
if not os.path.isfile(inpath):
|
||||
usage(progname)
|
||||
print u"\n{0:s} file not found".format(inpath)
|
||||
return 2
|
||||
|
||||
serials = get_serials(inpath)
|
||||
if not getkey(outpath, inpath):
|
||||
print u"Could not retrieve Kindle for Android key."
|
||||
return 0
|
||||
|
||||
if len(serials) == 0:
|
||||
print u"No keys found in {0:s}".format(inpath)
|
||||
return 2
|
||||
|
||||
for serial in serials:
|
||||
print serial
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import tkFileDialog
|
||||
import traceback
|
||||
except:
|
||||
print "Tkinter not installed"
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"Select backup.ab file")
|
||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||
body = Tkinter.Frame(self)
|
||||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
Tkinter.Label(body, text=u"Backup file").grid(row=0, column=0)
|
||||
self.keypath = Tkinter.Entry(body, width=40)
|
||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||
self.keypath.insert(2, u"backup.ab")
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||
button.grid(row=0, column=2)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
button2 = Tkinter.Button(
|
||||
buttons, text=u"Extract", width=10, command=self.generate)
|
||||
button2.pack(side=Tkconstants.LEFT)
|
||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||
button3 = Tkinter.Button(
|
||||
buttons, text=u"Quit", width=10, command=self.quit)
|
||||
button3.pack(side=Tkconstants.RIGHT)
|
||||
|
||||
def get_keypath(self):
|
||||
keypath = tkFileDialog.askopenfilename(
|
||||
parent=None, title=u"Select backup.ab file",
|
||||
defaultextension=u".ab",
|
||||
filetypes=[('adb backup com.amazon.kindle', '.ab'),
|
||||
('All Files', '.*')])
|
||||
if keypath:
|
||||
keypath = os.path.normpath(keypath)
|
||||
self.keypath.delete(0, Tkconstants.END)
|
||||
self.keypath.insert(0, keypath)
|
||||
return
|
||||
|
||||
def generate(self):
|
||||
inpath = self.keypath.get()
|
||||
self.status['text'] = u"Getting key..."
|
||||
try:
|
||||
keys = get_serials(inpath)
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(progpath,u"kindlekey{0:d}.k4a".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||
return
|
||||
self.status['text'] = u"Select backup.ab file"
|
||||
|
||||
argv=unicode_argv()
|
||||
progpath, progname = os.path.split(argv[0])
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Kindle for Android Key Extraction v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
sys.exit(cli_main())
|
||||
usage(os.path.basename(unicode_argv()[0]))
|
||||
sys.exit(0);
|
||||
sys.exit(gui_main())
|
||||
|
|
|
@ -109,14 +109,15 @@ class ConfigWidget(QWidget):
|
|||
self.ereader_button.setToolTip(_(u"Click to manage keys for eReader ebooks"))
|
||||
self.ereader_button.setText(u"eReader ebooks")
|
||||
self.ereader_button.clicked.connect(self.ereader_keys)
|
||||
|
||||
button_layout.addWidget(self.adept_button)
|
||||
button_layout.addWidget(self.kindle_key_button)
|
||||
button_layout.addWidget(self.kindle_serial_button)
|
||||
button_layout.addWidget(self.kindle_android_button)
|
||||
button_layout.addWidget(self.bandn_button)
|
||||
button_layout.addWidget(self.mobi_button)
|
||||
button_layout.addWidget(self.ereader_button)
|
||||
button_layout.addWidget(self.adept_button)
|
||||
button_layout.addWidget(self.kindle_key_button)
|
||||
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
def kindle_serials(self):
|
||||
|
@ -124,7 +125,7 @@ class ConfigWidget(QWidget):
|
|||
d.exec_()
|
||||
|
||||
def kindle_android_serials(self):
|
||||
d = ManageKeysDialog(self,u"Kindle for Andoid Serial Number",self.tempdedrmprefs['androidserials'], AddAndroidSerialDialog, 'ab')
|
||||
d = ManageKeysDialog(self,u"Kindle for Andoid",self.tempdedrmprefs['androidserials'], AddAndroidSerialDialog, 'ab')
|
||||
d.exec_()
|
||||
|
||||
def kindle_keys(self):
|
||||
|
@ -904,7 +905,7 @@ class AddAndroidSerialDialog(QDialog):
|
|||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"Kindle for Android Serial Number:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(u"Enter a Kindle for ANdroid serial number. These can be found using the androidkindlekey.py script.")
|
||||
self.key_ledit.setToolTip(u"Enter a Kindle for Android serial number. These can be found using the androidkindlekey.py script.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
key_label = QLabel(_(''), self)
|
||||
key_label.setAlignment(Qt.AlignHCenter)
|
||||
|
|
|
@ -240,6 +240,7 @@ def gui_main():
|
|||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import tkFileDialog
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
|
|
@ -0,0 +1,446 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# androidkindlekey.py
|
||||
# Copyright © 2013-15 by Thom
|
||||
# Some portions Copyright © 2010-15 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
#
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Android serial number extracted from AmazonSecureStorage.xml
|
||||
# 1.1 - Fixes and enhancements of some kind
|
||||
# 1.2 - Changed to be callable from AppleScript by returning only serial number
|
||||
# - and changed name to androidkindlekey.py
|
||||
# - and added in unicode command line support
|
||||
# 1.3 - added in TkInter interface, output to a file and attempt to get backup from a connected android device.
|
||||
|
||||
"""
|
||||
Retrieve Kindle for Android Serial Number.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.3'
|
||||
|
||||
import os
|
||||
import sys
|
||||
import getopt
|
||||
import tempfile
|
||||
import zlib
|
||||
import tarfile
|
||||
from hashlib import md5
|
||||
from cStringIO import StringIO
|
||||
from binascii import a2b_hex, b2a_hex
|
||||
|
||||
# Routines common to Mac and PC
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
try:
|
||||
from calibre.constants import iswindows, isosx
|
||||
except:
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def unicode_argv():
|
||||
if iswindows:
|
||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
||||
# strings.
|
||||
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
|
||||
# as a list of Unicode strings and encode them as utf-8
|
||||
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = GetCommandLineW()
|
||||
argc = c_int(0)
|
||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(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"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
|
||||
class DrmException(Exception):
|
||||
pass
|
||||
|
||||
STORAGE = u"backup.ab"
|
||||
STORAGE1 = u"AmazonSecureStorage.xml"
|
||||
STORAGE2 = u"map_data_storage.db"
|
||||
|
||||
class AndroidObfuscation(object):
|
||||
'''AndroidObfuscation
|
||||
For the key, it's written in java, and run in android dalvikvm
|
||||
'''
|
||||
|
||||
key = a2b_hex('0176e04c9408b1702d90be333fd53523')
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
cipher = self._get_cipher()
|
||||
padding = len(self.key) - len(plaintext) % len(self.key)
|
||||
plaintext += chr(padding) * padding
|
||||
return b2a_hex(cipher.encrypt(plaintext))
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
cipher = self._get_cipher()
|
||||
plaintext = cipher.decrypt(a2b_hex(ciphertext))
|
||||
return plaintext[:-ord(plaintext[-1])]
|
||||
|
||||
def _get_cipher(self):
|
||||
try:
|
||||
from Crypto.Cipher import AES
|
||||
return AES.new(self.key)
|
||||
except ImportError:
|
||||
from aescbc import AES, noPadding
|
||||
return AES(self.key, padding=noPadding())
|
||||
|
||||
class AndroidObfuscationV2(AndroidObfuscation):
|
||||
'''AndroidObfuscationV2
|
||||
'''
|
||||
|
||||
count = 503
|
||||
password = 'Thomsun was here!'
|
||||
|
||||
def __init__(self, salt):
|
||||
key = self.password + salt
|
||||
for _ in range(self.count):
|
||||
key = md5(key).digest()
|
||||
self.key = key[:8]
|
||||
self.iv = key[8:16]
|
||||
|
||||
def _get_cipher(self):
|
||||
try :
|
||||
from Crypto.Cipher import DES
|
||||
return DES.new(self.key, DES.MODE_CBC, self.iv)
|
||||
except ImportError:
|
||||
from python_des import Des, CBC
|
||||
return Des(self.key, CBC, self.iv)
|
||||
|
||||
def parse_preference(path):
|
||||
''' parse android's shared preference xml '''
|
||||
storage = {}
|
||||
read = open(path)
|
||||
for line in read:
|
||||
line = line.strip()
|
||||
# <string name="key">value</string>
|
||||
if line.startswith('<string name="'):
|
||||
index = line.find('"', 14)
|
||||
key = line[14:index]
|
||||
value = line[index+2:-9]
|
||||
storage[key] = value
|
||||
read.close()
|
||||
return storage
|
||||
|
||||
def get_serials1(path=STORAGE1):
|
||||
''' get serials from android's shared preference xml '''
|
||||
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
|
||||
storage = parse_preference(path)
|
||||
salt = storage.get('AmazonSaltKey')
|
||||
if salt and len(salt) == 16:
|
||||
obfuscation = AndroidObfuscationV2(a2b_hex(salt))
|
||||
else:
|
||||
obfuscation = AndroidObfuscation()
|
||||
|
||||
def get_value(key):
|
||||
encrypted_key = obfuscation.encrypt(key)
|
||||
encrypted_value = storage.get(encrypted_key)
|
||||
if encrypted_value:
|
||||
return obfuscation.decrypt(encrypted_value)
|
||||
return ''
|
||||
|
||||
# also see getK4Pids in kgenpids.py
|
||||
try:
|
||||
dsnid = get_value('DsnId')
|
||||
except:
|
||||
sys.stderr.write('cannot get DsnId\n')
|
||||
return []
|
||||
|
||||
try:
|
||||
tokens = set(get_value('kindle.account.tokens').split(','))
|
||||
except:
|
||||
return []
|
||||
|
||||
serials = []
|
||||
for token in tokens:
|
||||
if token:
|
||||
serials.append('%s%s' % (dsnid, token))
|
||||
return serials
|
||||
|
||||
def get_serials2(path=STORAGE2):
|
||||
''' get serials from android's shared preference xml '''
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
|
||||
import sqlite3
|
||||
connection = sqlite3.connect(path)
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%token.device.deviceserialname%' ''')
|
||||
dsns = [x[0].encode('utf8') for x in cursor.fetchall()]
|
||||
|
||||
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
|
||||
tokens = [x[0].encode('utf8') for x in cursor.fetchall()]
|
||||
serials = []
|
||||
for x in dsns:
|
||||
for y in tokens:
|
||||
serials.append('%s%s' % (x, y))
|
||||
return serials
|
||||
|
||||
def get_serials(path=STORAGE):
|
||||
'''get serials from files in from android backup.ab
|
||||
backup.ab can be get using adb command:
|
||||
shell> adb backup com.amazon.kindle
|
||||
or from individual files if they're passed.
|
||||
'''
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
|
||||
basename = os.path.basename(path)
|
||||
if basename == STORAGE1:
|
||||
return get_serials1(path)
|
||||
elif basename == STORAGE2:
|
||||
return get_serials2(path)
|
||||
|
||||
output = None
|
||||
try :
|
||||
read = open(path, 'rb')
|
||||
head = read.read(24)
|
||||
if head[:14] == 'ANDROID BACKUP':
|
||||
output = StringIO(zlib.decompress(read.read()))
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
read.close()
|
||||
|
||||
if not output:
|
||||
return []
|
||||
|
||||
serials = []
|
||||
tar = tarfile.open(fileobj=output)
|
||||
for member in tar.getmembers():
|
||||
if member.name.strip().endswith(STORAGE1):
|
||||
write = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
||||
write.write(tar.extractfile(member).read())
|
||||
write.close()
|
||||
write_path = os.path.abspath(write.name)
|
||||
serials.extend(get_serials1(write_path))
|
||||
os.remove(write_path)
|
||||
elif member.name.strip().endswith(STORAGE2):
|
||||
write = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
||||
write.write(tar.extractfile(member).read())
|
||||
write.close()
|
||||
write_path = os.path.abspath(write.name)
|
||||
serials.extend(get_serials2(write_path))
|
||||
os.remove(write_path)
|
||||
|
||||
return serials
|
||||
|
||||
__all__ = [ 'get_serials', 'getkey']
|
||||
|
||||
# interface for Python DeDRM
|
||||
# returns single key or multiple keys, depending on path or file passed in
|
||||
def getkey(outpath, inpath):
|
||||
keys = get_serials(inpath)
|
||||
if len(keys) > 0:
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(outpath,u"kindlekey{0:d}.k4a".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def usage(progname):
|
||||
print u"{0} v{1}\nCopyright © 2013-2015 Thom and Apprentice Harper".format(progname,__version__)
|
||||
print u"Decrypts the serial number of Kindle For Android from Android backup or file"
|
||||
print u"Get backup.ab file using adb backup com.amazon.kindle for Android 4.0+."
|
||||
print u"Otherwise extract AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml"
|
||||
print u"Or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db"
|
||||
print u""
|
||||
print u"Serial number is written to standard output."
|
||||
print u"Usage:"
|
||||
print u" {0:s} [-h] [-b <backup.ab>] [<outpath>]".format(progname)
|
||||
|
||||
|
||||
def cli_main():
|
||||
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-2015 Thom, some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "hb:")
|
||||
except getopt.GetoptError, err:
|
||||
usage(progname)
|
||||
print u"\nError in options or arguments: {0}".format(err.args[0])
|
||||
return 2
|
||||
|
||||
inpath = ""
|
||||
for o, a in opts:
|
||||
if o == "-h":
|
||||
usage(progname)
|
||||
return 0
|
||||
if o == "-b":
|
||||
inpath = a
|
||||
|
||||
if len(args) > 1:
|
||||
usage(progname)
|
||||
return 2
|
||||
|
||||
if len(args) == 1:
|
||||
# save to the specified file or directory
|
||||
outpath = args[0]
|
||||
if not os.path.isabs(outpath):
|
||||
outpath = os.path.join(os.path.dirname(argv[0]),outpath)
|
||||
outpath = os.path.abspath(outpath)
|
||||
else:
|
||||
# save to the same directory as the script
|
||||
outpath = os.path.dirname(argv[0])
|
||||
|
||||
# make sure the outpath is OK
|
||||
outpath = os.path.realpath(os.path.normpath(outpath))
|
||||
|
||||
if not os.path.isfile(inpath):
|
||||
usage(progname)
|
||||
print u"\n{0:s} file not found".format(inpath)
|
||||
return 2
|
||||
|
||||
if not getkey(outpath, inpath):
|
||||
print u"Could not retrieve Kindle for Android key."
|
||||
return 0
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import tkFileDialog
|
||||
import traceback
|
||||
except:
|
||||
print "Tkinter not installed"
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"Select backup.ab file")
|
||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||
body = Tkinter.Frame(self)
|
||||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
Tkinter.Label(body, text=u"Backup file").grid(row=0, column=0)
|
||||
self.keypath = Tkinter.Entry(body, width=40)
|
||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||
self.keypath.insert(2, u"backup.ab")
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||
button.grid(row=0, column=2)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
button2 = Tkinter.Button(
|
||||
buttons, text=u"Extract", width=10, command=self.generate)
|
||||
button2.pack(side=Tkconstants.LEFT)
|
||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||
button3 = Tkinter.Button(
|
||||
buttons, text=u"Quit", width=10, command=self.quit)
|
||||
button3.pack(side=Tkconstants.RIGHT)
|
||||
|
||||
def get_keypath(self):
|
||||
keypath = tkFileDialog.askopenfilename(
|
||||
parent=None, title=u"Select backup.ab file",
|
||||
defaultextension=u".ab",
|
||||
filetypes=[('adb backup com.amazon.kindle', '.ab'),
|
||||
('All Files', '.*')])
|
||||
if keypath:
|
||||
keypath = os.path.normpath(keypath)
|
||||
self.keypath.delete(0, Tkconstants.END)
|
||||
self.keypath.insert(0, keypath)
|
||||
return
|
||||
|
||||
def generate(self):
|
||||
inpath = self.keypath.get()
|
||||
self.status['text'] = u"Getting key..."
|
||||
try:
|
||||
keys = get_serials(inpath)
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(progpath,u"kindlekey{0:d}.k4a".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||
return
|
||||
self.status['text'] = u"Select backup.ab file"
|
||||
|
||||
argv=unicode_argv()
|
||||
progpath, progname = os.path.split(argv[0])
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Kindle for Android Key Extraction v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
sys.exit(cli_main())
|
||||
sys.exit(gui_main())
|
Loading…
Reference in New Issue