]> xmof Git - DeDRM.git/commitdiff
changed for android support - in progress
authorApprentice Harper <apprenticealf@gmail.com>
Tue, 24 Mar 2015 07:04:06 +0000 (07:04 +0000)
committerApprentice Alf <apprenticealf@gmail.com>
Tue, 24 Mar 2015 07:04:06 +0000 (07:04 +0000)
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/androidkindlekey.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/androidkindlekey.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/scriptinterface.py
DeDRM_calibre_plugin/DeDRM_plugin/androidkindlekey.py
DeDRM_calibre_plugin/DeDRM_plugin/config.py
Other_Tools/DRM_Key_Scripts/Barnes_and_Noble_ePubs/ignoblekeygen.pyw
Other_Tools/DRM_Key_Scripts/Kindle_for_Android/androidkindlekey.pyw [new file with mode: 0644]
Other_Tools/DRM_Key_Scripts/Kindle_for_iOS/kindleiospidgen.pyw [moved from Other_Tools/DRM_Key_Scripts/Kindle_for_iOS/kindleiospidgen.py with 100% similarity]

index fd4c19355fa5938ecf21cc1b24374152ad5c345c..6e6aef742602a42f8beecacf9f716b6c24d8f468 100644 (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())
index 7381664e9f49e3acd1f11c7d56dab3d2aee35708..d33fef9ed2f0dd5e64972cfd181696240255477a 100644 (file)
@@ -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)
+
+        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)
 
-        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='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',
index fd4c19355fa5938ecf21cc1b24374152ad5c345c..6e6aef742602a42f8beecacf9f716b6c24d8f468 100644 (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())
index 3be643f5447bff1896e2816ff75265873b663b84..1212f51f9447d8346cd8409a5a7dbe0c2384fdbf 100644 (file)
@@ -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)
index fd4c19355fa5938ecf21cc1b24374152ad5c345c..6e6aef742602a42f8beecacf9f716b6c24d8f468 100644 (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())
index 1357b496f469461d881cfeca9c0767bff6b8e513..d47425774ac0005d0a91913f0bcd0d82a831dfc1 100644 (file)
@@ -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)
index 5118c870358d1c1dfcf80faa9ca771968b34f7c2..83bb2ceb03c0e97128575df01c303a035a78c47d 100644 (file)
@@ -240,6 +240,7 @@ def gui_main():
         import Tkinter
         import Tkconstants
         import tkMessageBox
+        import tkFileDialog
         import traceback
     except:
         return cli_main()
diff --git a/Other_Tools/DRM_Key_Scripts/Kindle_for_Android/androidkindlekey.pyw b/Other_Tools/DRM_Key_Scripts/Kindle_for_Android/androidkindlekey.pyw
new file mode 100644 (file)
index 0000000..6e6aef7
--- /dev/null
@@ -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())