]> xmof Git - DeDRM.git/commitdiff
Merge of bugfix 6.2.1 into master
authorApprentice Harper <apprenticealf@gmail.com>
Thu, 26 Mar 2015 07:31:45 +0000 (07:31 +0000)
committerApprentice Alf <apprenticealf@gmail.com>
Thu, 26 Mar 2015 07:31:45 +0000 (07:31 +0000)
1  2 
DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Help.htm
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py
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/DeDRM_Help.htm
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/androidkindlekey.py
DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Help.htm
DeDRM_calibre_plugin/DeDRM_plugin/__init__.py
DeDRM_calibre_plugin/DeDRM_plugin/androidkindlekey.py

index 25ae8b22dc338ca29e07feb9e7ce8ee8e758a575,acdebbbed719301697eea0072f5ea1773dbb83a8..1e1d4cc5727e895702ba1e8f98e8785dac53924a
@@@ -24,7 -24,7 +24,7 @@@
        <key>CFBundleExecutable</key>
        <string>droplet</string>
        <key>CFBundleGetInfoString</key>
-       <string>DeDRM AppleScript 6.2.0. Written 2010–2015 by Apprentice Alf et al.</string>
 -      <string>DeDRM AppleScript 6.2.1 Written 2010–2015 by Apprentice Alf et al.</string>
++      <string>DeDRM AppleScript 6.3.0 Written 2010–2015 by Apprentice Alf et al.</string>
        <key>CFBundleIconFile</key>
        <string>DeDRM</string>
        <key>CFBundleIdentifier</key>
