From: Apprentice Harper Date: Thu, 26 Mar 2015 07:31:45 +0000 (+0000) Subject: Merge of bugfix 6.2.1 into master X-Git-Url: http://git.xmof.duckdns.org/?a=commitdiff_plain;h=d140b7e2dcab38802a86815a6ffd5e52ec9cd79c;p=DeDRM.git Merge of bugfix 6.2.1 into master --- d140b7e2dcab38802a86815a6ffd5e52ec9cd79c diff --cc DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist index 25ae8b2,acdebbb..1e1d4cc --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist @@@ -24,7 -24,7 +24,7 @@@ CFBundleExecutable droplet CFBundleGetInfoString - DeDRM AppleScript 6.2.0. Written 2010–2015 by Apprentice Alf et al. - DeDRM AppleScript 6.2.1 Written 2010–2015 by Apprentice Alf et al. ++ DeDRM AppleScript 6.3.0 Written 2010–2015 by Apprentice Alf et al. CFBundleIconFile DeDRM CFBundleIdentifier diff --cc DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Help.htm index 019ba36,6d703dd..86653eb --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Help.htm +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Help.htm @@@ -17,7 -17,7 +17,7 @@@ p {margin-top: 0 -

DeDRM Plugin (v6.1.0)

-

DeDRM Plugin (v6.2.1)

++

DeDRM Plugin (v6.3.0)

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.

diff --cc DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py index ef1e1b4,f0bf535..0ba33f4 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py @@@ -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. diff --cc DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/androidkindlekey.py index 6e6aef7,0000000..d88e1df mode 100644,000000..100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/androidkindlekey.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/androidkindlekey.py @@@ -1,446 -1,0 +1,444 @@@ +#!/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 ++# Copyright © 2013-15 by Thom and Apprentice Harper ++# Some portions Copyright © 2010-15 by some_updates and Apprentice Alf +# + +# 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.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() + # value + if line.startswith(' 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 ] []".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()) diff --cc DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw index d33fef9,b56dc8d..e27355f --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw @@@ -12,9 -12,9 +12,10 @@@ # 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 diff --cc DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Help.htm index 019ba36,6d703dd..86653eb --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Help.htm +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Help.htm @@@ -17,7 -17,7 +17,7 @@@ p {margin-top: 0 -

DeDRM Plugin (v6.1.0)

-

DeDRM Plugin (v6.2.1)

++

DeDRM Plugin (v6.3.0)

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.

diff --cc DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py index ef1e1b4,f0bf535..0ba33f4 --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py @@@ -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. diff --cc DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/androidkindlekey.py index 6e6aef7,0000000..d88e1df mode 100644,000000..100644 --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/androidkindlekey.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/androidkindlekey.py @@@ -1,446 -1,0 +1,444 @@@ +#!/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 ++# Copyright © 2013-15 by Thom and Apprentice Harper ++# Some portions Copyright © 2010-15 by some_updates and Apprentice Alf +# + +# 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.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() + # value + if line.startswith(' 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 ] []".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()) diff --cc DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Help.htm index 019ba36,6d703dd..86653eb --- a/DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Help.htm +++ b/DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Help.htm @@@ -17,7 -17,7 +17,7 @@@ p {margin-top: 0 -

DeDRM Plugin (v6.1.0)

-

DeDRM Plugin (v6.2.1)

++

DeDRM Plugin (v6.3.0)

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.

diff --cc DeDRM_calibre_plugin/DeDRM_plugin/__init__.py index ef1e1b4,f0bf535..0ba33f4 --- a/DeDRM_calibre_plugin/DeDRM_plugin/__init__.py +++ b/DeDRM_calibre_plugin/DeDRM_plugin/__init__.py @@@ -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. diff --cc DeDRM_calibre_plugin/DeDRM_plugin/androidkindlekey.py index 6e6aef7,0000000..d88e1df mode 100644,000000..100644 --- a/DeDRM_calibre_plugin/DeDRM_plugin/androidkindlekey.py +++ b/DeDRM_calibre_plugin/DeDRM_plugin/androidkindlekey.py @@@ -1,446 -1,0 +1,444 @@@ +#!/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 ++# Copyright © 2013-15 by Thom and Apprentice Harper ++# Some portions Copyright © 2010-15 by some_updates and Apprentice Alf +# + +# 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.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() + # value + if line.startswith(' 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 ] []".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())