index 019ba360d34aa960bd1a85964cd6c94762eb6bf8,6d703ddb96c57a63a1a5f810a092693a6ee386c4..86653eba7f30b7f7c6f5e2c16702b93420ac271f
@@@ -17,7 -17,7 +17,7 @@@ p {margin-top: 0
  
  <body>
  
- <h1>DeDRM Plugin <span class="version">(v6.1.0)</span></h1>
 -<h1>DeDRM Plugin <span class="version">(v6.2.1)</span></h1>
++<h1>DeDRM Plugin <span class="version">(v6.3.0)</span></h1>
  
  <p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
  
index ef1e1b44a5c85004e1c1e361ece179a2fd72c59a,f0bf5359dc0bd4424b9d04ac8fa2326d8c948bf5..0ba33f4eea9f7f43f2fd9d36b8485bb2713cddb4
@@@ -39,7 -39,7 +39,8 @@@ __docformat__ = 'restructuredtext en
  #   6.1.0 - Fixed multiple books import problem and PDF import with no key problem
  #   6.2.0 - Support for getting B&N key from nook Study log. Fix for UTF-8 filenames in Adobe ePubs.
  #           Fix for not copying needed files. Fix for getting default Adobe key for PDFs
+ #   6.2.1 - Fix for non-ascii Windows user names
 +#   6.3.0 - Added in Kindle for Android serial number solution
  
  """
  Decrypt DRMed ebooks.
index 6e6aef742602a42f8beecacf9f716b6c24d8f468,0000000000000000000000000000000000000000..d88e1df7e6a0d9d9fe68497fe1719b7e848adf21
mode 100644,000000..100644
--- /dev/null
@@@ -1,446 -1,0 +1,444 @@@
- # Copyright © 2013-15 by Thom
- # Some portions Copyright © 2010-15 by some_updates, Apprentice Alf and Apprentice Harper
 +#!/usr/bin/env python
 +# -*- coding: utf-8 -*-
 +
 +from __future__ import with_statement
 +
 +# androidkindlekey.py
- #  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
++# Copyright © 2013-15 by Thom and Apprentice Harper
++# Some portions Copyright © 2010-15 by some_updates and Apprentice Alf
 +#
 +
 +# Revision history:
++#  1.0   - AmazonSecureStorage.xml decryption to serial number
++#  1.1   - map_data_storage.db decryption to serial number
++#  1.2   - BugFix
 +#  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())
index d33fef9ed2f0dd5e64972cfd181696240255477a,b56dc8d11a885bae510225b205180dee7dd92ce8..e27355f9d986871d8a5375cdc0757cb5cf731f13
  #   6.0.4 - Fix for other potential unicode problems
  #   6.0.5 - Fix typo
  #   6.2.0 - Update to match plugin and AppleScript
+ #   6.2.1 - Fix for non-ascii user names
 +#   6.3.0 - Add in Android support
  
 -__version__ = '6.2.1'
 +__version__ = '6.3.0'
  
  import sys
  import os, os.path
index 019ba360d34aa960bd1a85964cd6c94762eb6bf8,6d703ddb96c57a63a1a5f810a092693a6ee386c4..86653eba7f30b7f7c6f5e2c16702b93420ac271f
@@@ -17,7 -17,7 +17,7 @@@ p {margin-top: 0
  
  <body>
  
- <h1>DeDRM Plugin <span class="version">(v6.1.0)</span></h1>
 -<h1>DeDRM Plugin <span class="version">(v6.2.1)</span></h1>
++<h1>DeDRM Plugin <span class="version">(v6.3.0)</span></h1>
  
  <p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
  
index ef1e1b44a5c85004e1c1e361ece179a2fd72c59a,f0bf5359dc0bd4424b9d04ac8fa2326d8c948bf5..0ba33f4eea9f7f43f2fd9d36b8485bb2713cddb4
@@@ -39,7 -39,7 +39,8 @@@ __docformat__ = 'restructuredtext en
  #   6.1.0 - Fixed multiple books import problem and PDF import with no key problem
  #   6.2.0 - Support for getting B&N key from nook Study log. Fix for UTF-8 filenames in Adobe ePubs.
  #           Fix for not copying needed files. Fix for getting default Adobe key for PDFs
+ #   6.2.1 - Fix for non-ascii Windows user names
 +#   6.3.0 - Added in Kindle for Android serial number solution
  
  """
  Decrypt DRMed ebooks.
index 6e6aef742602a42f8beecacf9f716b6c24d8f468,0000000000000000000000000000000000000000..d88e1df7e6a0d9d9fe68497fe1719b7e848adf21
mode 100644,000000..100644
--- /dev/null
@@@ -1,446 -1,0 +1,444 @@@
- # Copyright © 2013-15 by Thom
- # Some portions Copyright © 2010-15 by some_updates, Apprentice Alf and Apprentice Harper
 +#!/usr/bin/env python
 +# -*- coding: utf-8 -*-
 +
 +from __future__ import with_statement
 +
 +# androidkindlekey.py
- #  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
++# Copyright © 2013-15 by Thom and Apprentice Harper
++# Some portions Copyright © 2010-15 by some_updates and Apprentice Alf
 +#
 +
 +# Revision history:
++#  1.0   - AmazonSecureStorage.xml decryption to serial number
++#  1.1   - map_data_storage.db decryption to serial number
++#  1.2   - BugFix
 +#  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())
index 019ba360d34aa960bd1a85964cd6c94762eb6bf8,6d703ddb96c57a63a1a5f810a092693a6ee386c4..86653eba7f30b7f7c6f5e2c16702b93420ac271f
@@@ -17,7 -17,7 +17,7 @@@ p {margin-top: 0
  
  <body>
  
- <h1>DeDRM Plugin <span class="version">(v6.1.0)</span></h1>
 -<h1>DeDRM Plugin <span class="version">(v6.2.1)</span></h1>
++<h1>DeDRM Plugin <span class="version">(v6.3.0)</span></h1>
  
  <p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
  
index ef1e1b44a5c85004e1c1e361ece179a2fd72c59a,f0bf5359dc0bd4424b9d04ac8fa2326d8c948bf5..0ba33f4eea9f7f43f2fd9d36b8485bb2713cddb4
@@@ -39,7 -39,7 +39,8 @@@ __docformat__ = 'restructuredtext en
  #   6.1.0 - Fixed multiple books import problem and PDF import with no key problem
  #   6.2.0 - Support for getting B&N key from nook Study log. Fix for UTF-8 filenames in Adobe ePubs.
  #           Fix for not copying needed files. Fix for getting default Adobe key for PDFs
+ #   6.2.1 - Fix for non-ascii Windows user names
 +#   6.3.0 - Added in Kindle for Android serial number solution
  
  """
  Decrypt DRMed ebooks.
index 6e6aef742602a42f8beecacf9f716b6c24d8f468,0000000000000000000000000000000000000000..d88e1df7e6a0d9d9fe68497fe1719b7e848adf21
mode 100644,000000..100644
--- /dev/null
@@@ -1,446 -1,0 +1,444 @@@
- # Copyright © 2013-15 by Thom
- # Some portions Copyright © 2010-15 by some_updates, Apprentice Alf and Apprentice Harper
 +#!/usr/bin/env python
 +# -*- coding: utf-8 -*-
 +
 +from __future__ import with_statement
 +
 +# androidkindlekey.py
- #  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
++# Copyright © 2013-15 by Thom and Apprentice Harper
++# Some portions Copyright © 2010-15 by some_updates and Apprentice Alf
 +#
 +
 +# Revision history:
++#  1.0   - AmazonSecureStorage.xml decryption to serial number
++#  1.1   - map_data_storage.db decryption to serial number
++#  1.2   - BugFix
 +#  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())