Compare commits

..

8 Commits
v4.8 ... v5.3.1

Author SHA1 Message Date
Apprentice Alf
07ea87edf4 tools v5.3.1 2015-03-06 18:00:34 +00:00
Apprentice Alf
899fd419ae tools v5.3 2015-03-06 17:57:20 +00:00
Apprentice Alf
f3f02adc98 tools v5.2 2015-03-06 17:43:57 +00:00
Apprentice Alf
0812438b9d nib changed from folder to file, so must delete first 2015-03-06 17:41:42 +00:00
Apprentice Alf
26d9f7bd20 ReadMe names changed 2015-03-06 17:30:07 +00:00
Apprentice Alf
2c95633fcd tools v5.1
alfcrypto added to DeDRM plugin
2015-03-06 17:15:59 +00:00
Apprentice Alf
07e532f59c tools v5.0
Introduction of alfcrypto library for speed
Reorganisation of archive plugins,apps,other
2015-03-06 07:43:33 +00:00
Apprentice Alf
882edb6c69 Re-arrange folders and files for 5.0 tools 2015-03-06 07:32:13 +00:00
198 changed files with 12823 additions and 3644 deletions

3
.gitignore vendored
View File

@@ -2,9 +2,6 @@
__pycache__/ __pycache__/
*.pyc *.pyc
# C extensions
*.so
# Distribution / packaging # Distribution / packaging
.Python .Python
env/ env/

View File

@@ -0,0 +1,38 @@
Ignoble Epub DeDRM - ignobleepub_v01.6_plugin.zip
All credit given to I♥Cabbages for the original standalone scripts.
I had the much easier job of converting them to a calibre plugin.
This plugin is meant to decrypt Barnes & Noble Epubs that are protected with Adobe's Adept encryption. It is meant to function without having to install any dependencies... other than having calibre installed, of course. It will still work if you have Python and PyCrypto already installed, but they aren't necessary.
Installation:
Go to calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ignobleepub_vXX_plugin.zip) and click the 'Add' button. you're done.
Please note: calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added.
Configuration:
1) The easiest way to configure the plugin is to enter your name (Barnes & Noble account name) and credit card number (the one used to purchase the books) into the plugin's customization window. It's the same info you would enter into the ignoblekeygen script. Highlight the plugin (Ignoble Epub DeDRM) and click the "Customize Plugin" button on calibre's Preferences->Plugins page. Enter the name and credit card number separated by a comma: Your Name,1234123412341234
If you've purchased books with more than one credit card, separate that other info with a colon: Your Name,1234123412341234:Other Name,2345234523452345
** NOTE ** The above method is your only option if you don't have/can't run the original I♥Cabbages scripts on your particular machine. Your credit card number will be on display in calibre's Plugin configuration page when using the above method. If other people have access to your computer, you may want to use the second configuration method below.
2) If you already have keyfiles generated with I <3 Cabbages' ignoblekeygen.pyw script, you can put those keyfiles into calibre's configuration directory. The easiest way to find the correct directory is to go to calibre's Preferences page... click on the 'Miscellaneous' button (looks like a gear), and then click the 'Open calibre configuration directory' button. Paste your keyfiles in there. Just make sure that they have different names and are saved with the '.b64' extension (like the ignoblekeygen script produces). This directory isn't touched when upgrading calibre, so it's quite safe to leave them there.
All keyfiles from method 2 and all data entered from method 1 will be used to attempt to decrypt a book. You can use method 1 or method 2, or a combination of both.
Troubleshooting:
If you find that it's not working for you (imported epubs still have DRM), you can save a lot of time and trouble by trying to add the epub to calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.epub". Don't type the quotes and obviously change the 'your_ebook.epub' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.

View File

@@ -1,11 +1,11 @@
Inept Epub DeDRM - ineptepub_vXX_plugin.zip Inept Epub DeDRM - ineptepub_v01.7_plugin.zip
Requires Calibre version 0.6.44 or higher.
All credit given to I <3 Cabbages for the original standalone scripts. All credit given to ICabbages for the original standalone scripts.
I had the much easier job of converting them to a Calibre plugin. I had the much easier job of converting them to a Calibre plugin.
This plugin is meant to decrypt Adobe Digital Edition Epubs that are protected with Adobe's Adept encryption. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. It will still work if you have Python and PyCrypto already installed, but they aren't necessary. This plugin is meant to decrypt Adobe Digital Edition Epubs that are protected with Adobe's Adept encryption. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. It will still work if you have Python and PyCrypto already installed, but they aren't necessary.
Installation: Installation:
Go to Calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Cahnge calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptepub_vXX_plugin.zip) and click the 'Add' button. you're done. Go to Calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Cahnge calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptepub_vXX_plugin.zip) and click the 'Add' button. you're done.
@@ -15,25 +15,25 @@ Please note: Calibre does not provide any immediate feedback to indicate that a
Configuration: Configuration:
When first run, the plugin will attempt to find your Adobe Digital Editions installation (on Windows and Mac OS's). If successful, it will create an 'adeptkey.der' file and save it in Calibre's configuration directory. It will use that file on subsequent runs. If there are already '*.der' files in the directory, the plugin won't attempt to When first run, the plugin will attempt to find your Adobe Digital Editions installation (on Windows and Mac OS's). If successful, it will create an 'adeptkey.der' file and save it in Calibre's configuration directory. It will use that file on subsequent runs. If there are already '*.der' files in the directory, the plugin won't attempt to find the Adobe Digital Editions installation installation.
find the Adobe Digital Editions installation installation.
So if you have Adobe Digital Editions installation installed on the same machine as Calibre... you are ready to go. If not... keep reading. So if you have Adobe Digital Editions installation installed on the same machine as Calibre... you are ready to go. If not... keep reading.
If you already have keyfiles generated with I <3 Cabbages' ineptkey.pyw script, you can put those keyfiles in Calibre's configuration directory. The easiest way to find the correct directory is to go to Calibre's Preferences page... click on the 'Miscellaneous' button (looks like a gear), and then click the 'Open Calibre configuration directory' button. Paste your keyfiles in there. Just make sure that If you already have keyfiles generated with ICabbages' ineptkey.pyw script, you can put those keyfiles in Calibre's configuration directory. The easiest way to find the correct directory is to go to Calibre's Preferences page... click on the 'Miscellaneous' button (looks like a gear), and then click the 'Open Calibre configuration directory' button. Paste your keyfiles in there. Just make sure that they have different names and are saved with the '.der' extension (like the ineptkey script produces). This directory isn't touched when upgrading Calibre, so it's quite safe to leave them there.
they have different names and are saved with the '.der' extension (like the ineptkey script produces). This directory isn't touched when upgrading Calibre, so it's quite safe to leave them there.
Since there is no Linux version of Adobe Digital Editions, Linux users will have to obtain a keyfile through other methods and put the file in Calibre's configuration directory. Since there is no Linux version of Adobe Digital Editions, Linux users will have to obtain a keyfile through other methods and put the file in Calibre's configuration directory.
All keyfiles with a '.der' extension found in Calibre's configuration directory will be used to attempt to decrypt a book. All keyfiles with a '.der' extension found in Calibre's configuration directory will be used to attempt to decrypt a book.
** NOTE ** There is no plugin customization data for the Inept Epub DeDRM plugin. ** NOTE ** There is no plugin customization data for the Inept Epub DeDRM plugin.
Troubleshooting: Troubleshooting:
If you find that it's not working for you (imported epubs still have DRM), you can save a lot of time and trouble by trying to add the epub to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might If you find that it's not working for you (imported epubs still have DRM), you can save a lot of time and trouble by trying to add the epub to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)
as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.epub". Don't type the quotes and obviously change the 'your_ebook.epub' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make. Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.epub". Don't type the quotes and obviously change the 'your_ebook.epub' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools. ** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.

View File

@@ -1,11 +1,11 @@
Inept PDF Plugin - ineptpdf_vXX_plugin.zip Inept PDF Plugin - ineptpdf_v01.5_plugin.zip
Requires Calibre version 0.6.44 or higher.
All credit given to IHeartCabbages for the original standalone scripts. All credit given to ICabbages for the original standalone scripts.
I had the much easier job of converting them to a Calibre plugin. I had the much easier job of converting them to a Calibre plugin.
This plugin is meant to decrypt Adobe Digital Edition PDFs that are protected with Adobe's Adept encryption. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. It will still work if you have Python, PyCrypto and/or OpenSSL already installed, but they aren't necessary. This plugin is meant to decrypt Adobe Digital Edition PDFs that are protected with Adobe's Adept encryption. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. It will still work if you have Python, PyCrypto and/or OpenSSL already installed, but they aren't necessary.
Installation: Installation:
Go to Calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptpdf_vXX_plugin.zip) and click the 'Add' button. you're done. Go to Calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptpdf_vXX_plugin.zip) and click the 'Add' button. you're done.
@@ -15,8 +15,7 @@ Please note: Calibre does not provide any immediate feedback to indicate that a
Configuration: Configuration:
When first run, the plugin will attempt to find your Adobe Digital Editions installation (on Windows and Mac OS's). If successful, it will create an 'adeptkey.der' file and save it in Calibre's configuration directory. It will use that file on subsequent runs. If there are already '*.der' files in the directory, the plugin won't attempt to When first run, the plugin will attempt to find your Adobe Digital Editions installation (on Windows and Mac OS's). If successful, it will create an 'adeptkey.der' file and save it in Calibre's configuration directory. It will use that file on subsequent runs. If there are already '*.der' files in the directory, the plugin won't attempt to find the Adobe Digital Editions installation installation.
find the Adobe Digital Editions installation installation.
So if you have Adobe Digital Editions installation installed on the same machine as Calibre... you are ready to go. If not... keep reading. So if you have Adobe Digital Editions installation installed on the same machine as Calibre... you are ready to go. If not... keep reading.
@@ -29,11 +28,12 @@ All keyfiles with a '.der' extension found in Calibre's configuration directory
** NOTE ** There is no plugin customization data for the Inept PDF plugin. ** NOTE ** There is no plugin customization data for the Inept PDF plugin.
Troubleshooting: Troubleshooting:
If you find that it's not working for you (imported PDFs still have DRM), you can save a lot of time and trouble by trying to add the PDF to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might If you find that it's not working for you (imported PDFs still have DRM), you can save a lot of time and trouble by trying to add the PDF to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)
as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.pdf". Don't type the quotes and obviously change the 'your_ebook.pdf' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make. Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.pdf". Don't type the quotes and obviously change the 'your_ebook.pdf' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools. ** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.

View File

@@ -0,0 +1,37 @@
K4MobiDeDRM_v04.5_plugin.zip
Credit given to The Dark Reverser for the original standalone script. Credit also to the many people who have updated and expanded that script since then.
Plugin for K4PC, K4Mac, eInk Kindles and Mobipocket.
This plugin supersedes MobiDeDRM, K4DeDRM, and K4PCDeDRM and K4X plugins. If you install this plugin, those plugins can be safely removed.
This plugin is meant to remove the DRM from .prc, .mobi, .azw, .azw1, .azw3, .azw4 and .tpz ebooks. Calibre can then convert them to whatever format you desire. It is meant to function without having to install any dependencies except for Calibre being on your same machine and in the same account as your "Kindle for PC" or "Kindle for Mac" application if you are going to remove the DRM from those types of books.
Installation:
Go to Calibre's Preferences page. Do **NOT** select "Get Plugins to enhance calibre" as this is reserved for official calibre plugins", instead select "Change calibre behavior". Under "Advanced" click on the on the Plugins button. Click on the "Load plugin from file" button at the bottom of the screen. Use the file dialog button to select the plugin's zip file (K4MobiDeDRM_vXX_plugin.zip) and click the "Add" (or it may say "Open" button. Then click on the "Yes" button in the warning dialog that appears. A Confirmation dialog appears that says the plugin has been installed.
Configuration:
Highlight the plugin (K4MobiDeDRM under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. If you have an eInk Kindle enter the 16 digit serial number (these typically begin "B0..."). If you have more than one eInk Kindle, you can enter multiple serial numbers separated by commas (no spaces). If you have Mobipocket books, enter your 10 digit PID. If you have more than one PID, separate them with commax (no spaces).
This configuration step is not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books.
Linux Systems Only:
If you install Kindle for PC in Wine, the plugin should be able to decode files from that Kindle for PC installation under Wine. You might need to enter a Wine Prefix if it's not already set in your Environment variables.
Troubleshooting:
If you find that it's not working for you, you can save a lot of time and trouble by trying to add the DRMed ebook to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook_file". Don't type the quotes and obviously change the 'your_ebook_file' to whatever the filename of your book is (including any file name extension like .azw). Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.

View File

@@ -4,59 +4,103 @@ from __future__ import with_statement
from calibre.customize import FileTypePlugin from calibre.customize import FileTypePlugin
from calibre.gui2 import is_ok_to_use_qt from calibre.gui2 import is_ok_to_use_qt
from calibre.utils.config import config_dir
from calibre.constants import iswindows, isosx
# from calibre.ptempfile import PersistentTemporaryDirectory # from calibre.ptempfile import PersistentTemporaryDirectory
from calibre_plugins.k4mobidedrm import kgenpids
from calibre_plugins.k4mobidedrm import topazextract
from calibre_plugins.k4mobidedrm import mobidedrm
import sys import sys
import os import os
import re import re
from zipfile import ZipFile
class K4DeDRM(FileTypePlugin): class K4DeDRM(FileTypePlugin):
name = 'K4PC, K4Mac, Kindle Mobi and Topaz DeDRM' # Name of the plugin name = 'Kindle and Mobipocket DeDRM' # Name of the plugin
description = 'Removes DRM from Mobipocket, Kindle/Mobi, Kindle/Topaz and Kindle/Print Replica files. Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' description = 'Removes DRM from eInk Kindle, Kindle 4 Mac and Kindle 4 PC ebooks, and from Mobipocket ebooks. Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, mdlnx, ApprenticeAlf, etc.'
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin author = 'DiapDealer, SomeUpdates, mdlnx, Apprentice Alf' # The author of this plugin
version = (0, 3, 8) # The version number of this plugin version = (0, 4, 5) # The version number of this plugin
file_types = set(['prc','mobi','azw','azw1','azw4','tpz']) # The file types that this plugin will be applied to file_types = set(['prc','mobi','azw','azw1','azw3','azw4','tpz']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import on_import = True # Run this plugin during the import
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm priority = 520 # run this plugin before earlier versions
minimum_calibre_version = (0, 7, 55) minimum_calibre_version = (0, 7, 55)
def initialize(self):
"""
Dynamic modules can't be imported/loaded from a zipfile... so this routine
runs whenever the plugin gets initialized. This will extract the appropriate
library for the target OS and copy it to the 'alfcrypto' subdirectory of
calibre's configuration directory. That 'alfcrypto' directory is then
inserted into the syspath (as the very first entry) in the run function
so the CDLL stuff will work in the alfcrypto.py script.
"""
if iswindows:
names = ['alfcrypto.dll','alfcrypto64.dll']
elif isosx:
names = ['libalfcrypto.dylib']
else:
names = ['libalfcrypto32.so','libalfcrypto64.so','alfcrypto.py','alfcrypto.dll','alfcrypto64.dll','getk4pcpids.py','mobidedrm.py','kgenpids.py','k4pcutils.py','topazextract.py']
lib_dict = self.load_resources(names)
self.alfdir = os.path.join(config_dir, 'alfcrypto')
if not os.path.exists(self.alfdir):
os.mkdir(self.alfdir)
for entry, data in lib_dict.items():
file_path = os.path.join(self.alfdir, entry)
with open(file_path,'wb') as f:
f.write(data)
def run(self, path_to_ebook): def run(self, path_to_ebook):
# add the alfcrypto directory to sys.path so alfcrypto.py
# will be able to locate the custom lib(s) for CDLL import.
sys.path.insert(0, self.alfdir)
# Had to move these imports here so the custom libs can be
# extracted to the appropriate places beforehand these routines
# look for them.
from calibre_plugins.k4mobidedrm import kgenpids
from calibre_plugins.k4mobidedrm import topazextract
from calibre_plugins.k4mobidedrm import mobidedrm
plug_ver = '.'.join(str(self.version).strip('()').replace(' ', '').split(',')) plug_ver = '.'.join(str(self.version).strip('()').replace(' ', '').split(','))
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
pids = [] pids = []
serials = [] serials = []
kInfoFiles = [] kInfoFiles = []
self.config()
# Get supplied list of PIDs to try from plugin customization. # Get supplied list of PIDs to try from plugin customization.
customvalues = self.site_customization.split(',') pidstringlistt = self.pids_string.split(',')
for customvalue in customvalues: for pid in pidstringlistt:
customvalue = str(customvalue) pid = str(pid).strip()
customvalue = customvalue.strip() if len(pid) == 10 or len(pid) == 8:
if len(customvalue) == 10 or len(customvalue) == 8: pids.append(pid)
pids.append(customvalue) else:
else : if len(pid) > 0:
if len(customvalue) == 16 and customvalue[0] == 'B': print "'%s' is not a valid Mobipocket PID." % pid
serials.append(customvalue)
else: # For linux, get PIDs by calling the right routines under WINE
print "%s is not a valid Kindle serial number or PID." % str(customvalue) if sys.platform.startswith('linux'):
k4 = False
pids.extend(self.WINEgetPIDs(path_to_ebook))
# Get supplied list of Kindle serial numbers to try from plugin customization.
serialstringlistt = self.serials_string.split(',')
for serial in serialstringlistt:
serial = str(serial).strip()
if len(serial) == 16 and serial[0] == 'B':
serials.append(serial)
else:
if len(serial) > 0:
print "'%s' is not a valid Kindle serial number." % serial
# Load any kindle info files (*.info) included Calibre's config directory. # Load any kindle info files (*.info) included Calibre's config directory.
try: try:
# Find Calibre's configuration directory. print 'K4MobiDeDRM v%s: Calibre configuration directory = %s' % (plug_ver, config_dir)
confpath = os.path.split(os.path.split(self.plugin_path)[0])[0] files = os.listdir(config_dir)
print 'K4MobiDeDRM v%s: Calibre configuration directory = %s' % (plug_ver, confpath)
files = os.listdir(confpath)
filefilter = re.compile("\.info$|\.kinf$", re.IGNORECASE) filefilter = re.compile("\.info$|\.kinf$", re.IGNORECASE)
files = filter(filefilter.search, files) files = filter(filefilter.search, files)
if files: if files:
for filename in files: for filename in files:
fpath = os.path.join(confpath, filename) fpath = os.path.join(config_dir, filename)
kInfoFiles.append(fpath) kInfoFiles.append(fpath)
print 'K4MobiDeDRM v%s: Kindle info/kinf file %s found in config folder.' % (plug_ver, filename) print 'K4MobiDeDRM v%s: Kindle info/kinf file %s found in config folder.' % (plug_ver, filename)
except IOError: except IOError:
@@ -94,11 +138,11 @@ class K4DeDRM(FileTypePlugin):
except topazextract.TpzDRMError, e: except topazextract.TpzDRMError, e:
#if you reached here then no luck raise and exception #if you reached here then no luck raise and exception
if is_ok_to_use_qt(): if is_ok_to_use_qt():
from PyQt4.Qt import QMessageBox from PyQt4.Qt import QMessageBox
d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM v%s Plugin" % plug_ver, "Error: " + str(e) + "... %s\n" % path_to_ebook) d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM v%s Plugin" % plug_ver, "Error: " + str(e) + "... %s\n" % path_to_ebook)
d.show() d.show()
d.raise_() d.raise_()
d.exec_() d.exec_()
raise Exception("K4MobiDeDRM plugin v%s Error: %s" % (plug_ver, str(e))) raise Exception("K4MobiDeDRM plugin v%s Error: %s" % (plug_ver, str(e)))
print "Success!" print "Success!"
@@ -106,6 +150,9 @@ class K4DeDRM(FileTypePlugin):
if mb.getPrintReplica(): if mb.getPrintReplica():
of = self.temporary_file(bookname+'.azw4') of = self.temporary_file(bookname+'.azw4')
print 'K4MobiDeDRM v%s: Print Replica format detected.' % plug_ver print 'K4MobiDeDRM v%s: Print Replica format detected.' % plug_ver
elif mb.getMobiVersion() >= 8:
print 'K4MobiDeDRM v%s: Stand-alone KF8 format detected.' % plug_ver
of = self.temporary_file(bookname+'.azw3')
else: else:
of = self.temporary_file(bookname+'.mobi') of = self.temporary_file(bookname+'.mobi')
mb.getMobiFile(of.name) mb.getMobiFile(of.name)
@@ -115,5 +162,97 @@ class K4DeDRM(FileTypePlugin):
mb.cleanup() mb.cleanup()
return of.name return of.name
def customization_help(self, gui=False): def WINEgetPIDs(self, infile):
return 'Enter 10 character PIDs and/or Kindle serial numbers, use a comma (no spaces) to separate each PID or SerialNumber from the next.'
import subprocess
from subprocess import Popen, PIPE, STDOUT
import subasyncio
from subasyncio import Process
print " Getting PIDs from WINE"
outfile = os.path.join(self.alfdir + 'winepids.txt')
# Remove any previous winepids.txt file.
if os.path.exists(outfile):
os.remove(outfile)
cmdline = 'wine python.exe ' \
+ '"'+self.alfdir + '/getk4pcpids.py"' \
+ ' "' + infile + '"' \
+ ' "' + outfile + '"'
env = os.environ
print "My wine_prefix from tweaks is ", self.wine_prefix
if ("WINEPREFIX" in env):
print "Using WINEPREFIX from the environment: ", env["WINEPREFIX"]
elif (self.wine_prefix is not None):
env['WINEPREFIX'] = self.wine_prefix
print "Using WINEPREFIX from tweaks: ", self.wine_prefix
else:
print "No wine prefix used"
print cmdline
try:
cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
result = p2.wait("wait")
except Exception, e:
print "WINE subprocess error ", str(e)
return []
print "WINE subprocess returned ", result
WINEpids = []
if os.path.exists(outfile):
try:
customvalues = file(outfile, 'r').readline().split(',')
for customvalue in customvalues:
customvalue = str(customvalue)
customvalue = customvalue.strip()
if len(customvalue) == 10 or len(customvalue) == 8:
WINEpids.append(customvalue)
else:
print "'%s' is not a valid PID." % customvalue
except Exception, e:
print "Error parsing winepids.txt: ", str(e)
return []
else:
print "No PIDs generated by Wine Python subprocess."
return WINEpids
def is_customizable(self):
# return true to allow customization via the Plugin->Preferences.
return True
def config_widget(self):
# It is important to put this import statement here rather than at the
# top of the module as importing the config class will also cause the
# GUI libraries to be loaded, which we do not want when using calibre
# from the command line
from calibre_plugins.k4mobidedrm.config import ConfigWidget
return config.ConfigWidget()
def config(self):
from calibre_plugins.k4mobidedrm.config import prefs
self.pids_string = prefs['pids']
self.serials_string = prefs['serials']
self.wine_prefix = prefs['WINEPREFIX']
def save_settings(self, config_widget):
'''
Save the settings specified by the user with config_widget.
'''
config_widget.save_settings()
self.config()
def load_resources(self, names):
ans = {}
with ZipFile(self.plugin_path, 'r') as zf:
for candidate in zf.namelist():
if candidate in names:
ans[candidate] = zf.read(candidate)
return ans

View File

@@ -0,0 +1,568 @@
#! /usr/bin/env python
"""
Routines for doing AES CBC in one file
Modified by some_updates to extract
and combine only those parts needed for AES CBC
into one simple to add python file
Original Version
Copyright (c) 2002 by Paul A. Lambert
Under:
CryptoPy Artisitic License Version 1.0
See the wonderful pure python package cryptopy-1.2.5
and read its LICENSE.txt for complete license details.
"""
class CryptoError(Exception):
""" Base class for crypto exceptions """
def __init__(self,errorMessage='Error!'):
self.message = errorMessage
def __str__(self):
return self.message
class InitCryptoError(CryptoError):
""" Crypto errors during algorithm initialization """
class BadKeySizeError(InitCryptoError):
""" Bad key size error """
class EncryptError(CryptoError):
""" Error in encryption processing """
class DecryptError(CryptoError):
""" Error in decryption processing """
class DecryptNotBlockAlignedError(DecryptError):
""" Error in decryption processing """
def xorS(a,b):
""" XOR two strings """
assert len(a)==len(b)
x = []
for i in range(len(a)):
x.append( chr(ord(a[i])^ord(b[i])))
return ''.join(x)
def xor(a,b):
""" XOR two strings """
x = []
for i in range(min(len(a),len(b))):
x.append( chr(ord(a[i])^ord(b[i])))
return ''.join(x)
"""
Base 'BlockCipher' and Pad classes for cipher instances.
BlockCipher supports automatic padding and type conversion. The BlockCipher
class was written to make the actual algorithm code more readable and
not for performance.
"""
class BlockCipher:
""" Block ciphers """
def __init__(self):
self.reset()
def reset(self):
self.resetEncrypt()
self.resetDecrypt()
def resetEncrypt(self):
self.encryptBlockCount = 0
self.bytesToEncrypt = ''
def resetDecrypt(self):
self.decryptBlockCount = 0
self.bytesToDecrypt = ''
def encrypt(self, plainText, more = None):
""" Encrypt a string and return a binary string """
self.bytesToEncrypt += plainText # append plainText to any bytes from prior encrypt
numBlocks, numExtraBytes = divmod(len(self.bytesToEncrypt), self.blockSize)
cipherText = ''
for i in range(numBlocks):
bStart = i*self.blockSize
ctBlock = self.encryptBlock(self.bytesToEncrypt[bStart:bStart+self.blockSize])
self.encryptBlockCount += 1
cipherText += ctBlock
if numExtraBytes > 0: # save any bytes that are not block aligned
self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
else:
self.bytesToEncrypt = ''
if more == None: # no more data expected from caller
finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize)
if len(finalBytes) > 0:
ctBlock = self.encryptBlock(finalBytes)
self.encryptBlockCount += 1
cipherText += ctBlock
self.resetEncrypt()
return cipherText
def decrypt(self, cipherText, more = None):
""" Decrypt a string and return a string """
self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt
numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
if more == None: # no more calls to decrypt, should have all the data
if numExtraBytes != 0:
raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt'
# hold back some bytes in case last decrypt has zero len
if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
numBlocks -= 1
numExtraBytes = self.blockSize
plainText = ''
for i in range(numBlocks):
bStart = i*self.blockSize
ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize])
self.decryptBlockCount += 1
plainText += ptBlock
if numExtraBytes > 0: # save any bytes that are not block aligned
self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
else:
self.bytesToEncrypt = ''
if more == None: # last decrypt remove padding
plainText = self.padding.removePad(plainText, self.blockSize)
self.resetDecrypt()
return plainText
class Pad:
def __init__(self):
pass # eventually could put in calculation of min and max size extension
class padWithPadLen(Pad):
""" Pad a binary string with the length of the padding """
def addPad(self, extraBytes, blockSize):
""" Add padding to a binary string to make it an even multiple
of the block size """
blocks, numExtraBytes = divmod(len(extraBytes), blockSize)
padLength = blockSize - numExtraBytes
return extraBytes + padLength*chr(padLength)
def removePad(self, paddedBinaryString, blockSize):
""" Remove padding from a binary string """
if not(0<len(paddedBinaryString)):
raise DecryptNotBlockAlignedError, 'Expected More Data'
return paddedBinaryString[:-ord(paddedBinaryString[-1])]
class noPadding(Pad):
""" No padding. Use this to get ECB behavior from encrypt/decrypt """
def addPad(self, extraBytes, blockSize):
""" Add no padding """
return extraBytes
def removePad(self, paddedBinaryString, blockSize):
""" Remove no padding """
return paddedBinaryString
"""
Rijndael encryption algorithm
This byte oriented implementation is intended to closely
match FIPS specification for readability. It is not implemented
for performance.
"""
class Rijndael(BlockCipher):
""" Rijndael encryption algorithm """
def __init__(self, key = None, padding = padWithPadLen(), keySize=16, blockSize=16 ):
self.name = 'RIJNDAEL'
self.keySize = keySize
self.strength = keySize*8
self.blockSize = blockSize # blockSize is in bytes
self.padding = padding # change default to noPadding() to get normal ECB behavior
assert( keySize%4==0 and NrTable[4].has_key(keySize/4)),'key size must be 16,20,24,29 or 32 bytes'
assert( blockSize%4==0 and NrTable.has_key(blockSize/4)), 'block size must be 16,20,24,29 or 32 bytes'
self.Nb = self.blockSize/4 # Nb is number of columns of 32 bit words
self.Nk = keySize/4 # Nk is the key length in 32-bit words
self.Nr = NrTable[self.Nb][self.Nk] # The number of rounds (Nr) is a function of
# the block (Nb) and key (Nk) sizes.
if key != None:
self.setKey(key)
def setKey(self, key):
""" Set a key and generate the expanded key """
assert( len(key) == (self.Nk*4) ), 'Key length must be same as keySize parameter'
self.__expandedKey = keyExpansion(self, key)
self.reset() # BlockCipher.reset()
def encryptBlock(self, plainTextBlock):
""" Encrypt a block, plainTextBlock must be a array of bytes [Nb by 4] """
self.state = self._toBlock(plainTextBlock)
AddRoundKey(self, self.__expandedKey[0:self.Nb])
for round in range(1,self.Nr): #for round = 1 step 1 to Nr
SubBytes(self)
ShiftRows(self)
MixColumns(self)
AddRoundKey(self, self.__expandedKey[round*self.Nb:(round+1)*self.Nb])
SubBytes(self)
ShiftRows(self)
AddRoundKey(self, self.__expandedKey[self.Nr*self.Nb:(self.Nr+1)*self.Nb])
return self._toBString(self.state)
def decryptBlock(self, encryptedBlock):
""" decrypt a block (array of bytes) """
self.state = self._toBlock(encryptedBlock)
AddRoundKey(self, self.__expandedKey[self.Nr*self.Nb:(self.Nr+1)*self.Nb])
for round in range(self.Nr-1,0,-1):
InvShiftRows(self)
InvSubBytes(self)
AddRoundKey(self, self.__expandedKey[round*self.Nb:(round+1)*self.Nb])
InvMixColumns(self)
InvShiftRows(self)
InvSubBytes(self)
AddRoundKey(self, self.__expandedKey[0:self.Nb])
return self._toBString(self.state)
def _toBlock(self, bs):
""" Convert binary string to array of bytes, state[col][row]"""
assert ( len(bs) == 4*self.Nb ), 'Rijndarl blocks must be of size blockSize'
return [[ord(bs[4*i]),ord(bs[4*i+1]),ord(bs[4*i+2]),ord(bs[4*i+3])] for i in range(self.Nb)]
def _toBString(self, block):
""" Convert block (array of bytes) to binary string """
l = []
for col in block:
for rowElement in col:
l.append(chr(rowElement))
return ''.join(l)
#-------------------------------------
""" Number of rounds Nr = NrTable[Nb][Nk]
Nb Nk=4 Nk=5 Nk=6 Nk=7 Nk=8
------------------------------------- """
NrTable = {4: {4:10, 5:11, 6:12, 7:13, 8:14},
5: {4:11, 5:11, 6:12, 7:13, 8:14},
6: {4:12, 5:12, 6:12, 7:13, 8:14},
7: {4:13, 5:13, 6:13, 7:13, 8:14},
8: {4:14, 5:14, 6:14, 7:14, 8:14}}
#-------------------------------------
def keyExpansion(algInstance, keyString):
""" Expand a string of size keySize into a larger array """
Nk, Nb, Nr = algInstance.Nk, algInstance.Nb, algInstance.Nr # for readability
key = [ord(byte) for byte in keyString] # convert string to list
w = [[key[4*i],key[4*i+1],key[4*i+2],key[4*i+3]] for i in range(Nk)]
for i in range(Nk,Nb*(Nr+1)):
temp = w[i-1] # a four byte column
if (i%Nk) == 0 :
temp = temp[1:]+[temp[0]] # RotWord(temp)
temp = [ Sbox[byte] for byte in temp ]
temp[0] ^= Rcon[i/Nk]
elif Nk > 6 and i%Nk == 4 :
temp = [ Sbox[byte] for byte in temp ] # SubWord(temp)
w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] )
return w
Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!!
0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,
0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91)
#-------------------------------------
def AddRoundKey(algInstance, keyBlock):
""" XOR the algorithm state with a block of key material """
for column in range(algInstance.Nb):
for row in range(4):
algInstance.state[column][row] ^= keyBlock[column][row]
#-------------------------------------
def SubBytes(algInstance):
for column in range(algInstance.Nb):
for row in range(4):
algInstance.state[column][row] = Sbox[algInstance.state[column][row]]
def InvSubBytes(algInstance):
for column in range(algInstance.Nb):
for row in range(4):
algInstance.state[column][row] = InvSbox[algInstance.state[column][row]]
Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,
0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,
0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,
0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,
0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,
0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,
0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,
0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,
0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,
0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,
0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,
0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,
0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,
0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,
0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,
0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,
0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16)
InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,
0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,
0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,
0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,
0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,
0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,
0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,
0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,
0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,
0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,
0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,
0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,
0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,
0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,
0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,
0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,
0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d)
#-------------------------------------
""" For each block size (Nb), the ShiftRow operation shifts row i
by the amount Ci. Note that row 0 is not shifted.
Nb C1 C2 C3
------------------- """
shiftOffset = { 4 : ( 0, 1, 2, 3),
5 : ( 0, 1, 2, 3),
6 : ( 0, 1, 2, 3),
7 : ( 0, 1, 2, 4),
8 : ( 0, 1, 3, 4) }
def ShiftRows(algInstance):
tmp = [0]*algInstance.Nb # list of size Nb
for r in range(1,4): # row 0 reamains unchanged and can be skipped
for c in range(algInstance.Nb):
tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r]
for c in range(algInstance.Nb):
algInstance.state[c][r] = tmp[c]
def InvShiftRows(algInstance):
tmp = [0]*algInstance.Nb # list of size Nb
for r in range(1,4): # row 0 reamains unchanged and can be skipped
for c in range(algInstance.Nb):
tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r]
for c in range(algInstance.Nb):
algInstance.state[c][r] = tmp[c]
#-------------------------------------
def MixColumns(a):
Sprime = [0,0,0,0]
for j in range(a.Nb): # for each column
Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3])
Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3])
Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3])
Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3])
for i in range(4):
a.state[j][i] = Sprime[i]
def InvMixColumns(a):
""" Mix the four bytes of every column in a linear way
This is the opposite operation of Mixcolumn """
Sprime = [0,0,0,0]
for j in range(a.Nb): # for each column
Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3])
Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3])
Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3])
Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3])
for i in range(4):
a.state[j][i] = Sprime[i]
#-------------------------------------
def mul(a, b):
""" Multiply two elements of GF(2^m)
needed for MixColumn and InvMixColumn """
if (a !=0 and b!=0):
return Alogtable[(Logtable[a] + Logtable[b])%255]
else:
return 0
Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3,
100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193,
125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120,
101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142,
150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56,
102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16,
126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186,
43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87,
175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232,
44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160,
127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183,
204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157,
151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209,
83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171,
68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165,
103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7)
Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53,
95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170,
229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49,
83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205,
76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136,
131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154,
181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163,
254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160,
251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65,
195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117,
159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128,
155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84,
252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202,
69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14,
18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23,
57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1)
"""
AES Encryption Algorithm
The AES algorithm is just Rijndael algorithm restricted to the default
blockSize of 128 bits.
"""
class AES(Rijndael):
""" The AES algorithm is the Rijndael block cipher restricted to block
sizes of 128 bits and key sizes of 128, 192 or 256 bits
"""
def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
""" Initialize AES, keySize is in bytes """
if not (keySize == 16 or keySize == 24 or keySize == 32) :
raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes'
Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
self.name = 'AES'
"""
CBC mode of encryption for block ciphers.
This algorithm mode wraps any BlockCipher to make a
Cipher Block Chaining mode.
"""
from random import Random # should change to crypto.random!!!
class CBC(BlockCipher):
""" The CBC class wraps block ciphers to make cipher block chaining (CBC) mode
algorithms. The initialization (IV) is automatic if set to None. Padding
is also automatic based on the Pad class used to initialize the algorithm
"""
def __init__(self, blockCipherInstance, padding = padWithPadLen()):
""" CBC algorithms are created by initializing with a BlockCipher instance """
self.baseCipher = blockCipherInstance
self.name = self.baseCipher.name + '_CBC'
self.blockSize = self.baseCipher.blockSize
self.keySize = self.baseCipher.keySize
self.padding = padding
self.baseCipher.padding = noPadding() # baseCipher should NOT pad!!
self.r = Random() # for IV generation, currently uses
# mediocre standard distro version <----------------
import time
newSeed = time.ctime()+str(self.r) # seed with instance location
self.r.seed(newSeed) # to make unique
self.reset()
def setKey(self, key):
self.baseCipher.setKey(key)
# Overload to reset both CBC state and the wrapped baseCipher
def resetEncrypt(self):
BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class)
self.baseCipher.resetEncrypt() # reset base cipher encrypt state
def resetDecrypt(self):
BlockCipher.resetDecrypt(self) # reset CBC state (super class)
self.baseCipher.resetDecrypt() # reset base cipher decrypt state
def encrypt(self, plainText, iv=None, more=None):
""" CBC encryption - overloads baseCipher to allow optional explicit IV
when iv=None, iv is auto generated!
"""
if self.encryptBlockCount == 0:
self.iv = iv
else:
assert(iv==None), 'IV used only on first call to encrypt'
return BlockCipher.encrypt(self,plainText, more=more)
def decrypt(self, cipherText, iv=None, more=None):
""" CBC decryption - overloads baseCipher to allow optional explicit IV
when iv=None, iv is auto generated!
"""
if self.decryptBlockCount == 0:
self.iv = iv
else:
assert(iv==None), 'IV used only on first call to decrypt'
return BlockCipher.decrypt(self, cipherText, more=more)
def encryptBlock(self, plainTextBlock):
""" CBC block encryption, IV is set with 'encrypt' """
auto_IV = ''
if self.encryptBlockCount == 0:
if self.iv == None:
# generate IV and use
self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)])
self.prior_encr_CT_block = self.iv
auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic
else: # application provided IV
assert(len(self.iv) == self.blockSize ),'IV must be same length as block'
self.prior_encr_CT_block = self.iv
""" encrypt the prior CT XORed with the PT """
ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) )
self.prior_encr_CT_block = ct
return auto_IV+ct
def decryptBlock(self, encryptedBlock):
""" Decrypt a single block """
if self.decryptBlockCount == 0: # first call, process IV
if self.iv == None: # auto decrypt IV?
self.prior_CT_block = encryptedBlock
return ''
else:
assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption"
self.prior_CT_block = self.iv
dct = self.baseCipher.decryptBlock(encryptedBlock)
""" XOR the prior decrypted CT with the prior CT """
dct_XOR_priorCT = xor( self.prior_CT_block, dct )
self.prior_CT_block = encryptedBlock
return dct_XOR_priorCT
"""
AES_CBC Encryption Algorithm
"""
class AES_CBC(CBC):
""" AES encryption in CBC feedback mode """
def __init__(self, key=None, padding=padWithPadLen(), keySize=16):
CBC.__init__( self, AES(key, noPadding(), keySize), padding)
self.name = 'AES_CBC'

Binary file not shown.

View File

@@ -0,0 +1,290 @@
#! /usr/bin/env python
import sys, os
import hmac
from struct import pack
import hashlib
# interface to needed routines libalfcrypto
def _load_libalfcrypto():
import ctypes
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
Structure, c_ulong, create_string_buffer, addressof, string_at, cast, sizeof
pointer_size = ctypes.sizeof(ctypes.c_voidp)
name_of_lib = None
if sys.platform.startswith('darwin'):
name_of_lib = 'libalfcrypto.dylib'
elif sys.platform.startswith('win'):
if pointer_size == 4:
name_of_lib = 'alfcrypto.dll'
else:
name_of_lib = 'alfcrypto64.dll'
else:
if pointer_size == 4:
name_of_lib = 'libalfcrypto32.so'
else:
name_of_lib = 'libalfcrypto64.so'
libalfcrypto = sys.path[0] + os.sep + name_of_lib
if not os.path.isfile(libalfcrypto):
raise Exception('libalfcrypto not found')
libalfcrypto = CDLL(libalfcrypto)
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
def F(restype, name, argtypes):
func = getattr(libalfcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
# aes cbc decryption
#
# struct aes_key_st {
# unsigned long rd_key[4 *(AES_MAXNR + 1)];
# int rounds;
# };
#
# typedef struct aes_key_st AES_KEY;
#
# int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
#
#
# void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
# const unsigned long length, const AES_KEY *key,
# unsigned char *ivec, const int enc);
AES_MAXNR = 14
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, c_int])
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
# Pukall 1 Cipher
# unsigned char *PC1(const unsigned char *key, unsigned int klen, const unsigned char *src,
# unsigned char *dest, unsigned int len, int decryption);
PC1 = F(c_char_p, 'PC1', [c_char_p, c_ulong, c_char_p, c_char_p, c_ulong, c_ulong])
# Topaz Encryption
# typedef struct _TpzCtx {
# unsigned int v[2];
# } TpzCtx;
#
# void topazCryptoInit(TpzCtx *ctx, const unsigned char *key, int klen);
# void topazCryptoDecrypt(const TpzCtx *ctx, const unsigned char *in, unsigned char *out, int len);
class TPZ_CTX(Structure):
_fields_ = [('v', c_long * 2)]
TPZ_CTX_p = POINTER(TPZ_CTX)
topazCryptoInit = F(None, 'topazCryptoInit', [TPZ_CTX_p, c_char_p, c_ulong])
topazCryptoDecrypt = F(None, 'topazCryptoDecrypt', [TPZ_CTX_p, c_char_p, c_char_p, c_ulong])
class AES_CBC(object):
def __init__(self):
self._blocksize = 0
self._keyctx = None
self._iv = 0
def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise Exception('AES CBC improper key used')
return
keyctx = self._keyctx = AES_KEY()
self._iv = iv
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0:
raise Exception('Failed to initialize AES CBC key')
def decrypt(self, data):
out = create_string_buffer(len(data))
mutable_iv = create_string_buffer(self._iv, len(self._iv))
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, mutable_iv, 0)
if rv == 0:
raise Exception('AES CBC decryption failed')
return out.raw
class Pukall_Cipher(object):
def __init__(self):
self.key = None
def PC1(self, key, src, decryption=True):
self.key = key
out = create_string_buffer(len(src))
de = 0
if decryption:
de = 1
rv = PC1(key, len(key), src, out, len(src), de)
return out.raw
class Topaz_Cipher(object):
def __init__(self):
self._ctx = None
def ctx_init(self, key):
tpz_ctx = self._ctx = TPZ_CTX()
topazCryptoInit(tpz_ctx, key, len(key))
return tpz_ctx
def decrypt(self, data, ctx=None):
if ctx == None:
ctx = self._ctx
out = create_string_buffer(len(data))
topazCryptoDecrypt(ctx, data, out, len(data))
return out.raw
print "Using Library AlfCrypto DLL/DYLIB/SO"
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
def _load_python_alfcrypto():
import aescbc
class Pukall_Cipher(object):
def __init__(self):
self.key = None
def PC1(self, key, src, decryption=True):
sum1 = 0;
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
print "Bad key length!"
return None
wkey = []
for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
dst = ""
for i in xrange(len(src)):
temp1 = 0;
byteXorVal = 0;
for j in xrange(8):
temp1 ^= wkey[j]
sum2 = (sum2+j)*20021 + sum1
sum1 = (temp1*346)&0xFFFF
sum2 = (sum2+sum1)&0xFFFF
temp1 = (temp1*20021+1)&0xFFFF
byteXorVal ^= temp1 ^ sum2
curByte = ord(src[i])
if not decryption:
keyXorVal = curByte * 257;
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
if decryption:
keyXorVal = curByte * 257;
for j in xrange(8):
wkey[j] ^= keyXorVal;
dst+=chr(curByte)
return dst
class Topaz_Cipher(object):
def __init__(self):
self._ctx = None
def ctx_init(self, key):
ctx1 = 0x0CAFFE19E
for keyChar in key:
keyByte = ord(keyChar)
ctx2 = ctx1
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
self._ctx = [ctx1, ctx2]
return [ctx1,ctx2]
def decrypt(self, data, ctx=None):
if ctx == None:
ctx = self._ctx
ctx1 = ctx[0]
ctx2 = ctx[1]
plainText = ""
for dataChar in data:
dataByte = ord(dataChar)
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
ctx2 = ctx1
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
plainText += chr(m)
return plainText
class AES_CBC(object):
def __init__(self):
self._key = None
self._iv = None
self.aes = None
def set_decrypt_key(self, userkey, iv):
self._key = userkey
self._iv = iv
self.aes = aescbc.AES_CBC(userkey, aescbc.noPadding(), len(userkey))
def decrypt(self, data):
iv = self._iv
cleartext = self.aes.decrypt(iv + data)
return cleartext
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
def _load_crypto():
AES_CBC = Pukall_Cipher = Topaz_Cipher = None
cryptolist = (_load_libalfcrypto, _load_python_alfcrypto)
for loader in cryptolist:
try:
AES_CBC, Pukall_Cipher, Topaz_Cipher = loader()
break
except (ImportError, Exception):
pass
return AES_CBC, Pukall_Cipher, Topaz_Cipher
AES_CBC, Pukall_Cipher, Topaz_Cipher = _load_crypto()
class KeyIVGen(object):
# this only exists in openssl so we will use pure python implementation instead
# PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
# [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
def pbkdf2(self, passwd, salt, iter, keylen):
def xorstr( a, b ):
if len(a) != len(b):
raise Exception("xorstr(): lengths differ")
return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b)))
def prf( h, data ):
hm = h.copy()
hm.update( data )
return hm.digest()
def pbkdf2_F( h, salt, itercount, blocknum ):
U = prf( h, salt + pack('>i',blocknum ) )
T = U
for i in range(2, itercount+1):
U = prf( h, U )
T = xorstr( T, U )
return T
sha = hashlib.sha1
digest_size = sha().digest_size
# l - number of output blocks to produce
l = keylen / digest_size
if keylen % digest_size != 0:
l += 1
h = hmac.new( passwd, None, sha )
T = ""
for i in range(1, l+1):
T += pbkdf2_F( h, salt, iter, i )
return T[0: keylen]

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,899 @@
#! /usr/bin/python
"""
Comprehensive Mazama Book DRM with Topaz Cryptography V2.2
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdBHJ4CNc6DNFCw4MRCw4SWAK6
M8hYfnNEI0yQmn5Ti+W8biT7EatpauE/5jgQMPBmdNrDr1hbHyHBSP7xeC2qlRWC
B62UCxeu/fpfnvNHDN/wPWWH4jynZ2M6cdcnE5LQ+FfeKqZn7gnG2No1U9h7oOHx
y2/pHuYme7U1TsgSjwIDAQAB
-----END PUBLIC KEY-----
"""
from __future__ import with_statement
import csv
import sys
import os
import getopt
import zlib
from struct import pack
from struct import unpack
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
string_at, Structure, c_void_p, cast
import _winreg as winreg
import Tkinter
import Tkconstants
import tkMessageBox
import traceback
import hashlib
MAX_PATH = 255
kernel32 = windll.kernel32
advapi32 = windll.advapi32
crypt32 = windll.crypt32
global kindleDatabase
global bookFile
global bookPayloadOffset
global bookHeaderRecords
global bookMetadata
global bookKey
global command
#
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
#
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
#
# Exceptions for all the problems that might happen during the script
#
class CMBDTCError(Exception):
pass
class CMBDTCFatal(Exception):
pass
#
# Stolen stuff
#
class DataBlob(Structure):
_fields_ = [('cbData', c_uint),
('pbData', c_void_p)]
DataBlob_p = POINTER(DataBlob)
def GetSystemDirectory():
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
GetSystemDirectoryW.restype = c_uint
def GetSystemDirectory():
buffer = create_unicode_buffer(MAX_PATH + 1)
GetSystemDirectoryW(buffer, len(buffer))
return buffer.value
return GetSystemDirectory
GetSystemDirectory = GetSystemDirectory()
def GetVolumeSerialNumber():
GetVolumeInformationW = kernel32.GetVolumeInformationW
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
POINTER(c_uint), POINTER(c_uint),
POINTER(c_uint), c_wchar_p, c_uint]
GetVolumeInformationW.restype = c_uint
def GetVolumeSerialNumber(path):
vsn = c_uint(0)
GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
return vsn.value
return GetVolumeSerialNumber
GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetUserName():
GetUserNameW = advapi32.GetUserNameW
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
GetUserNameW.restype = c_uint
def GetUserName():
buffer = create_unicode_buffer(32)
size = c_uint(len(buffer))
while not GetUserNameW(buffer, byref(size)):
buffer = create_unicode_buffer(len(buffer) * 2)
size.value = len(buffer)
return buffer.value.encode('utf-16-le')[::2]
return GetUserName
GetUserName = GetUserName()
def CryptUnprotectData():
_CryptUnprotectData = crypt32.CryptUnprotectData
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
c_void_p, c_void_p, c_uint, DataBlob_p]
_CryptUnprotectData.restype = c_uint
def CryptUnprotectData(indata, entropy):
indatab = create_string_buffer(indata)
indata = DataBlob(len(indata), cast(indatab, c_void_p))
entropyb = create_string_buffer(entropy)
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
outdata = DataBlob()
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
None, None, 0, byref(outdata)):
raise CMBDTCFatal("Failed to Unprotect Data")
return string_at(outdata.pbData, outdata.cbData)
return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData()
#
# Returns the MD5 digest of "message"
#
def MD5(message):
ctx = hashlib.md5()
ctx.update(message)
return ctx.digest()
#
# Returns the MD5 digest of "message"
#
def SHA1(message):
ctx = hashlib.sha1()
ctx.update(message)
return ctx.digest()
#
# Open the book file at path
#
def openBook(path):
try:
return open(path,'rb')
except:
raise CMBDTCFatal("Could not open book file: " + path)
#
# Encode the bytes in data with the characters in map
#
def encode(data, map):
result = ""
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
R = value % len(map)
result += map[Q]
result += map[R]
return result
#
# Hash the bytes in data and then encode the digest with the characters in map
#
def encodeHash(data,map):
return encode(MD5(data),map)
#
# Decode the string in data with the characters in map. Returns the decoded bytes
#
def decode(data,map):
result = ""
for i in range (0,len(data),2):
high = map.find(data[i])
low = map.find(data[i+1])
value = (((high * 0x40) ^ 0x80) & 0xFF) + low
result += pack("B",value)
return result
#
# Locate and open the Kindle.info file (Hopefully in the way it is done in the Kindle application)
#
def openKindleInfo():
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
#
# Parse the Kindle.info file and return the records as a list of key-values
#
def parseKindleInfo():
DB = {}
infoReader = openKindleInfo()
infoReader.read(1)
data = infoReader.read()
items = data.split('{')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
#
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string. (Totally not optimal)
#
def findNameForHash(hash):
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
result = ""
for name in names:
if hash == encodeHash(name, charMap2):
result = name
break
return name
#
# Print all the records from the kindle.info file (option -i)
#
def printKindleInfo():
for record in kindleDatabase:
name = findNameForHash(record)
if name != "" :
print (name)
print ("--------------------------\n")
else :
print ("Unknown Record")
print getKindleInfoValueForHash(record)
print "\n"
#
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
#
def getKindleInfoValueForHash(hashedKey):
global kindleDatabase
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
return CryptUnprotectData(encryptedValue,"")
#
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
#
def getKindleInfoValueForKey(key):
return getKindleInfoValueForHash(encodeHash(key,charMap2))
#
# Get a 7 bit encoded number from the book file
#
def bookReadEncodedNumber():
flag = False
data = ord(bookFile.read(1))
if data == 0xFF:
flag = True
data = ord(bookFile.read(1))
if data >= 0x80:
datax = (data & 0x7F)
while data >= 0x80 :
data = ord(bookFile.read(1))
datax = (datax <<7) + (data & 0x7F)
data = datax
if flag:
data = -data
return data
#
# Encode a number in 7 bit format
#
def encodeNumber(number):
result = ""
negative = False
flag = 0
if number < 0 :
number = -number + 1
negative = True
while True:
byte = number & 0x7F
number = number >> 7
byte += flag
result += chr(byte)
flag = 0x80
if number == 0 :
if (byte == 0xFF and negative == False) :
result += chr(0x80)
break
if negative:
result += chr(0xFF)
return result[::-1]
#
# Get a length prefixed string from the file
#
def bookReadString():
stringLength = bookReadEncodedNumber()
return unpack(str(stringLength)+"s",bookFile.read(stringLength))[0]
#
# Returns a length prefixed string
#
def lengthPrefixString(data):
return encodeNumber(len(data))+data
#
# Read and return the data of one header record at the current book file position [[offset,compressedLength,decompressedLength],...]
#
def bookReadHeaderRecordData():
nbValues = bookReadEncodedNumber()
values = []
for i in range (0,nbValues):
values.append([bookReadEncodedNumber(),bookReadEncodedNumber(),bookReadEncodedNumber()])
return values
#
# Read and parse one header record at the current book file position and return the associated data [[offset,compressedLength,decompressedLength],...]
#
def parseTopazHeaderRecord():
if ord(bookFile.read(1)) != 0x63:
raise CMBDTCFatal("Parse Error : Invalid Header")
tag = bookReadString()
record = bookReadHeaderRecordData()
return [tag,record]
#
# Parse the header of a Topaz file, get all the header records and the offset for the payload
#
def parseTopazHeader():
global bookHeaderRecords
global bookPayloadOffset
magic = unpack("4s",bookFile.read(4))[0]
if magic != 'TPZ0':
raise CMBDTCFatal("Parse Error : Invalid Header, not a Topaz file")
nbRecords = bookReadEncodedNumber()
bookHeaderRecords = {}
for i in range (0,nbRecords):
result = parseTopazHeaderRecord()
bookHeaderRecords[result[0]] = result[1]
if ord(bookFile.read(1)) != 0x64 :
raise CMBDTCFatal("Parse Error : Invalid Header")
bookPayloadOffset = bookFile.tell()
#
# Get a record in the book payload, given its name and index. If necessary the record is decrypted. The record is not decompressed
#
def getBookPayloadRecord(name, index):
encrypted = False
try:
recordOffset = bookHeaderRecords[name][index][0]
except:
raise CMBDTCFatal("Parse Error : Invalid Record, record not found")
bookFile.seek(bookPayloadOffset + recordOffset)
tag = bookReadString()
if tag != name :
raise CMBDTCFatal("Parse Error : Invalid Record, record name doesn't match")
recordIndex = bookReadEncodedNumber()
if recordIndex < 0 :
encrypted = True
recordIndex = -recordIndex -1
if recordIndex != index :
raise CMBDTCFatal("Parse Error : Invalid Record, index doesn't match")
if bookHeaderRecords[name][index][2] != 0 :
record = bookFile.read(bookHeaderRecords[name][index][2])
else:
record = bookFile.read(bookHeaderRecords[name][index][1])
if encrypted:
ctx = topazCryptoInit(bookKey)
record = topazCryptoDecrypt(record,ctx)
return record
#
# Extract, decrypt and decompress a book record indicated by name and index and print it or save it in "filename"
#
def extractBookPayloadRecord(name, index, filename):
compressed = False
try:
compressed = bookHeaderRecords[name][index][2] != 0
record = getBookPayloadRecord(name,index)
except:
print("Could not find record")
if compressed:
try:
record = zlib.decompress(record)
except:
raise CMBDTCFatal("Could not decompress record")
if filename != "":
try:
file = open(filename,"wb")
file.write(record)
file.close()
except:
raise CMBDTCFatal("Could not write to destination file")
else:
print(record)
#
# return next record [key,value] from the book metadata from the current book position
#
def readMetadataRecord():
return [bookReadString(),bookReadString()]
#
# Parse the metadata record from the book payload and return a list of [key,values]
#
def parseMetadata():
global bookHeaderRecords
global bookPayloadAddress
global bookMetadata
bookMetadata = {}
bookFile.seek(bookPayloadOffset + bookHeaderRecords["metadata"][0][0])
tag = bookReadString()
if tag != "metadata" :
raise CMBDTCFatal("Parse Error : Record Names Don't Match")
flags = ord(bookFile.read(1))
nbRecords = ord(bookFile.read(1))
for i in range (0,nbRecords) :
record =readMetadataRecord()
bookMetadata[record[0]] = record[1]
#
# Returns two bit at offset from a bit field
#
def getTwoBitsFromBitField(bitField,offset):
byteNumber = offset // 4
bitPosition = 6 - 2*(offset % 4)
return ord(bitField[byteNumber]) >> bitPosition & 3
#
# Returns the six bits at offset from a bit field
#
def getSixBitsFromBitField(bitField,offset):
offset *= 3
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
return value
#
# 8 bits to six bits encoding from hash to generate PID string
#
def encodePID(hash):
global charMap3
PID = ""
for position in range (0,8):
PID += charMap3[getSixBitsFromBitField(hash,position)]
return PID
#
# Context initialisation for the Topaz Crypto
#
def topazCryptoInit(key):
ctx1 = 0x0CAFFE19E
for keyChar in key:
keyByte = ord(keyChar)
ctx2 = ctx1
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
return [ctx1,ctx2]
#
# decrypt data with the context prepared by topazCryptoInit()
#
def topazCryptoDecrypt(data, ctx):
ctx1 = ctx[0]
ctx2 = ctx[1]
plainText = ""
for dataChar in data:
dataByte = ord(dataChar)
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
ctx2 = ctx1
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
plainText += chr(m)
return plainText
#
# Decrypt a payload record with the PID
#
def decryptRecord(data,PID):
ctx = topazCryptoInit(PID)
return topazCryptoDecrypt(data, ctx)
#
# Try to decrypt a dkey record (contains the book PID)
#
def decryptDkeyRecord(data,PID):
record = decryptRecord(data,PID)
fields = unpack("3sB8sB8s3s",record)
if fields[0] != "PID" or fields[5] != "pid" :
raise CMBDTCError("Didn't find PID magic numbers in record")
elif fields[1] != 8 or fields[3] != 8 :
raise CMBDTCError("Record didn't contain correct length fields")
elif fields[2] != PID :
raise CMBDTCError("Record didn't contain PID")
return fields[4]
#
# Decrypt all the book's dkey records (contain the book PID)
#
def decryptDkeyRecords(data,PID):
nbKeyRecords = ord(data[0])
records = []
data = data[1:]
for i in range (0,nbKeyRecords):
length = ord(data[0])
try:
key = decryptDkeyRecord(data[1:length+1],PID)
records.append(key)
except CMBDTCError:
pass
data = data[1+length:]
return records
#
# Encryption table used to generate the device PID
#
def generatePidEncryptionTable() :
table = []
for counter1 in range (0,0x100):
value = counter1
for counter2 in range (0,8):
if (value & 1 == 0) :
value = value >> 1
else :
value = value >> 1
value = value ^ 0xEDB88320
table.append(value)
return table
#
# Seed value used to generate the device PID
#
def generatePidSeed(table,dsn) :
value = 0
for counter in range (0,4) :
index = (ord(dsn[counter]) ^ value) &0xFF
value = (value >> 8) ^ table[index]
return value
#
# Generate the device PID
#
def generateDevicePID(table,dsn,nbRoll):
seed = generatePidSeed(table,dsn)
pidAscii = ""
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
index = 0
for counter in range (0,nbRoll):
pid[index] = pid[index] ^ ord(dsn[counter])
index = (index+1) %8
for counter in range (0,8):
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
pidAscii += charMap4[index]
return pidAscii
#
# Create decrypted book payload
#
def createDecryptedPayload(payload):
# store data to be able to create the header later
headerData= []
currentOffset = 0
# Add social DRM to decrypted files
try:
data = getKindleInfoValueForKey("kindle.name.info")+":"+ getKindleInfoValueForKey("login")
if payload!= None:
payload.write(lengthPrefixString("sdrm"))
payload.write(encodeNumber(0))
payload.write(data)
else:
currentOffset += len(lengthPrefixString("sdrm"))
currentOffset += len(encodeNumber(0))
currentOffset += len(data)
except:
pass
for headerRecord in bookHeaderRecords:
name = headerRecord
newRecord = []
if name != "dkey" :
for index in range (0,len(bookHeaderRecords[name])) :
offset = currentOffset
if payload != None:
# write tag
payload.write(lengthPrefixString(name))
# write data
payload.write(encodeNumber(index))
payload.write(getBookPayloadRecord(name, index))
else :
currentOffset += len(lengthPrefixString(name))
currentOffset += len(encodeNumber(index))
currentOffset += len(getBookPayloadRecord(name, index))
newRecord.append([offset,bookHeaderRecords[name][index][1],bookHeaderRecords[name][index][2]])
headerData.append([name,newRecord])
return headerData
#
# Create decrypted book
#
def createDecryptedBook(outputFile):
outputFile = open(outputFile,"wb")
# Write the payload in a temporary file
headerData = createDecryptedPayload(None)
outputFile.write("TPZ0")
outputFile.write(encodeNumber(len(headerData)))
for header in headerData :
outputFile.write(chr(0x63))
outputFile.write(lengthPrefixString(header[0]))
outputFile.write(encodeNumber(len(header[1])))
for numbers in header[1] :
outputFile.write(encodeNumber(numbers[0]))
outputFile.write(encodeNumber(numbers[1]))
outputFile.write(encodeNumber(numbers[2]))
outputFile.write(chr(0x64))
createDecryptedPayload(outputFile)
outputFile.close()
#
# Set the command to execute by the programm according to cmdLine parameters
#
def setCommand(name) :
global command
if command != "" :
raise CMBDTCFatal("Invalid command line parameters")
else :
command = name
#
# Program usage
#
def usage():
print("\nUsage:")
print("\nCMBDTC.py [options] bookFileName\n")
print("-p Adds a PID to the list of PIDs that are tried to decrypt the book key (can be used several times)")
print("-d Saves a decrypted copy of the book")
print("-r Prints or writes to disk a record indicated in the form name:index (e.g \"img:0\")")
print("-o Output file name to write records and decrypted books")
print("-v Verbose (can be used several times)")
print("-i Prints kindle.info database")
#
# Main
#
def main(argv=sys.argv):
global kindleDatabase
global bookMetadata
global bookKey
global bookFile
global command
progname = os.path.basename(argv[0])
verbose = 0
recordName = ""
recordIndex = 0
outputFile = ""
PIDs = []
kindleDatabase = None
command = ""
try:
opts, args = getopt.getopt(sys.argv[1:], "vdir:o:p:")
except getopt.GetoptError, err:
# print help information and exit:
print str(err) # will print something like "option -a not recognized"
usage()
sys.exit(2)
if len(opts) == 0 and len(args) == 0 :
usage()
sys.exit(2)
for o, a in opts:
if o == "-v":
verbose+=1
if o == "-i":
setCommand("printInfo")
if o =="-o":
if a == None :
raise CMBDTCFatal("Invalid parameter for -o")
outputFile = a
if o =="-r":
setCommand("printRecord")
try:
recordName,recordIndex = a.split(':')
except:
raise CMBDTCFatal("Invalid parameter for -r")
if o =="-p":
PIDs.append(a)
if o =="-d":
setCommand("doit")
if command == "" :
raise CMBDTCFatal("No action supplied on command line")
#
# Read the encrypted database
#
try:
kindleDatabase = parseKindleInfo()
except Exception, message:
if verbose>0:
print(message)
if kindleDatabase != None :
if command == "printInfo" :
printKindleInfo()
#
# Compute the DSN
#
# Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(str(GetVolumeSerialNumber(GetSystemDirectory().split('\\')[0] + '\\')),charMap1)
# Get the current user name
encodedUsername = encodeHash(GetUserName(),charMap1)
# concat, hash and encode
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
if verbose >1:
print("DSN: " + DSN)
#
# Compute the device PID
#
table = generatePidEncryptionTable()
devicePID = generateDevicePID(table,DSN,4)
PIDs.append(devicePID)
if verbose > 0:
print("Device PID: " + devicePID)
#
# Open book and parse metadata
#
if len(args) == 1:
bookFile = openBook(args[0])
parseTopazHeader()
parseMetadata()
#
# Compute book PID
#
# Get the account token
if kindleDatabase != None:
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
if verbose >1:
print("Account Token: " + kindleAccountToken)
keysRecord = bookMetadata["keys"]
keysRecordRecord = bookMetadata[keysRecord]
pidHash = SHA1(DSN+kindleAccountToken+keysRecord+keysRecordRecord)
bookPID = encodePID(pidHash)
PIDs.append(bookPID)
if verbose > 0:
print ("Book PID: " + bookPID )
#
# Decrypt book key
#
dkey = getBookPayloadRecord('dkey', 0)
bookKeys = []
for PID in PIDs :
bookKeys+=decryptDkeyRecords(dkey,PID)
if len(bookKeys) == 0 :
if verbose > 0 :
print ("Book key could not be found. Maybe this book is not registered with this device.")
else :
bookKey = bookKeys[0]
if verbose > 0:
print("Book key: " + bookKey.encode('hex'))
if command == "printRecord" :
extractBookPayloadRecord(recordName,int(recordIndex),outputFile)
if outputFile != "" and verbose>0 :
print("Wrote record to file: "+outputFile)
elif command == "doit" :
if outputFile!="" :
createDecryptedBook(outputFile)
if verbose >0 :
print ("Decrypted book saved. Don't pirate!")
elif verbose > 0:
print("Output file name was not supplied.")
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,59 @@
from PyQt4.Qt import QWidget, QVBoxLayout, QLabel, QLineEdit
from calibre.utils.config import JSONConfig
# This is where all preferences for this plugin will be stored
# You should always prefix your config file name with plugins/,
# so as to ensure you dont accidentally clobber a calibre config file
prefs = JSONConfig('plugins/K4MobiDeDRM')
# Set defaults
prefs.defaults['pids'] = ""
prefs.defaults['serials'] = ""
prefs.defaults['WINEPREFIX'] = None
class ConfigWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
self.l = QVBoxLayout()
self.setLayout(self.l)
self.serialLabel = QLabel('Kindle Serial numbers (separate with commas, no spaces)')
self.l.addWidget(self.serialLabel)
self.serials = QLineEdit(self)
self.serials.setText(prefs['serials'])
self.l.addWidget(self.serials)
self.serialLabel.setBuddy(self.serials)
self.pidLabel = QLabel('Mobipocket PIDs (separate with commas, no spaces)')
self.l.addWidget(self.pidLabel)
self.pids = QLineEdit(self)
self.pids.setText(prefs['pids'])
self.l.addWidget(self.pids)
self.pidLabel.setBuddy(self.serials)
self.wpLabel = QLabel('For Linux only: WINEPREFIX (enter absolute path)')
self.l.addWidget(self.wpLabel)
self.wineprefix = QLineEdit(self)
wineprefix = prefs['WINEPREFIX']
if wineprefix is not None:
self.wineprefix.setText(wineprefix)
else:
self.wineprefix.setText('')
self.l.addWidget(self.wineprefix)
self.wpLabel.setBuddy(self.wineprefix)
def save_settings(self):
prefs['pids'] = str(self.pids.text())
prefs['serials'] = str(self.serials.text())
winepref=str(self.wineprefix.text())
if winepref.strip() != '':
prefs['WINEPREFIX'] = winepref
else:
prefs['WINEPREFIX'] = None

View File

@@ -34,11 +34,11 @@ def readEncodedNumber(file):
data = ord(c) data = ord(c)
if data == 0xFF: if data == 0xFF:
flag = True flag = True
c = file.read(1) c = file.read(1)
if (len(c) == 0): if (len(c) == 0):
return None return None
data = ord(c) data = ord(c)
if data >= 0x80: if data >= 0x80:
datax = (data & 0x7F) datax = (data & 0x7F)
@@ -51,7 +51,7 @@ def readEncodedNumber(file):
data = datax data = datax
if flag: if flag:
data = -data data = -data
return data return data
@@ -59,29 +59,29 @@ def readEncodedNumber(file):
# most significant byte first which has the high bit set # most significant byte first which has the high bit set
def encodeNumber(number): def encodeNumber(number):
result = "" result = ""
negative = False negative = False
flag = 0 flag = 0
if number < 0 : if number < 0 :
number = -number + 1 number = -number + 1
negative = True negative = True
while True: while True:
byte = number & 0x7F byte = number & 0x7F
number = number >> 7 number = number >> 7
byte += flag byte += flag
result += chr(byte) result += chr(byte)
flag = 0x80 flag = 0x80
if number == 0 : if number == 0 :
if (byte == 0xFF and negative == False) : if (byte == 0xFF and negative == False) :
result += chr(0x80) result += chr(0x80)
break break
if negative: if negative:
result += chr(0xFF) result += chr(0xFF)
return result[::-1] return result[::-1]
@@ -214,6 +214,7 @@ class PageParser(object):
'links.title' : (1, 'text', 0, 0), 'links.title' : (1, 'text', 0, 0),
'links.href' : (1, 'text', 0, 0), 'links.href' : (1, 'text', 0, 0),
'links.type' : (1, 'text', 0, 0), 'links.type' : (1, 'text', 0, 0),
'links.id' : (1, 'number', 0, 0),
'paraCont' : (0, 'number', 1, 1), 'paraCont' : (0, 'number', 1, 1),
'paraCont.rootID' : (1, 'number', 0, 0), 'paraCont.rootID' : (1, 'number', 0, 0),
@@ -239,6 +240,7 @@ class PageParser(object):
'group' : (1, 'snippets', 1, 0), 'group' : (1, 'snippets', 1, 0),
'group.type' : (1, 'scalar_text', 0, 0), 'group.type' : (1, 'scalar_text', 0, 0),
'group._tag' : (1, 'scalar_text', 0, 0), 'group._tag' : (1, 'scalar_text', 0, 0),
'group.orientation': (1, 'scalar_text', 0, 0),
'region' : (1, 'snippets', 1, 0), 'region' : (1, 'snippets', 1, 0),
'region.type' : (1, 'scalar_text', 0, 0), 'region.type' : (1, 'scalar_text', 0, 0),
@@ -246,6 +248,7 @@ class PageParser(object):
'region.y' : (1, 'scalar_number', 0, 0), 'region.y' : (1, 'scalar_number', 0, 0),
'region.h' : (1, 'scalar_number', 0, 0), 'region.h' : (1, 'scalar_number', 0, 0),
'region.w' : (1, 'scalar_number', 0, 0), 'region.w' : (1, 'scalar_number', 0, 0),
'region.orientation' : (1, 'scalar_text', 0, 0),
'empty_text_region' : (1, 'snippets', 1, 0), 'empty_text_region' : (1, 'snippets', 1, 0),
@@ -265,6 +268,8 @@ class PageParser(object):
'paragraph.gridSize' : (1, 'scalar_number', 0, 0), 'paragraph.gridSize' : (1, 'scalar_number', 0, 0),
'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0), 'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0),
'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0), 'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0),
'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0),
'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0),
'word_semantic' : (1, 'snippets', 1, 1), 'word_semantic' : (1, 'snippets', 1, 1),
@@ -284,6 +289,8 @@ class PageParser(object):
'_span.gridSize' : (1, 'scalar_number', 0, 0), '_span.gridSize' : (1, 'scalar_number', 0, 0),
'_span.gridBottomCenter' : (1, 'scalar_number', 0, 0), '_span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
'_span.gridTopCenter' : (1, 'scalar_number', 0, 0), '_span.gridTopCenter' : (1, 'scalar_number', 0, 0),
'_span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
'_span.gridEndCenter' : (1, 'scalar_number', 0, 0),
'span' : (1, 'snippets', 1, 0), 'span' : (1, 'snippets', 1, 0),
'span.firstWord' : (1, 'scalar_number', 0, 0), 'span.firstWord' : (1, 'scalar_number', 0, 0),
@@ -291,6 +298,8 @@ class PageParser(object):
'span.gridSize' : (1, 'scalar_number', 0, 0), 'span.gridSize' : (1, 'scalar_number', 0, 0),
'span.gridBottomCenter' : (1, 'scalar_number', 0, 0), 'span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
'span.gridTopCenter' : (1, 'scalar_number', 0, 0), 'span.gridTopCenter' : (1, 'scalar_number', 0, 0),
'span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
'extratokens' : (1, 'snippets', 1, 0), 'extratokens' : (1, 'snippets', 1, 0),
'extratokens.type' : (1, 'scalar_text', 0, 0), 'extratokens.type' : (1, 'scalar_text', 0, 0),
@@ -609,65 +618,70 @@ class PageParser(object):
nodename = fullpathname.pop() nodename = fullpathname.pop()
ilvl = len(fullpathname) ilvl = len(fullpathname)
indent = ' ' * (3 * ilvl) indent = ' ' * (3 * ilvl)
result = indent + '<' + nodename + '>' rlst = []
rlst.append(indent + '<' + nodename + '>')
if len(argList) > 0: if len(argList) > 0:
argres = '' alst = []
for j in argList: for j in argList:
if (argtype == 'text') or (argtype == 'scalar_text') : if (argtype == 'text') or (argtype == 'scalar_text') :
argres += j + '|' alst.append(j + '|')
else : else :
argres += str(j) + ',' alst.append(str(j) + ',')
argres = "".join(alst)
argres = argres[0:-1] argres = argres[0:-1]
if argtype == 'snippets' : if argtype == 'snippets' :
result += 'snippets:' + argres rlst.append('snippets:' + argres)
else : else :
result += argres rlst.append(argres)
if len(subtagList) > 0 : if len(subtagList) > 0 :
result += '\n' rlst.append('\n')
for j in subtagList: for j in subtagList:
if len(j) > 0 : if len(j) > 0 :
result += self.formatTag(j) rlst.append(self.formatTag(j))
result += indent + '</' + nodename + '>\n' rlst.append(indent + '</' + nodename + '>\n')
else: else:
result += '</' + nodename + '>\n' rlst.append('</' + nodename + '>\n')
return result return "".join(rlst)
# flatten tag # flatten tag
def flattenTag(self, node): def flattenTag(self, node):
name = node[0] name = node[0]
subtagList = node[1] subtagList = node[1]
argtype = node[2] argtype = node[2]
argList = node[3] argList = node[3]
result = name rlst = []
rlst.append(name)
if (len(argList) > 0): if (len(argList) > 0):
argres = '' alst = []
for j in argList: for j in argList:
if (argtype == 'text') or (argtype == 'scalar_text') : if (argtype == 'text') or (argtype == 'scalar_text') :
argres += j + '|' alst.append(j + '|')
else : else :
argres += str(j) + '|' alst.append(str(j) + '|')
argres = "".join(alst)
argres = argres[0:-1] argres = argres[0:-1]
if argtype == 'snippets' : if argtype == 'snippets' :
result += '.snippets=' + argres rlst.append('.snippets=' + argres)
else : else :
result += '=' + argres rlst.append('=' + argres)
result += '\n' rlst.append('\n')
for j in subtagList: for j in subtagList:
if len(j) > 0 : if len(j) > 0 :
result += self.flattenTag(j) rlst.append(self.flattenTag(j))
return result return "".join(rlst)
# reduce create xml output # reduce create xml output
def formatDoc(self, flat_xml): def formatDoc(self, flat_xml):
result = '' rlst = []
for j in self.doc : for j in self.doc :
if len(j) > 0: if len(j) > 0:
if flat_xml: if flat_xml:
result += self.flattenTag(j) rlst.append(self.flattenTag(j))
else: else:
result += self.formatTag(j) rlst.append(self.formatTag(j))
result = "".join(rlst)
if self.debug : print result if self.debug : print result
return result return result

View File

@@ -292,6 +292,11 @@ class DocParser(object):
if self.fixedimage : if self.fixedimage :
makeImage = makeImage or (pclass.find('cl-f-') >= 0) makeImage = makeImage or (pclass.find('cl-f-') >= 0)
# before creating an image make sure glyph info exists
gidList = self.getData('info.glyph.glyphID',0,-1)
makeImage = makeImage & (len(gidList) > 0)
if not makeImage : if not makeImage :
# standard all word paragraph # standard all word paragraph
for wordnum in xrange(first, last): for wordnum in xrange(first, last):
@@ -382,10 +387,10 @@ class DocParser(object):
ws_last = int(argres) ws_last = int(argres)
elif name.endswith('word.class'): elif name.endswith('word.class'):
(cname, space) = argres.split('-',1) (cname, space) = argres.split('-',1)
if space == '' : space = '0' if space == '' : space = '0'
if (cname == 'spaceafter') and (int(space) > 0) : if (cname == 'spaceafter') and (int(space) > 0) :
word_class = 'sa' word_class = 'sa'
elif name.endswith('word.img.src'): elif name.endswith('word.img.src'):
result.append(('img' + word_class, int(argres))) result.append(('img' + word_class, int(argres)))
@@ -590,8 +595,8 @@ class DocParser(object):
def process(self): def process(self):
htmlpage = ''
tocinfo = '' tocinfo = ''
hlst = []
# get the ocr text # get the ocr text
(pos, argres) = self.findinDoc('info.word.ocrText',0,-1) (pos, argres) = self.findinDoc('info.word.ocrText',0,-1)
@@ -648,8 +653,8 @@ class DocParser(object):
# set anchor for link target on this page # set anchor for link target on this page
if not anchorSet and not first_para_continued: if not anchorSet and not first_para_continued:
htmlpage += '<div style="visibility: hidden; height: 0; width: 0;" id="' hlst.append('<div style="visibility: hidden; height: 0; width: 0;" id="')
htmlpage += self.id + '" title="pagetype_' + pagetype + '"></div>\n' hlst.append(self.id + '" title="pagetype_' + pagetype + '"></div>\n')
anchorSet = True anchorSet = True
# handle groups of graphics with text captions # handle groups of graphics with text captions
@@ -658,12 +663,12 @@ class DocParser(object):
if grptype != None: if grptype != None:
if grptype == 'graphic': if grptype == 'graphic':
gcstr = ' class="' + grptype + '"' gcstr = ' class="' + grptype + '"'
htmlpage += '<div' + gcstr + '>' hlst.append('<div' + gcstr + '>')
inGroup = True inGroup = True
elif (etype == 'grpend'): elif (etype == 'grpend'):
if inGroup: if inGroup:
htmlpage += '</div>\n' hlst.append('</div>\n')
inGroup = False inGroup = False
else: else:
@@ -673,25 +678,25 @@ class DocParser(object):
(pos, simgsrc) = self.findinDoc('img.src',start,end) (pos, simgsrc) = self.findinDoc('img.src',start,end)
if simgsrc: if simgsrc:
if inGroup: if inGroup:
htmlpage += '<img src="img/img%04d.jpg" alt="" />' % int(simgsrc) hlst.append('<img src="img/img%04d.jpg" alt="" />' % int(simgsrc))
else: else:
htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc) hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
elif regtype == 'chapterheading' : elif regtype == 'chapterheading' :
(pclass, pdesc) = self.getParaDescription(start,end, regtype) (pclass, pdesc) = self.getParaDescription(start,end, regtype)
if not breakSet: if not breakSet:
htmlpage += '<div style="page-break-after: always;">&nbsp;</div>\n' hlst.append('<div style="page-break-after: always;">&nbsp;</div>\n')
breakSet = True breakSet = True
tag = 'h1' tag = 'h1'
if pclass and (len(pclass) >= 7): if pclass and (len(pclass) >= 7):
if pclass[3:7] == 'ch1-' : tag = 'h1' if pclass[3:7] == 'ch1-' : tag = 'h1'
if pclass[3:7] == 'ch2-' : tag = 'h2' if pclass[3:7] == 'ch2-' : tag = 'h2'
if pclass[3:7] == 'ch3-' : tag = 'h3' if pclass[3:7] == 'ch3-' : tag = 'h3'
htmlpage += '<' + tag + ' class="' + pclass + '">' hlst.append('<' + tag + ' class="' + pclass + '">')
else: else:
htmlpage += '<' + tag + '>' hlst.append('<' + tag + '>')
htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype) hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
htmlpage += '</' + tag + '>' hlst.append('</' + tag + '>')
elif (regtype == 'text') or (regtype == 'fixed') or (regtype == 'insert') or (regtype == 'listitem'): elif (regtype == 'text') or (regtype == 'fixed') or (regtype == 'insert') or (regtype == 'listitem'):
ptype = 'full' ptype = 'full'
@@ -705,11 +710,11 @@ class DocParser(object):
if pclass[3:6] == 'h1-' : tag = 'h4' if pclass[3:6] == 'h1-' : tag = 'h4'
if pclass[3:6] == 'h2-' : tag = 'h5' if pclass[3:6] == 'h2-' : tag = 'h5'
if pclass[3:6] == 'h3-' : tag = 'h6' if pclass[3:6] == 'h3-' : tag = 'h6'
htmlpage += '<' + tag + ' class="' + pclass + '">' hlst.append('<' + tag + ' class="' + pclass + '">')
htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype) hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
htmlpage += '</' + tag + '>' hlst.append('</' + tag + '>')
else : else :
htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype) hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
elif (regtype == 'tocentry') : elif (regtype == 'tocentry') :
ptype = 'full' ptype = 'full'
@@ -718,7 +723,7 @@ class DocParser(object):
first_para_continued = False first_para_continued = False
(pclass, pdesc) = self.getParaDescription(start,end, regtype) (pclass, pdesc) = self.getParaDescription(start,end, regtype)
tocinfo += self.buildTOCEntry(pdesc) tocinfo += self.buildTOCEntry(pdesc)
htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype) hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
elif (regtype == 'vertical') or (regtype == 'table') : elif (regtype == 'vertical') or (regtype == 'table') :
ptype = 'full' ptype = 'full'
@@ -728,13 +733,13 @@ class DocParser(object):
ptype = 'end' ptype = 'end'
first_para_continued = False first_para_continued = False
(pclass, pdesc) = self.getParaDescription(start, end, regtype) (pclass, pdesc) = self.getParaDescription(start, end, regtype)
htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype) hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
elif (regtype == 'synth_fcvr.center'): elif (regtype == 'synth_fcvr.center'):
(pos, simgsrc) = self.findinDoc('img.src',start,end) (pos, simgsrc) = self.findinDoc('img.src',start,end)
if simgsrc: if simgsrc:
htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc) hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
else : else :
print ' Making region type', regtype, print ' Making region type', regtype,
@@ -760,18 +765,19 @@ class DocParser(object):
if pclass[3:6] == 'h1-' : tag = 'h4' if pclass[3:6] == 'h1-' : tag = 'h4'
if pclass[3:6] == 'h2-' : tag = 'h5' if pclass[3:6] == 'h2-' : tag = 'h5'
if pclass[3:6] == 'h3-' : tag = 'h6' if pclass[3:6] == 'h3-' : tag = 'h6'
htmlpage += '<' + tag + ' class="' + pclass + '">' hlst.append('<' + tag + ' class="' + pclass + '">')
htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype) hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
htmlpage += '</' + tag + '>' hlst.append('</' + tag + '>')
else : else :
htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype) hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
else : else :
print ' a "graphic" region' print ' a "graphic" region'
(pos, simgsrc) = self.findinDoc('img.src',start,end) (pos, simgsrc) = self.findinDoc('img.src',start,end)
if simgsrc: if simgsrc:
htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc) hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
htmlpage = "".join(hlst)
if last_para_continued : if last_para_continued :
if htmlpage[-4:] == '</p>': if htmlpage[-4:] == '</p>':
htmlpage = htmlpage[0:-4] htmlpage = htmlpage[0:-4]

View File

@@ -181,70 +181,69 @@ class PParser(object):
def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi): def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi):
ml = '' mlst = []
pp = PParser(gdict, flat_xml, meta_array) pp = PParser(gdict, flat_xml, meta_array)
ml += '<?xml version="1.0" standalone="no"?>\n' mlst.append('<?xml version="1.0" standalone="no"?>\n')
if (raw): if (raw):
ml += '<!DOCTYPE svg PUBLIC "-//W3C/DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' mlst.append('<!DOCTYPE svg PUBLIC "-//W3C/DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
ml += '<svg width="%fin" height="%fin" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">\n' % (pp.pw / scaledpi, pp.ph / scaledpi, pp.pw -1, pp.ph -1) mlst.append('<svg width="%fin" height="%fin" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">\n' % (pp.pw / scaledpi, pp.ph / scaledpi, pp.pw -1, pp.ph -1))
ml += '<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors']) mlst.append('<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors']))
else: else:
ml += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' mlst.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n')
ml += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" ><head>\n' mlst.append('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" ><head>\n')
ml += '<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors']) mlst.append('<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors']))
ml += '<script><![CDATA[\n' mlst.append('<script><![CDATA[\n')
ml += 'function gd(){var p=window.location.href.replace(/^.*\?dpi=(\d+).*$/i,"$1");return p;}\n' mlst.append('function gd(){var p=window.location.href.replace(/^.*\?dpi=(\d+).*$/i,"$1");return p;}\n')
ml += 'var dpi=%d;\n' % scaledpi mlst.append('var dpi=%d;\n' % scaledpi)
if (previd) : if (previd) :
ml += 'var prevpage="page%04d.xhtml";\n' % (previd) mlst.append('var prevpage="page%04d.xhtml";\n' % (previd))
if (nextid) : if (nextid) :
ml += 'var nextpage="page%04d.xhtml";\n' % (nextid) mlst.append('var nextpage="page%04d.xhtml";\n' % (nextid))
ml += 'var pw=%d;var ph=%d;' % (pp.pw, pp.ph) mlst.append('var pw=%d;var ph=%d;' % (pp.pw, pp.ph))
ml += 'function zoomin(){dpi=dpi*(0.8);setsize();}\n' mlst.append('function zoomin(){dpi=dpi*(0.8);setsize();}\n')
ml += 'function zoomout(){dpi=dpi*1.25;setsize();}\n' mlst.append('function zoomout(){dpi=dpi*1.25;setsize();}\n')
ml += 'function setsize(){var svg=document.getElementById("svgimg");var prev=document.getElementById("prevsvg");var next=document.getElementById("nextsvg");var width=(pw/dpi)+"in";var height=(ph/dpi)+"in";svg.setAttribute("width",width);svg.setAttribute("height",height);prev.setAttribute("height",height);prev.setAttribute("width","50px");next.setAttribute("height",height);next.setAttribute("width","50px");}\n' mlst.append('function setsize(){var svg=document.getElementById("svgimg");var prev=document.getElementById("prevsvg");var next=document.getElementById("nextsvg");var width=(pw/dpi)+"in";var height=(ph/dpi)+"in";svg.setAttribute("width",width);svg.setAttribute("height",height);prev.setAttribute("height",height);prev.setAttribute("width","50px");next.setAttribute("height",height);next.setAttribute("width","50px");}\n')
ml += 'function ppage(){window.location.href=prevpage+"?dpi="+Math.round(dpi);}\n' mlst.append('function ppage(){window.location.href=prevpage+"?dpi="+Math.round(dpi);}\n')
ml += 'function npage(){window.location.href=nextpage+"?dpi="+Math.round(dpi);}\n' mlst.append('function npage(){window.location.href=nextpage+"?dpi="+Math.round(dpi);}\n')
ml += 'var gt=gd();if(gt>0){dpi=gt;}\n' mlst.append('var gt=gd();if(gt>0){dpi=gt;}\n')
ml += 'window.onload=setsize;\n' mlst.append('window.onload=setsize;\n')
ml += ']]></script>\n' mlst.append(']]></script>\n')
ml += '</head>\n' mlst.append('</head>\n')
ml += '<body onLoad="setsize();" style="background-color:#777;text-align:center;">\n' mlst.append('<body onLoad="setsize();" style="background-color:#777;text-align:center;">\n')
ml += '<div style="white-space:nowrap;">\n' mlst.append('<div style="white-space:nowrap;">\n')
if previd == None: if previd == None:
ml += '<a href="javascript:ppage();"><svg id="prevsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"></svg></a>\n' mlst.append('<a href="javascript:ppage();"><svg id="prevsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"></svg></a>\n')
else: else:
ml += '<a href="javascript:ppage();"><svg id="prevsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"><polygon points="5,150,95,5,95,295" fill="#AAAAAA" /></svg></a>\n' mlst.append('<a href="javascript:ppage();"><svg id="prevsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"><polygon points="5,150,95,5,95,295" fill="#AAAAAA" /></svg></a>\n')
ml += '<a href="javascript:npage();"><svg id="svgimg" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" style="background-color:#FFF;border:1px solid black;">' % (pp.pw, pp.ph) mlst.append('<a href="javascript:npage();"><svg id="svgimg" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" style="background-color:#FFF;border:1px solid black;">' % (pp.pw, pp.ph))
if (pp.gid != None): if (pp.gid != None):
ml += '<defs>\n' mlst.append('<defs>\n')
gdefs = pp.getGlyphs() gdefs = pp.getGlyphs()
for j in xrange(0,len(gdefs)): for j in xrange(0,len(gdefs)):
ml += gdefs[j] mlst.append(gdefs[j])
ml += '</defs>\n' mlst.append('</defs>\n')
img = pp.getImages() img = pp.getImages()
if (img != None): if (img != None):
for j in xrange(0,len(img)): for j in xrange(0,len(img)):
ml += img[j] mlst.append(img[j])
if (pp.gid != None): if (pp.gid != None):
for j in xrange(0,len(pp.gid)): for j in xrange(0,len(pp.gid)):
ml += '<use xlink:href="#gl%d" x="%d" y="%d" />\n' % (pp.gid[j], pp.gx[j], pp.gy[j]) mlst.append('<use xlink:href="#gl%d" x="%d" y="%d" />\n' % (pp.gid[j], pp.gx[j], pp.gy[j]))
if (img == None or len(img) == 0) and (pp.gid == None or len(pp.gid) == 0): if (img == None or len(img) == 0) and (pp.gid == None or len(pp.gid) == 0):
xpos = "%d" % (pp.pw // 3) xpos = "%d" % (pp.pw // 3)
ypos = "%d" % (pp.ph // 3) ypos = "%d" % (pp.ph // 3)
ml += '<text x="' + xpos + '" y="' + ypos + '" font-size="' + meta_array['fontSize'] + '" font-family="Helvetica" stroke="black">This page intentionally left blank.</text>\n' mlst.append('<text x="' + xpos + '" y="' + ypos + '" font-size="' + meta_array['fontSize'] + '" font-family="Helvetica" stroke="black">This page intentionally left blank.</text>\n')
if (raw) : if (raw) :
ml += '</svg>' mlst.append('</svg>')
else : else :
ml += '</svg></a>\n' mlst.append('</svg></a>\n')
if nextid == None: if nextid == None:
ml += '<a href="javascript:npage();"><svg id="nextsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"></svg></a>\n' mlst.append('<a href="javascript:npage();"><svg id="nextsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"></svg></a>\n')
else : else :
ml += '<a href="javascript:npage();"><svg id="nextsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"><polygon points="5,5,5,295,95,150" fill="#AAAAAA" /></svg></a>\n' mlst.append('<a href="javascript:npage();"><svg id="nextsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"><polygon points="5,5,5,295,95,150" fill="#AAAAAA" /></svg></a>\n')
ml += '</div>\n' mlst.append('</div>\n')
ml += '<div><a href="javascript:zoomin();">zoom in</a> - <a href="javascript:zoomout();">zoom out</a></div>\n' mlst.append('<div><a href="javascript:zoomin();">zoom in</a> - <a href="javascript:zoomout();">zoom out</a></div>\n')
ml += '</body>\n' mlst.append('</body>\n')
ml += '</html>\n' mlst.append('</html>\n')
return ml return "".join(mlst)

View File

@@ -39,6 +39,8 @@ else :
import flatxml2svg import flatxml2svg
import stylexml2css import stylexml2css
# global switch
buildXML = False
# Get a 7 bit encoded number from a file # Get a 7 bit encoded number from a file
def readEncodedNumber(file): def readEncodedNumber(file):
@@ -48,11 +50,11 @@ def readEncodedNumber(file):
return None return None
data = ord(c) data = ord(c)
if data == 0xFF: if data == 0xFF:
flag = True flag = True
c = file.read(1) c = file.read(1)
if (len(c) == 0): if (len(c) == 0):
return None return None
data = ord(c) data = ord(c)
if data >= 0x80: if data >= 0x80:
datax = (data & 0x7F) datax = (data & 0x7F)
while data >= 0x80 : while data >= 0x80 :
@@ -63,7 +65,7 @@ def readEncodedNumber(file):
datax = (datax <<7) + (data & 0x7F) datax = (datax <<7) + (data & 0x7F)
data = datax data = datax
if flag: if flag:
data = -data data = -data
return data return data
# Get a length prefixed string from the file # Get a length prefixed string from the file
@@ -298,9 +300,10 @@ def generateBook(bookDir, raw, fixedimage):
if not os.path.exists(svgDir) : if not os.path.exists(svgDir) :
os.makedirs(svgDir) os.makedirs(svgDir)
xmlDir = os.path.join(bookDir,'xml') if buildXML:
if not os.path.exists(xmlDir) : xmlDir = os.path.join(bookDir,'xml')
os.makedirs(xmlDir) if not os.path.exists(xmlDir) :
os.makedirs(xmlDir)
otherFile = os.path.join(bookDir,'other0000.dat') otherFile = os.path.join(bookDir,'other0000.dat')
if not os.path.exists(otherFile) : if not os.path.exists(otherFile) :
@@ -348,23 +351,38 @@ def generateBook(bookDir, raw, fixedimage):
authors = authors.replace('>','&gt;') authors = authors.replace('>','&gt;')
meta_array['Authors'] = authors meta_array['Authors'] = authors
xname = os.path.join(xmlDir, 'metadata.xml') if buildXML:
metastr = '' xname = os.path.join(xmlDir, 'metadata.xml')
for key in meta_array: mlst = []
metastr += '<meta name="' + key + '" content="' + meta_array[key] + '" />\n' for key in meta_array:
file(xname, 'wb').write(metastr) mlst.append('<meta name="' + key + '" content="' + meta_array[key] + '" />\n')
metastr = "".join(mlst)
mlst = None
file(xname, 'wb').write(metastr)
print 'Processing StyleSheet' print 'Processing StyleSheet'
# get some scaling info from metadata to use while processing styles # get some scaling info from metadata to use while processing styles
# and first page info
fontsize = '135' fontsize = '135'
if 'fontSize' in meta_array: if 'fontSize' in meta_array:
fontsize = meta_array['fontSize'] fontsize = meta_array['fontSize']
# also get the size of a normal text page # also get the size of a normal text page
# get the total number of pages unpacked as a safety check
filenames = os.listdir(pageDir)
numfiles = len(filenames)
spage = '1' spage = '1'
if 'firstTextPage' in meta_array: if 'firstTextPage' in meta_array:
spage = meta_array['firstTextPage'] spage = meta_array['firstTextPage']
pnum = int(spage) pnum = int(spage)
if pnum >= numfiles or pnum < 0:
# metadata is wrong so just select a page near the front
# 10% of the book to get a normal text page
pnum = int(0.10 * numfiles)
# print "first normal text page is", spage
# get page height and width from first text page for use in stylesheet scaling # get page height and width from first text page for use in stylesheet scaling
pname = 'page%04d.dat' % (pnum + 1) pname = 'page%04d.dat' % (pnum + 1)
@@ -404,8 +422,9 @@ def generateBook(bookDir, raw, fixedimage):
# now get the css info # now get the css info
cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw) cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw)
file(xname, 'wb').write(cssstr) file(xname, 'wb').write(cssstr)
xname = os.path.join(xmlDir, 'other0000.xml') if buildXML:
file(xname, 'wb').write(convert2xml.getXML(dict, otherFile)) xname = os.path.join(xmlDir, 'other0000.xml')
file(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
print 'Processing Glyphs' print 'Processing Glyphs'
gd = GlyphDict() gd = GlyphDict()
@@ -425,8 +444,9 @@ def generateBook(bookDir, raw, fixedimage):
fname = os.path.join(glyphsDir,filename) fname = os.path.join(glyphsDir,filename)
flat_xml = convert2xml.fromData(dict, fname) flat_xml = convert2xml.fromData(dict, fname)
xname = os.path.join(xmlDir, filename.replace('.dat','.xml')) if buildXML:
file(xname, 'wb').write(convert2xml.getXML(dict, fname)) xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
file(xname, 'wb').write(convert2xml.getXML(dict, fname))
gp = GParser(flat_xml) gp = GParser(flat_xml)
for i in xrange(0, gp.count): for i in xrange(0, gp.count):
@@ -441,25 +461,25 @@ def generateBook(bookDir, raw, fixedimage):
glyfile.close() glyfile.close()
print " " print " "
# build up tocentries while processing html
tocentries = ''
# start up the html # start up the html
# also build up tocentries while processing html
htmlFileName = "book.html" htmlFileName = "book.html"
htmlstr = '<?xml version="1.0" encoding="utf-8"?>\n' hlst = []
htmlstr += '<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.1 Strict//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11-strict.dtd">\n' hlst.append('<?xml version="1.0" encoding="utf-8"?>\n')
htmlstr += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' hlst.append('<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.1 Strict//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11-strict.dtd">\n')
htmlstr += '<head>\n' hlst.append('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n')
htmlstr += '<meta http-equiv="content-type" content="text/html; charset=utf-8"/>\n' hlst.append('<head>\n')
htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n' hlst.append('<meta http-equiv="content-type" content="text/html; charset=utf-8"/>\n')
htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' hlst.append('<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n')
htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' hlst.append('<meta name="Author" content="' + meta_array['Authors'] + '" />\n')
hlst.append('<meta name="Title" content="' + meta_array['Title'] + '" />\n')
if 'ASIN' in meta_array: if 'ASIN' in meta_array:
htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' hlst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
if 'GUID' in meta_array: if 'GUID' in meta_array:
htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' hlst.append('<meta name="GUID" content="' + meta_array['GUID'] + '" />\n')
htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n' hlst.append('<link href="style.css" rel="stylesheet" type="text/css" />\n')
htmlstr += '</head>\n<body>\n' hlst.append('</head>\n<body>\n')
print 'Processing Pages' print 'Processing Pages'
# Books are at 1440 DPI. This is rendering at twice that size for # Books are at 1440 DPI. This is rendering at twice that size for
@@ -471,6 +491,7 @@ def generateBook(bookDir, raw, fixedimage):
numfiles = len(filenames) numfiles = len(filenames)
xmllst = [] xmllst = []
elst = []
for filename in filenames: for filename in filenames:
# print ' ', filename # print ' ', filename
@@ -481,45 +502,51 @@ def generateBook(bookDir, raw, fixedimage):
# keep flat_xml for later svg processing # keep flat_xml for later svg processing
xmllst.append(flat_xml) xmllst.append(flat_xml)
xname = os.path.join(xmlDir, filename.replace('.dat','.xml')) if buildXML:
file(xname, 'wb').write(convert2xml.getXML(dict, fname)) xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
file(xname, 'wb').write(convert2xml.getXML(dict, fname))
# first get the html # first get the html
pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage) pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage)
tocentries += tocinfo elst.append(tocinfo)
htmlstr += pagehtml hlst.append(pagehtml)
# finish up the html string and output it # finish up the html string and output it
htmlstr += '</body>\n</html>\n' hlst.append('</body>\n</html>\n')
htmlstr = "".join(hlst)
hlst = None
file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr) file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr)
print " " print " "
print 'Extracting Table of Contents from Amazon OCR' print 'Extracting Table of Contents from Amazon OCR'
# first create a table of contents file for the svg images # first create a table of contents file for the svg images
tochtml = '<?xml version="1.0" encoding="utf-8"?>\n' tlst = []
tochtml += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' tlst.append('<?xml version="1.0" encoding="utf-8"?>\n')
tochtml += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >' tlst.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n')
tochtml += '<head>\n' tlst.append('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >')
tochtml += '<title>' + meta_array['Title'] + '</title>\n' tlst.append('<head>\n')
tochtml += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' tlst.append('<title>' + meta_array['Title'] + '</title>\n')
tochtml += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' tlst.append('<meta name="Author" content="' + meta_array['Authors'] + '" />\n')
tlst.append('<meta name="Title" content="' + meta_array['Title'] + '" />\n')
if 'ASIN' in meta_array: if 'ASIN' in meta_array:
tochtml += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' tlst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
if 'GUID' in meta_array: if 'GUID' in meta_array:
tochtml += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' tlst.append('<meta name="GUID" content="' + meta_array['GUID'] + '" />\n')
tochtml += '</head>\n' tlst.append('</head>\n')
tochtml += '<body>\n' tlst.append('<body>\n')
tochtml += '<h2>Table of Contents</h2>\n' tlst.append('<h2>Table of Contents</h2>\n')
start = pageidnums[0] start = pageidnums[0]
if (raw): if (raw):
startname = 'page%04d.svg' % start startname = 'page%04d.svg' % start
else: else:
startname = 'page%04d.xhtml' % start startname = 'page%04d.xhtml' % start
tochtml += '<h3><a href="' + startname + '">Start of Book</a></h3>\n' tlst.append('<h3><a href="' + startname + '">Start of Book</a></h3>\n')
# build up a table of contents for the svg xhtml output # build up a table of contents for the svg xhtml output
tocentries = "".join(elst)
elst = None
toclst = tocentries.split('\n') toclst = tocentries.split('\n')
toclst.pop() toclst.pop()
for entry in toclst: for entry in toclst:
@@ -530,30 +557,32 @@ def generateBook(bookDir, raw, fixedimage):
fname = 'page%04d.svg' % id fname = 'page%04d.svg' % id
else: else:
fname = 'page%04d.xhtml' % id fname = 'page%04d.xhtml' % id
tochtml += '<h3><a href="'+ fname + '">' + title + '</a></h3>\n' tlst.append('<h3><a href="'+ fname + '">' + title + '</a></h3>\n')
tochtml += '</body>\n' tlst.append('</body>\n')
tochtml += '</html>\n' tlst.append('</html>\n')
tochtml = "".join(tlst)
file(os.path.join(svgDir, 'toc.xhtml'), 'wb').write(tochtml) file(os.path.join(svgDir, 'toc.xhtml'), 'wb').write(tochtml)
# now create index_svg.xhtml that points to all required files # now create index_svg.xhtml that points to all required files
svgindex = '<?xml version="1.0" encoding="utf-8"?>\n' slst = []
svgindex += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' slst.append('<?xml version="1.0" encoding="utf-8"?>\n')
svgindex += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >' slst.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n')
svgindex += '<head>\n' slst.append('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >')
svgindex += '<title>' + meta_array['Title'] + '</title>\n' slst.append('<head>\n')
svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' slst.append('<title>' + meta_array['Title'] + '</title>\n')
svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' slst.append('<meta name="Author" content="' + meta_array['Authors'] + '" />\n')
slst.append('<meta name="Title" content="' + meta_array['Title'] + '" />\n')
if 'ASIN' in meta_array: if 'ASIN' in meta_array:
svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' slst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
if 'GUID' in meta_array: if 'GUID' in meta_array:
svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' slst.append('<meta name="GUID" content="' + meta_array['GUID'] + '" />\n')
svgindex += '</head>\n' slst.append('</head>\n')
svgindex += '<body>\n' slst.append('<body>\n')
print "Building svg images of each book page" print "Building svg images of each book page"
svgindex += '<h2>List of Pages</h2>\n' slst.append('<h2>List of Pages</h2>\n')
svgindex += '<div>\n' slst.append('<div>\n')
idlst = sorted(pageIDMap.keys()) idlst = sorted(pageIDMap.keys())
numids = len(idlst) numids = len(idlst)
cnt = len(idlst) cnt = len(idlst)
@@ -566,49 +595,54 @@ def generateBook(bookDir, raw, fixedimage):
nextid = None nextid = None
print '.', print '.',
pagelst = pageIDMap[pageid] pagelst = pageIDMap[pageid]
flat_svg = '' flst = []
for page in pagelst: for page in pagelst:
flat_svg += xmllst[page] flst.append(xmllst[page])
flat_svg = "".join(flst)
flst=None
svgxml = flatxml2svg.convert2SVG(gd, flat_svg, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi) svgxml = flatxml2svg.convert2SVG(gd, flat_svg, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi)
if (raw) : if (raw) :
pfile = open(os.path.join(svgDir,'page%04d.svg' % pageid),'w') pfile = open(os.path.join(svgDir,'page%04d.svg' % pageid),'w')
svgindex += '<a href="svg/page%04d.svg">Page %d</a>\n' % (pageid, pageid) slst.append('<a href="svg/page%04d.svg">Page %d</a>\n' % (pageid, pageid))
else : else :
pfile = open(os.path.join(svgDir,'page%04d.xhtml' % pageid), 'w') pfile = open(os.path.join(svgDir,'page%04d.xhtml' % pageid), 'w')
svgindex += '<a href="svg/page%04d.xhtml">Page %d</a>\n' % (pageid, pageid) slst.append('<a href="svg/page%04d.xhtml">Page %d</a>\n' % (pageid, pageid))
previd = pageid previd = pageid
pfile.write(svgxml) pfile.write(svgxml)
pfile.close() pfile.close()
counter += 1 counter += 1
svgindex += '</div>\n' slst.append('</div>\n')
svgindex += '<h2><a href="svg/toc.xhtml">Table of Contents</a></h2>\n' slst.append('<h2><a href="svg/toc.xhtml">Table of Contents</a></h2>\n')
svgindex += '</body>\n</html>\n' slst.append('</body>\n</html>\n')
svgindex = "".join(slst)
slst = None
file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex) file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex)
print " " print " "
# build the opf file # build the opf file
opfname = os.path.join(bookDir, 'book.opf') opfname = os.path.join(bookDir, 'book.opf')
opfstr = '<?xml version="1.0" encoding="utf-8"?>\n' olst = []
opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n' olst.append('<?xml version="1.0" encoding="utf-8"?>\n')
olst.append('<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n')
# adding metadata # adding metadata
opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n' olst.append(' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n')
if 'GUID' in meta_array: if 'GUID' in meta_array:
opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n' olst.append(' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n')
if 'ASIN' in meta_array: if 'ASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n' olst.append(' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n')
if 'oASIN' in meta_array: if 'oASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n' olst.append(' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n')
opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n' olst.append(' <dc:title>' + meta_array['Title'] + '</dc:title>\n')
opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n' olst.append(' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n')
opfstr += ' <dc:language>en</dc:language>\n' olst.append(' <dc:language>en</dc:language>\n')
opfstr += ' <dc:date>' + meta_array['UpdateTime'] + '</dc:date>\n' olst.append(' <dc:date>' + meta_array['UpdateTime'] + '</dc:date>\n')
if isCover: if isCover:
opfstr += ' <meta name="cover" content="bookcover"/>\n' olst.append(' <meta name="cover" content="bookcover"/>\n')
opfstr += ' </metadata>\n' olst.append(' </metadata>\n')
opfstr += '<manifest>\n' olst.append('<manifest>\n')
opfstr += ' <item id="book" href="book.html" media-type="application/xhtml+xml"/>\n' olst.append(' <item id="book" href="book.html" media-type="application/xhtml+xml"/>\n')
opfstr += ' <item id="stylesheet" href="style.css" media-type="text/css"/>\n' olst.append(' <item id="stylesheet" href="style.css" media-type="text/css"/>\n')
# adding image files to manifest # adding image files to manifest
filenames = os.listdir(imgDir) filenames = os.listdir(imgDir)
filenames = sorted(filenames) filenames = sorted(filenames)
@@ -618,17 +652,19 @@ def generateBook(bookDir, raw, fixedimage):
imgext = 'jpeg' imgext = 'jpeg'
if imgext == '.svg': if imgext == '.svg':
imgext = 'svg+xml' imgext = 'svg+xml'
opfstr += ' <item id="' + imgname + '" href="img/' + filename + '" media-type="image/' + imgext + '"/>\n' olst.append(' <item id="' + imgname + '" href="img/' + filename + '" media-type="image/' + imgext + '"/>\n')
if isCover: if isCover:
opfstr += ' <item id="bookcover" href="cover.jpg" media-type="image/jpeg" />\n' olst.append(' <item id="bookcover" href="cover.jpg" media-type="image/jpeg" />\n')
opfstr += '</manifest>\n' olst.append('</manifest>\n')
# adding spine # adding spine
opfstr += '<spine>\n <itemref idref="book" />\n</spine>\n' olst.append('<spine>\n <itemref idref="book" />\n</spine>\n')
if isCover: if isCover:
opfstr += ' <guide>\n' olst.append(' <guide>\n')
opfstr += ' <reference href="cover.jpg" type="cover" title="Cover"/>\n' olst.append(' <reference href="cover.jpg" type="cover" title="Cover"/>\n')
opfstr += ' </guide>\n' olst.append(' </guide>\n')
opfstr += '</package>\n' olst.append('</package>\n')
opfstr = "".join(olst)
olst = None
file(opfname, 'wb').write(opfstr) file(opfname, 'wb').write(opfstr)
print 'Processing Complete' print 'Processing Complete'
@@ -649,7 +685,6 @@ def usage():
def main(argv): def main(argv):
bookDir = '' bookDir = ''
if len(argv) == 0: if len(argv) == 0:
argv = sys.argv argv = sys.argv

View File

@@ -0,0 +1,77 @@
#!/usr/bin/python
#
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
# Changelog
# 1.00 - Initial version
__version__ = '1.00'
import sys
class Unbuffered:
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
sys.stdout=Unbuffered(sys.stdout)
import os
import struct
import binascii
import kgenpids
import topazextract
import mobidedrm
from alfcrypto import Pukall_Cipher
class DrmException(Exception):
pass
def getK4PCpids(path_to_ebook):
# Return Kindle4PC PIDs. Assumes that the caller checked that we are not on Linux, which will raise an exception
mobi = True
magic3 = file(path_to_ebook,'rb').read(3)
if magic3 == 'TPZ':
mobi = False
if mobi:
mb = mobidedrm.MobiBook(path_to_ebook,False)
else:
mb = topazextract.TopazBook(path_to_ebook)
md1, md2 = mb.getPIDMetaInfo()
return kgenpids.getPidList(md1, md2, True, [], [], [])
def main(argv=sys.argv):
print ('getk4pcpids.py v%(__version__)s. '
'Copyright 2012 Apprentic Alf' % globals())
if len(argv)<2 or len(argv)>3:
print "Gets the possible book-specific PIDs from K4PC for a particular book"
print "Usage:"
print " %s <bookfile> [<outfile>]" % sys.argv[0]
return 1
else:
infile = argv[1]
try:
pidlist = getK4PCpids(infile)
except DrmException, e:
print "Error: %s" % e
return 1
pidstring = ','.join(pidlist)
print "Possible PIDs are: ", pidstring
if len(argv) is 3:
outfile = argv[2]
file(outfile, 'w').write(pidstring)
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -17,7 +17,7 @@ from __future__ import with_statement
# and many many others # and many many others
__version__ = '3.9' __version__ = '4.3'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -34,6 +34,8 @@ import string
import re import re
import traceback import traceback
buildXML = False
class DrmException(Exception): class DrmException(Exception):
pass pass
@@ -56,7 +58,7 @@ else:
# borrowed from calibre from calibre/src/calibre/__init__.py # borrowed from calibre from calibre/src/calibre/__init__.py
# added in removal of non-printing chars # added in removal of non-printing chars
# and removal of . at start # and removal of . at start
# convert spaces to underscores # convert underscores to spaces (we're OK with spaces in file names)
def cleanup_name(name): def cleanup_name(name):
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]') _filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
substitute='_' substitute='_'
@@ -71,10 +73,12 @@ def cleanup_name(name):
# Mac and Unix don't like file names that begin with a full stop # Mac and Unix don't like file names that begin with a full stop
if len(one) > 0 and one[0] == '.': if len(one) > 0 and one[0] == '.':
one = substitute+one[1:] one = substitute+one[1:]
one = one.replace(' ','_') one = one.replace('_',' ')
return one return one
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
global buildXML
# handle the obvious cases at the beginning # handle the obvious cases at the beginning
if not os.path.isfile(infile): if not os.path.isfile(infile):
print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: Input file does not exist" print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: Input file does not exist"
@@ -95,11 +99,20 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
title = mb.getBookTitle() title = mb.getBookTitle()
print "Processing Book: ", title print "Processing Book: ", title
filenametitle = cleanup_name(title) filenametitle = cleanup_name(title)
outfilename = bookname outfilename = cleanup_name(bookname)
if len(outfilename)<=8 or len(filenametitle)<=8:
outfilename = outfilename + "_" + filenametitle # generate 'sensible' filename, that will sort with the original name,
elif outfilename[:8] != filenametitle[:8]: # but is close to the name from the file.
outfilename = outfilename[:8] + "_" + filenametitle outlength = len(outfilename)
comparelength = min(8,min(outlength,len(filenametitle)))
copylength = min(max(outfilename.find(' '),8),len(outfilename))
if outlength==0:
outfilename = filenametitle
elif comparelength > 0:
if outfilename[:comparelength] == filenametitle[:comparelength]:
outfilename = filenametitle
else:
outfilename = outfilename[:copylength] + " " + filenametitle
# avoid excessively long file names # avoid excessively long file names
if len(outfilename)>150: if len(outfilename)>150:
@@ -125,6 +138,8 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
if mobi: if mobi:
if mb.getPrintReplica(): if mb.getPrintReplica():
outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw4') outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw4')
elif mb.getMobiVersion() >= 8:
outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw3')
else: else:
outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi') outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi')
mb.getMobiFile(outfile) mb.getMobiFile(outfile)
@@ -139,9 +154,10 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip') zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip')
mb.getSVGZip(zipname) mb.getSVGZip(zipname)
print " Creating XML ZIP Archive" if buildXML:
zipname = os.path.join(outdir, outfilename + '_XML' + '.zip') print " Creating XML ZIP Archive"
mb.getXMLZip(zipname) zipname = os.path.join(outdir, outfilename + '_XML' + '.zip')
mb.getXMLZip(zipname)
# remove internal temporary directory of Topaz pieces # remove internal temporary directory of Topaz pieces
mb.cleanup() mb.cleanup()
@@ -166,7 +182,7 @@ def main(argv=sys.argv):
pids = [] pids = []
print ('K4MobiDeDrm v%(__version__)s ' print ('K4MobiDeDrm v%(__version__)s '
'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals()) 'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
try: try:
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:") opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
@@ -195,8 +211,8 @@ def main(argv=sys.argv):
# try with built in Kindle Info files # try with built in Kindle Info files
k4 = True k4 = True
if sys.platform.startswith('linux'): if sys.platform.startswith('linux'):
k4 = False k4 = False
kInfoFiles = None kInfoFiles = None
infile = args[0] infile = args[0]
outdir = args[1] outdir = args[1]
return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids) return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids)
@@ -205,4 +221,3 @@ def main(argv=sys.argv):
if __name__ == '__main__': if __name__ == '__main__':
sys.stdout=Unbuffered(sys.stdout) sys.stdout=Unbuffered(sys.stdout)
sys.exit(main()) sys.exit(main())

View File

@@ -91,9 +91,9 @@ def getTwoBitsFromBitField(bitField,offset):
# Returns the six bits at offset from a bit field # Returns the six bits at offset from a bit field
def getSixBitsFromBitField(bitField,offset): def getSixBitsFromBitField(bitField,offset):
offset *= 3 offset *= 3
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2) value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
return value return value
# 8 bits to six bits encoding from hash to generate PID string # 8 bits to six bits encoding from hash to generate PID string
def encodePID(hash): def encodePID(hash):
@@ -121,8 +121,8 @@ def generatePidEncryptionTable() :
def generatePidSeed(table,dsn) : def generatePidSeed(table,dsn) :
value = 0 value = 0
for counter in range (0,4) : for counter in range (0,4) :
index = (ord(dsn[counter]) ^ value) &0xFF index = (ord(dsn[counter]) ^ value) &0xFF
value = (value >> 8) ^ table[index] value = (value >> 8) ^ table[index]
return value return value
# Generate the device PID # Generate the device PID
@@ -141,7 +141,7 @@ def generateDevicePID(table,dsn,nbRoll):
return pidAscii return pidAscii
def crc32(s): def crc32(s):
return (~binascii.crc32(s,-1))&0xFFFFFFFF return (~binascii.crc32(s,-1))&0xFFFFFFFF
# convert from 8 digit PID to 10 digit PID with checksum # convert from 8 digit PID to 10 digit PID with checksum
def checksumPid(s): def checksumPid(s):
@@ -258,13 +258,19 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
def getPidList(md1, md2, k4, pids, serials, kInfoFiles): def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
pidlst = [] pidlst = []
if kInfoFiles is None: if kInfoFiles is None:
kInfoFiles = [] kInfoFiles = []
if k4: if k4:
kInfoFiles = getKindleInfoFiles(kInfoFiles) kInfoFiles = getKindleInfoFiles(kInfoFiles)
for infoFile in kInfoFiles: for infoFile in kInfoFiles:
pidlst = getK4Pids(pidlst, md1, md2, infoFile) try:
pidlst = getK4Pids(pidlst, md1, md2, infoFile)
except Exception, message:
print("Error getting PIDs from " + infoFile + ": " + message)
for serialnum in serials: for serialnum in serials:
pidlst = getKindlePid(pidlst, md1, md2, serialnum) try:
pidlst = getKindlePid(pidlst, md1, md2, serialnum)
except Exception, message:
print("Error getting PIDs from " + serialnum + ": " + message)
for pid in pids: for pid in pids:
pidlst.append(pid) pidlst.append(pid)
return pidlst return pidlst

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,68 @@
# A simple implementation of pbkdf2 using stock python modules. See RFC2898
# for details. Basically, it derives a key from a password and salt.
# Copyright 2004 Matt Johnston <matt @ ucc asn au>
# Copyright 2009 Daniel Holth <dholth@fastmail.fm>
# This code may be freely used and modified for any purpose.
# Revision history
# v0.1 October 2004 - Initial release
# v0.2 8 March 2007 - Make usable with hashlib in Python 2.5 and use
# v0.3 "" the correct digest_size rather than always 20
# v0.4 Oct 2009 - Rescue from chandler svn, test and optimize.
import sys
import hmac
from struct import pack
try:
# only in python 2.5
import hashlib
sha = hashlib.sha1
md5 = hashlib.md5
sha256 = hashlib.sha256
except ImportError: # pragma: NO COVERAGE
# fallback
import sha
import md5
# this is what you want to call.
def pbkdf2( password, salt, itercount, keylen, hashfn = sha ):
try:
# depending whether the hashfn is from hashlib or sha/md5
digest_size = hashfn().digest_size
except TypeError: # pragma: NO COVERAGE
digest_size = hashfn.digest_size
# l - number of output blocks to produce
l = keylen / digest_size
if keylen % digest_size != 0:
l += 1
h = hmac.new( password, None, hashfn )
T = ""
for i in range(1, l+1):
T += pbkdf2_F( h, salt, itercount, i )
return T[0: keylen]
def xorstr( a, b ):
if len(a) != len(b):
raise ValueError("xorstr(): lengths differ")
return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b)))
def prf( h, data ):
hm = h.copy()
hm.update( data )
return hm.digest()
# Helper as per the spec. h is a hmac which has been created seeded with the
# password, it will be copy()ed and not modified.
def pbkdf2_F( h, salt, itercount, blocknum ):
U = prf( h, salt + pack('>i',blocknum ) )
T = U
for i in range(2, itercount+1):
U = prf( h, U )
T = xorstr( T, U )
return T

View File

@@ -6,6 +6,7 @@ import csv
import sys import sys
import os import os
import getopt import getopt
import re
from struct import pack from struct import pack
from struct import unpack from struct import unpack
@@ -82,12 +83,19 @@ class DocParser(object):
return startpos return startpos
# returns a vector of integers for the tagpath # returns a vector of integers for the tagpath
def getData(self, tagpath, pos, end): def getData(self, tagpath, pos, end, clean=False):
if clean:
digits_only = re.compile(r'''([0-9]+)''')
argres=[] argres=[]
(foundat, argt) = self.findinDoc(tagpath, pos, end) (foundat, argt) = self.findinDoc(tagpath, pos, end)
if (argt != None) and (len(argt) > 0) : if (argt != None) and (len(argt) > 0) :
argList = argt.split('|') argList = argt.split('|')
argres = [ int(strval) for strval in argList] for strval in argList:
if clean:
m = re.search(digits_only, strval)
if m != None:
strval = m.group()
argres.append(int(strval))
return argres return argres
def process(self): def process(self):
@@ -157,6 +165,9 @@ class DocParser(object):
elif attr == 'line-space': elif attr == 'line-space':
scale = self.fontsize * 2.0 scale = self.fontsize * 2.0
if val == "":
val = 0
if not ((attr == 'hang') and (int(val) == 0)) : if not ((attr == 'hang') and (int(val) == 0)) :
pv = float(val)/scale pv = float(val)/scale
cssargs[attr] = (self.attr_val_map[attr], pv) cssargs[attr] = (self.attr_val_map[attr], pv)
@@ -251,5 +262,5 @@ def convert2CSS(flatxml, fontsize, ph, pw):
def getpageIDMap(flatxml): def getpageIDMap(flatxml):
dp = DocParser(flatxml, 0, 0, 0) dp = DocParser(flatxml, 0, 0, 0)
pageidnumbers = dp.getData('info.original.pid', 0, -1) pageidnumbers = dp.getData('info.original.pid', 0, -1, True)
return pageidnumbers return pageidnumbers

View File

@@ -52,7 +52,7 @@ class Process(object):
self.__stdout_thread = threading.Thread( self.__stdout_thread = threading.Thread(
name="stdout-thread", name="stdout-thread",
target=self.__reader, args=(self.__collected_outdata, target=self.__reader, args=(self.__collected_outdata,
self.__process.stdout)) self.__process.stdout))
self.__stdout_thread.setDaemon(True) self.__stdout_thread.setDaemon(True)
self.__stdout_thread.start() self.__stdout_thread.start()
@@ -60,7 +60,7 @@ class Process(object):
self.__stderr_thread = threading.Thread( self.__stderr_thread = threading.Thread(
name="stderr-thread", name="stderr-thread",
target=self.__reader, args=(self.__collected_errdata, target=self.__reader, args=(self.__collected_errdata,
self.__process.stderr)) self.__process.stderr))
self.__stderr_thread.setDaemon(True) self.__stderr_thread.setDaemon(True)
self.__stderr_thread.start() self.__stderr_thread.start()
@@ -146,4 +146,3 @@ class Process(object):
self.__quit = True self.__quit = True
self.__inputsem.release() self.__inputsem.release()
self.__lock.release() self.__lock.release()

View File

@@ -16,10 +16,13 @@ if 'calibre' in sys.modules:
else: else:
inCalibre = False inCalibre = False
buildXML = False
import os, csv, getopt import os, csv, getopt
import zlib, zipfile, tempfile, shutil import zlib, zipfile, tempfile, shutil
from struct import pack from struct import pack
from struct import unpack from struct import unpack
from alfcrypto import Topaz_Cipher
class TpzDRMError(Exception): class TpzDRMError(Exception):
pass pass
@@ -28,11 +31,8 @@ class TpzDRMError(Exception):
# local support routines # local support routines
if inCalibre: if inCalibre:
from calibre_plugins.k4mobidedrm import kgenpids from calibre_plugins.k4mobidedrm import kgenpids
from calibre_plugins.k4mobidedrm import genbook
else: else:
import kgenpids import kgenpids
import genbook
# recursive zip creation support routine # recursive zip creation support routine
def zipUpDir(myzip, tdir, localname): def zipUpDir(myzip, tdir, localname):
@@ -58,8 +58,8 @@ def bookReadEncodedNumber(fo):
flag = False flag = False
data = ord(fo.read(1)) data = ord(fo.read(1))
if data == 0xFF: if data == 0xFF:
flag = True flag = True
data = ord(fo.read(1)) data = ord(fo.read(1))
if data >= 0x80: if data >= 0x80:
datax = (data & 0x7F) datax = (data & 0x7F)
while data >= 0x80 : while data >= 0x80 :
@@ -67,7 +67,7 @@ def bookReadEncodedNumber(fo):
datax = (datax <<7) + (data & 0x7F) datax = (datax <<7) + (data & 0x7F)
data = datax data = datax
if flag: if flag:
data = -data data = -data
return data return data
# Get a length prefixed string from file # Get a length prefixed string from file
@@ -81,25 +81,28 @@ def bookReadString(fo):
# Context initialisation for the Topaz Crypto # Context initialisation for the Topaz Crypto
def topazCryptoInit(key): def topazCryptoInit(key):
ctx1 = 0x0CAFFE19E return Topaz_Cipher().ctx_init(key)
for keyChar in key:
keyByte = ord(keyChar) # ctx1 = 0x0CAFFE19E
ctx2 = ctx1 # for keyChar in key:
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF ) # keyByte = ord(keyChar)
return [ctx1,ctx2] # ctx2 = ctx1
# ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
# return [ctx1,ctx2]
# decrypt data with the context prepared by topazCryptoInit() # decrypt data with the context prepared by topazCryptoInit()
def topazCryptoDecrypt(data, ctx): def topazCryptoDecrypt(data, ctx):
ctx1 = ctx[0] return Topaz_Cipher().decrypt(data, ctx)
ctx2 = ctx[1] # ctx1 = ctx[0]
plainText = "" # ctx2 = ctx[1]
for dataChar in data: # plainText = ""
dataByte = ord(dataChar) # for dataChar in data:
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF # dataByte = ord(dataChar)
ctx2 = ctx1 # m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF) # ctx2 = ctx1
plainText += chr(m) # ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
return plainText # plainText += chr(m)
# return plainText
# Decrypt data with the PID # Decrypt data with the PID
def decryptRecord(data,PID): def decryptRecord(data,PID):
@@ -265,6 +268,11 @@ class TopazBook:
self.createBookDirectory() self.createBookDirectory()
self.extractFiles() self.extractFiles()
print "Successfully Extracted Topaz contents" print "Successfully Extracted Topaz contents"
if inCalibre:
from calibre_plugins.k4mobidedrm import genbook
else:
import genbook
rv = genbook.generateBook(self.outdir, raw, fixedimage) rv = genbook.generateBook(self.outdir, raw, fixedimage)
if rv == 0: if rv == 0:
print "\nBook Successfully generated" print "\nBook Successfully generated"
@@ -294,6 +302,11 @@ class TopazBook:
self.createBookDirectory() self.createBookDirectory()
self.extractFiles() self.extractFiles()
print "Successfully Extracted Topaz contents" print "Successfully Extracted Topaz contents"
if inCalibre:
from calibre_plugins.k4mobidedrm import genbook
else:
import genbook
rv = genbook.generateBook(self.outdir, raw, fixedimage) rv = genbook.generateBook(self.outdir, raw, fixedimage)
if rv == 0: if rv == 0:
print "\nBook Successfully generated" print "\nBook Successfully generated"
@@ -371,8 +384,7 @@ class TopazBook:
def cleanup(self): def cleanup(self):
if os.path.isdir(self.outdir): if os.path.isdir(self.outdir):
pass shutil.rmtree(self.outdir, True)
# shutil.rmtree(self.outdir, True)
def usage(progname): def usage(progname):
print "Removes DRM protection from Topaz ebooks and extract the contents" print "Removes DRM protection from Topaz ebooks and extract the contents"
@@ -382,6 +394,7 @@ def usage(progname):
# Main # Main
def main(argv=sys.argv): def main(argv=sys.argv):
global buildXML
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
k4 = False k4 = False
pids = [] pids = []
@@ -443,9 +456,10 @@ def main(argv=sys.argv):
zipname = os.path.join(outdir, bookname + '_SVG' + '.zip') zipname = os.path.join(outdir, bookname + '_SVG' + '.zip')
tb.getSVGZip(zipname) tb.getSVGZip(zipname)
print " Creating XML ZIP Archive" if buildXML:
zipname = os.path.join(outdir, bookname + '_XML' + '.zip') print " Creating XML ZIP Archive"
tb.getXMLZip(zipname) zipname = os.path.join(outdir, bookname + '_XML' + '.zip')
tb.getXMLZip(zipname)
# removing internal temporary directory of pieces # removing internal temporary directory of pieces
tb.cleanup() tb.cleanup()
@@ -466,4 +480,3 @@ def main(argv=sys.argv):
if __name__ == '__main__': if __name__ == '__main__':
sys.stdout=Unbuffered(sys.stdout) sys.stdout=Unbuffered(sys.stdout)
sys.exit(main()) sys.exit(main())

View File

@@ -1,22 +0,0 @@
Plugin for K4PC, K4Mac, standalone Kindles, Mobi Books, and for Devices with Fixed PIDs.
This plugin supersedes MobiDeDRM, K4DeDRM, and K4PCDeDRM and K4X plugins. If you install this plugin, those plugins can be safely removed.
This plugin is meant to remove the DRM from .prc, .azw, .azw1, and .tpz ebooks. Calibre can then convert them to whatever format you desire. It is meant to function without having to install any dependencies except for Calibre being on your same machine and in the same account as your "Kindle for PC" or "Kindle for Mac" application if you are going to remove the DRM from those types of books.
Installation:
Go to Calibre's Preferences page. Do **NOT** select "Get Plugins to enhance calibre" as this is reserved for official calibre plugins", instead select "Change calibre behavior". Under "Advanced" click on the on the Plugins button. Click on the "Load plugin from file" button at the bottom of the screen. Use the file dialog button to select the plugin's zip file (K4MobiDeDRM_vXX_plugin.zip) and click the "Add" (or it may say "Open" button. Then click on the "Yes" button in the warning dialog that appears. A Confirmation dialog appears that says the plugin has been installed.
Configuration:
Highlight the plugin (K4MobiDeDRM under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. Enter your 10 digit PID. If you have more than one PID separate them with a comma (no spaces). If you have a standalone Kindle include the 16 digit serial number (these typically begin "B0...") in this list (again separated from the PIDs or other serial numbers with a comma (no spaces). This configuration is not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books.
Troubleshooting:

View File

@@ -1,40 +0,0 @@
Ignoble Epub DeDRM - ignobleepub_vXX_plugin.zip
Requires Calibre version 0.6.44 or higher.
All credit given to I <3 Cabbages for the original standalone scripts.
I had the much easier job of converting them to a Calibre plugin.
This plugin is meant to decrypt Barnes & Noble Epubs that are protected
with Adobe's Adept encryption. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. It will still work if you have Python and PyCrypto already installed, but they aren't necessary.
Installation:
Go to Calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ignobleepub_vXX_plugin.zip) and
click the 'Add' button. you're done.
Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added.
Configuration:
1) The easiest way to configure the plugin is to enter your name (Barnes & Noble account name) and credit card number (the one used to purchase the books) into the plugin's customization window. It's the same info you would enter into the ignoblekeygen script. Highlight the plugin (Ignoble Epub DeDRM) and click the "Customize Plugin" button on
Calibre's Preferences->Plugins page. Enter the name and credit card number separated by a comma: Your Name,1234123412341234

View File

@@ -1,23 +1,31 @@
eReader PDB2PML - eReaderPDB2PML_vXX_plugin.zip eReader PDB2PML - eReaderPDB2PML_v06_plugin.zip
All credit given to The Dark Reverser for the original standalone script. I had the much easier job of converting it to a Calibre plugin. All credit given to The Dark Reverser for the original standalone script. I had the much easier job of converting it to a Calibre plugin.
This plugin is meant to convert secure Ereader files (PDB) to unsecured PMLZ files. Calibre can then convert it to whatever format you desire. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. I've included the psyco libraries (compiled for each platform) for speed. If your system can use them, great! Otherwise, they won't be used and things will just work slower. This plugin is meant to convert secure Ereader files (PDB) to unsecured PMLZ files. Calibre can then convert it to whatever format you desire. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. I've included the psyco libraries (compiled for each platform) for speed. If your system can use them, great! Otherwise, they won't be used and things will just work slower.
Installation: Installation:
Go to Calibre's Preferences page. Do **NOT** select "Get Plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (eReaderPDB2PML_vXX_plugin.zip) and click the 'Add' button. You're done. Go to Calibre's Preferences page. Do **NOT** select "Get Plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (eReaderPDB2PML_vXX_plugin.zip) and click the 'Add' button. You're done.
Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added. Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added.
Configuration: Configuration:
Highlight the plugin (eReader PDB 2 PML under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. Enter your name and last 8 digits of the credit card number separated by a comma: Your Name,12341234 Highlight the plugin (eReader PDB 2 PML under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. Enter your name and last 8 digits of the credit card number separated by a comma: Your Name,12341234
If you've purchased books with more than one credit card, separate the info with a colon: Your Name,12341234:Other Name,23452345 (NOTE: Do NOT put quotes around your name like you do with the original script!!) If you've purchased books with more than one credit card, separate the info with a colon: Your Name,12341234:Other Name,23452345 (NOTE: Do NOT put quotes around your name like you do with the original script!!)
Troubleshooting: Troubleshooting:
If you find that it's not working for you (imported pdb's are not converted to pmlz format), you can save a lot of time and trouble by trying to add the pdb to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might
as well get used to it. ;) If you find that it's not working for you (imported pdb's are not converted to pmlz format), you can save a lot of time and trouble by trying to add the pdb to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.pdb". Don't type the quotes and obviously change the 'your_ebook.pdb' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make. Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.pdb". Don't type the quotes and obviously change the 'your_ebook.pdb' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools. ** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.

Binary file not shown.

View File

@@ -1,6 +1,8 @@
#! /usr/bin/env python #! /usr/bin/env python
# ineptpdf plugin __init__.py, version 0.1.5
from __future__ import with_statement
# ineptpdf plugin __init__.py
# Released under the terms of the GNU General Public Licence, version 3 or # Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/> # later. <http://www.gnu.org/licenses/>
@@ -52,13 +54,11 @@
# 0.1.2 - back port ineptpdf 8.4.X bug fixes # 0.1.2 - back port ineptpdf 8.4.X bug fixes
# 0.1.3 - add in fix for improper rejection of session bookkeys with len(bookkey) = length + 1 # 0.1.3 - add in fix for improper rejection of session bookkeys with len(bookkey) = length + 1
# 0.1.4 - update to the new calibre plugin interface # 0.1.4 - update to the new calibre plugin interface
# 0.1.5 - synced to ineptpdf 7.11
""" """
Decrypts Adobe ADEPT-encrypted PDF files. Decrypts Adobe ADEPT-encrypted PDF files.
""" """
from __future__ import with_statement
__license__ = 'GPL v3' __license__ = 'GPL v3'
import sys import sys
@@ -311,6 +311,7 @@ def _load_crypto_pycrypto():
return self._arc4.decrypt(data) return self._arc4.decrypt(data)
class AES(object): class AES(object):
MODE_CBC = _AES.MODE_CBC
@classmethod @classmethod
def new(cls, userkey, mode, iv): def new(cls, userkey, mode, iv):
self = AES() self = AES()
@@ -1045,25 +1046,25 @@ def stream_value(x):
# ascii85decode(data) # ascii85decode(data)
def ascii85decode(data): def ascii85decode(data):
n = b = 0 n = b = 0
out = '' out = ''
for c in data: for c in data:
if '!' <= c and c <= 'u': if '!' <= c and c <= 'u':
n += 1 n += 1
b = b*85+(ord(c)-33) b = b*85+(ord(c)-33)
if n == 5: if n == 5:
out += struct.pack('>L',b) out += struct.pack('>L',b)
n = b = 0 n = b = 0
elif c == 'z': elif c == 'z':
assert n == 0 assert n == 0
out += '\0\0\0\0' out += '\0\0\0\0'
elif c == '~': elif c == '~':
if n: if n:
for _ in range(5-n): for _ in range(5-n):
b = b*85+84 b = b*85+84
out += struct.pack('>L',b)[:n-1] out += struct.pack('>L',b)[:n-1]
break break
return out return out
## PDFStream type ## PDFStream type
@@ -2089,7 +2090,7 @@ class PDFSerializer(object):
### are no longer useful, as we have extracted all objects from ### are no longer useful, as we have extracted all objects from
### them. Therefore leave them out from the output. ### them. Therefore leave them out from the output.
if obj.dic.get('Type') == LITERAL_OBJSTM and not gen_xref_stm: if obj.dic.get('Type') == LITERAL_OBJSTM and not gen_xref_stm:
self.write('(deleted)') self.write('(deleted)')
else: else:
data = obj.get_decdata() data = obj.get_decdata()
self.serialize_object(obj.dic) self.serialize_object(obj.dic)
@@ -2136,7 +2137,7 @@ class IneptPDFDeDRM(FileTypePlugin):
Credit given to I <3 Cabbages for the original stand-alone scripts.' Credit given to I <3 Cabbages for the original stand-alone scripts.'
supported_platforms = ['linux', 'osx', 'windows'] supported_platforms = ['linux', 'osx', 'windows']
author = 'DiapDealer' author = 'DiapDealer'
version = (0, 1, 4) version = (0, 1, 5)
minimum_calibre_version = (0, 7, 55) # for the new plugin interface minimum_calibre_version = (0, 7, 55) # for the new plugin interface
file_types = set(['pdf']) file_types = set(['pdf'])
on_import = True on_import = True

View File

@@ -5,7 +5,8 @@ from __future__ import with_statement
import sys import sys
import os import os
import os.path import os.path
import re
import copy
import subprocess import subprocess
from struct import pack, unpack, unpack_from from struct import pack, unpack, unpack_from
@@ -24,6 +25,25 @@ def _load_crypto_libcrypto():
raise DrmException('libcrypto not found') raise DrmException('libcrypto not found')
libcrypto = CDLL(libcrypto) libcrypto = CDLL(libcrypto)
# From OpenSSL's crypto aes header
#
# AES_ENCRYPT 1
# AES_DECRYPT 0
# AES_MAXNR 14 (in bytes)
# AES_BLOCK_SIZE 16 (in bytes)
#
# struct aes_key_st {
# unsigned long rd_key[4 *(AES_MAXNR + 1)];
# int rounds;
# };
# typedef struct aes_key_st AES_KEY;
#
# int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
#
# note: the ivec string, and output buffer are both mutable
# void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
# const unsigned long length, const AES_KEY *key, unsigned char *ivec, const int enc);
AES_MAXNR = 14 AES_MAXNR = 14
c_char_pp = POINTER(c_char_p) c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int) c_int_p = POINTER(c_int)
@@ -42,6 +62,12 @@ def _load_crypto_libcrypto():
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p]) AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
# From OpenSSL's Crypto evp/p5_crpt2.c
#
# int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen,
# const unsigned char *salt, int saltlen, int iter,
# int keylen, unsigned char *out);
PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
[c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p]) [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
@@ -49,7 +75,7 @@ def _load_crypto_libcrypto():
def __init__(self): def __init__(self):
self._blocksize = 0 self._blocksize = 0
self._keyctx = None self._keyctx = None
self.iv = 0 self._iv = 0
def set_decrypt_key(self, userkey, iv): def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey) self._blocksize = len(userkey)
@@ -57,14 +83,17 @@ def _load_crypto_libcrypto():
raise DrmException('AES improper key used') raise DrmException('AES improper key used')
return return
keyctx = self._keyctx = AES_KEY() keyctx = self._keyctx = AES_KEY()
self.iv = iv self._iv = iv
self._userkey = userkey
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0: if rv < 0:
raise DrmException('Failed to initialize AES key') raise DrmException('Failed to initialize AES key')
def decrypt(self, data): def decrypt(self, data):
out = create_string_buffer(len(data)) out = create_string_buffer(len(data))
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0) mutable_iv = create_string_buffer(self._iv, len(self._iv))
keyctx = self._keyctx
rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
if rv == 0: if rv == 0:
raise DrmException('AES decryption failed') raise DrmException('AES decryption failed')
return out.raw return out.raw
@@ -113,11 +142,15 @@ def SHA256(message):
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
# For kinf approach of K4PC/K4Mac # For kinf approach of K4Mac 1.6.X or later
# On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" # On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
# For Mac they seem to re-use charMap2 here # For Mac they seem to re-use charMap2 here
charMap5 = charMap2 charMap5 = charMap2
# new in K4M 1.9.X
testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
def encode(data, map): def encode(data, map):
result = "" result = ""
for char in data: for char in data:
@@ -144,7 +177,7 @@ def decode(data,map):
result += pack("B",value) result += pack("B",value)
return result return result
# For .kinf approach of K4PC and now K4Mac # For K4M 1.6.X and later
# generate table of prime number less than or equal to int n # generate table of prime number less than or equal to int n
def primes(n): def primes(n):
if n==2: return [2] if n==2: return [2]
@@ -200,7 +233,7 @@ def GetVolumeSerialNumber():
def GetUserHomeAppSupKindleDirParitionName(): def GetUserHomeAppSupKindleDirParitionName():
home = os.getenv('HOME') home = os.getenv('HOME')
dpath = home + '/Library/Application Support/Kindle' dpath = home + '/Library'
cmdline = '/sbin/mount' cmdline = '/sbin/mount'
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
@@ -320,33 +353,15 @@ def GetUserName():
username = os.getenv('USER') username = os.getenv('USER')
return username return username
# implements an Pseudo Mac Version of Windows built-in Crypto routine
# used by Kindle for Mac versions < 1.6.0
def CryptUnprotectData(encryptedData):
sernum = GetVolumeSerialNumber()
if sernum == '':
sernum = '9999999999'
sp = sernum + '!@#' + GetUserName()
passwdData = encode(SHA256(sp),charMap1)
salt = '16743'
iter = 0x3e8
keylen = 0x80
crp = LibCrypto()
key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
key = key_iv[0:32]
iv = key_iv[32:48]
crp.set_decrypt_key(key,iv)
cleartext = crp.decrypt(encryptedData)
cleartext = decode(cleartext,charMap1)
return cleartext
def isNewInstall(): def isNewInstall():
home = os.getenv('HOME') home = os.getenv('HOME')
# soccer game fan anyone # soccer game fan anyone
dpath = home + '/Library/Application Support/Kindle/storage/.pes2011' dpath = home + '/Library/Application Support/Kindle/storage/.pes2011'
# print dpath, os.path.exists(dpath) # print dpath, os.path.exists(dpath)
if os.path.exists(dpath):
return True
dpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.pes2011'
# print dpath, os.path.exists(dpath)
if os.path.exists(dpath): if os.path.exists(dpath):
return True return True
return False return False
@@ -374,45 +389,136 @@ def GetIDString():
if isNewInstall(): if isNewInstall():
mungedmac = GetMACAddressMunged() mungedmac = GetMACAddressMunged()
if len(mungedmac) > 7: if len(mungedmac) > 7:
print('Using Munged MAC Address for ID: '+mungedmac)
return mungedmac return mungedmac
sernum = GetVolumeSerialNumber() sernum = GetVolumeSerialNumber()
if len(sernum) > 7: if len(sernum) > 7:
print('Using Volume Serial Number for ID: '+sernum)
return sernum return sernum
diskpart = GetUserHomeAppSupKindleDirParitionName() diskpart = GetUserHomeAppSupKindleDirParitionName()
uuidnum = GetDiskPartitionUUID(diskpart) uuidnum = GetDiskPartitionUUID(diskpart)
if len(uuidnum) > 7: if len(uuidnum) > 7:
print('Using Disk Partition UUID for ID: '+uuidnum)
return uuidnum return uuidnum
mungedmac = GetMACAddressMunged() mungedmac = GetMACAddressMunged()
if len(mungedmac) > 7: if len(mungedmac) > 7:
print('Using Munged MAC Address for ID: '+mungedmac)
return mungedmac return mungedmac
print('Using Fixed constant 9999999999 for ID.')
return '9999999999' return '9999999999'
# implements an Pseudo Mac Version of Windows built-in Crypto routine
# used by Kindle for Mac versions < 1.6.0
class CryptUnprotectData(object):
def __init__(self):
sernum = GetVolumeSerialNumber()
if sernum == '':
sernum = '9999999999'
sp = sernum + '!@#' + GetUserName()
passwdData = encode(SHA256(sp),charMap1)
salt = '16743'
self.crp = LibCrypto()
iter = 0x3e8
keylen = 0x80
key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
self.key = key_iv[0:32]
self.iv = key_iv[32:48]
self.crp.set_decrypt_key(self.key, self.iv)
def decrypt(self, encryptedData):
cleartext = self.crp.decrypt(encryptedData)
cleartext = decode(cleartext,charMap1)
return cleartext
# implements an Pseudo Mac Version of Windows built-in Crypto routine # implements an Pseudo Mac Version of Windows built-in Crypto routine
# used for Kindle for Mac Versions >= 1.6.0 # used for Kindle for Mac Versions >= 1.6.0
def CryptUnprotectDataV2(encryptedData): class CryptUnprotectDataV2(object):
sp = GetUserName() + ':&%:' + GetIDString() def __init__(self):
passwdData = encode(SHA256(sp),charMap5) sp = GetUserName() + ':&%:' + GetIDString()
# salt generation as per the code passwdData = encode(SHA256(sp),charMap5)
salt = 0x0512981d * 2 * 1 * 1 # salt generation as per the code
salt = str(salt) + GetUserName() salt = 0x0512981d * 2 * 1 * 1
salt = encode(salt,charMap5) salt = str(salt) + GetUserName()
salt = encode(salt,charMap5)
self.crp = LibCrypto()
iter = 0x800
keylen = 0x400
key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
self.key = key_iv[0:32]
self.iv = key_iv[32:48]
self.crp.set_decrypt_key(self.key, self.iv)
def decrypt(self, encryptedData):
cleartext = self.crp.decrypt(encryptedData)
cleartext = decode(cleartext, charMap5)
return cleartext
# unprotect the new header blob in .kinf2011
# used in Kindle for Mac Version >= 1.9.0
def UnprotectHeaderData(encryptedData):
passwdData = 'header_key_data'
salt = 'HEADER.2011'
iter = 0x80
keylen = 0x100
crp = LibCrypto() crp = LibCrypto()
iter = 0x800
keylen = 0x400
key_iv = crp.keyivgen(passwdData, salt, iter, keylen) key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
key = key_iv[0:32] key = key_iv[0:32]
iv = key_iv[32:48] iv = key_iv[32:48]
crp.set_decrypt_key(key,iv) crp.set_decrypt_key(key,iv)
cleartext = crp.decrypt(encryptedData) cleartext = crp.decrypt(encryptedData)
cleartext = decode(cleartext, charMap5)
return cleartext return cleartext
# implements an Pseudo Mac Version of Windows built-in Crypto routine
# used for Kindle for Mac Versions >= 1.9.0
class CryptUnprotectDataV3(object):
def __init__(self, entropy):
sp = GetUserName() + '+@#$%+' + GetIDString()
passwdData = encode(SHA256(sp),charMap2)
salt = entropy
self.crp = LibCrypto()
iter = 0x800
keylen = 0x400
key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
self.key = key_iv[0:32]
self.iv = key_iv[32:48]
self.crp.set_decrypt_key(self.key, self.iv)
def decrypt(self, encryptedData):
cleartext = self.crp.decrypt(encryptedData)
cleartext = decode(cleartext, charMap2)
return cleartext
# Locate the .kindle-info files # Locate the .kindle-info files
def getKindleInfoFiles(kInfoFiles): def getKindleInfoFiles(kInfoFiles):
# first search for current .kindle-info files
home = os.getenv('HOME') home = os.getenv('HOME')
# search for any .kinf2011 files in new location (Sep 2012)
cmdline = 'find "' + home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support" -name ".kinf2011"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
out1, out2 = p1.communicate()
reslst = out1.split('\n')
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found k4Mac kinf2011 file: ' + resline)
found = True
# search for any .kinf2011 files
cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
out1, out2 = p1.communicate()
reslst = out1.split('\n')
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found k4Mac kinf2011 file: ' + resline)
found = True
# search for any .kindle-info files
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"' cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
@@ -423,8 +529,9 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst: for resline in reslst:
if os.path.isfile(resline): if os.path.isfile(resline):
kInfoFiles.append(resline) kInfoFiles.append(resline)
print('Found K4Mac kindle-info file: ' + resline)
found = True found = True
# add any .kinf files # search for any .rainier*-kinf files
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"' cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
@@ -433,9 +540,10 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst: for resline in reslst:
if os.path.isfile(resline): if os.path.isfile(resline):
kInfoFiles.append(resline) kInfoFiles.append(resline)
print('Found k4Mac kinf file: ' + resline)
found = True found = True
if not found: if not found:
print('No kindle-info files have been found.') print('No k4Mac kindle-info/kinf/kinf2011 files have been found.')
return kInfoFiles return kInfoFiles
# determine type of kindle info provided and return a # determine type of kindle info provided and return a
@@ -449,7 +557,9 @@ def getDBfromFile(kInfoFile):
data = infoReader.read() data = infoReader.read()
if data.find('[') != -1 : if data.find('[') != -1 :
# older style kindle-info file # older style kindle-info file
cud = CryptUnprotectData()
items = data.split('[') items = data.split('[')
for item in items: for item in items:
if item != '': if item != '':
@@ -462,19 +572,111 @@ def getDBfromFile(kInfoFile):
if keyname == "unknown": if keyname == "unknown":
keyname = keyhash keyname = keyhash
encryptedValue = decode(rawdata,charMap2) encryptedValue = decode(rawdata,charMap2)
cleartext = CryptUnprotectData(encryptedValue) cleartext = cud.decrypt(encryptedValue)
DB[keyname] = cleartext DB[keyname] = cleartext
cnt = cnt + 1 cnt = cnt + 1
if cnt == 0: if cnt == 0:
DB = None DB = None
return DB return DB
# else newer style .kinf file used by K4Mac >= 1.6.0 if hdr == '/':
# the .kinf file uses "/" to separate it into records
# so remove the trailing "/" to make it easy to use split # else newer style .kinf file used by K4Mac >= 1.6.0
# the .kinf file uses "/" to separate it into records
# so remove the trailing "/" to make it easy to use split
data = data[:-1]
items = data.split('/')
cud = CryptUnprotectDataV2()
# loop through the item records until all are processed
while len(items) > 0:
# get the first item record
item = items.pop(0)
# the first 32 chars of the first record of a group
# is the MD5 hash of the key name encoded by charMap5
keyhash = item[0:32]
keyname = "unknown"
# the raw keyhash string is also used to create entropy for the actual
# CryptProtectData Blob that represents that keys contents
# "entropy" not used for K4Mac only K4PC
# entropy = SHA1(keyhash)
# the remainder of the first record when decoded with charMap5
# has the ':' split char followed by the string representation
# of the number of records that follow
# and make up the contents
srcnt = decode(item[34:],charMap5)
rcnt = int(srcnt)
# read and store in rcnt records of data
# that make up the contents value
edlst = []
for i in xrange(rcnt):
item = items.pop(0)
edlst.append(item)
keyname = "unknown"
for name in names:
if encodeHash(name,charMap5) == keyhash:
keyname = name
break
if keyname == "unknown":
keyname = keyhash
# the charMap5 encoded contents data has had a length
# of chars (always odd) cut off of the front and moved
# to the end to prevent decoding using charMap5 from
# working properly, and thereby preventing the ensuing
# CryptUnprotectData call from succeeding.
# The offset into the charMap5 encoded contents seems to be:
# len(contents) - largest prime number less than or equal to int(len(content)/3)
# (in other words split "about" 2/3rds of the way through)
# move first offsets chars to end to align for decode by charMap5
encdata = "".join(edlst)
contlen = len(encdata)
# now properly split and recombine
# by moving noffset chars from the start of the
# string to the end of the string
noffset = contlen - primes(int(contlen/3))[-1]
pfx = encdata[0:noffset]
encdata = encdata[noffset:]
encdata = encdata + pfx
# decode using charMap5 to get the CryptProtect Data
encryptedValue = decode(encdata,charMap5)
cleartext = cud.decrypt(encryptedValue)
DB[keyname] = cleartext
cnt = cnt + 1
if cnt == 0:
DB = None
return DB
# the latest .kinf2011 version for K4M 1.9.1
# put back the hdr char, it is needed
data = hdr + data
data = data[:-1] data = data[:-1]
items = data.split('/') items = data.split('/')
# the headerblob is the encrypted information needed to build the entropy string
headerblob = items.pop(0)
encryptedValue = decode(headerblob, charMap1)
cleartext = UnprotectHeaderData(encryptedValue)
# now extract the pieces in the same way
# this version is different from K4PC it scales the build number by multipying by 735
pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
for m in re.finditer(pattern, cleartext):
entropy = str(int(m.group(2)) * 0x2df) + m.group(4)
cud = CryptUnprotectDataV3(entropy)
# loop through the item records until all are processed # loop through the item records until all are processed
while len(items) > 0: while len(items) > 0:
@@ -486,10 +688,9 @@ def getDBfromFile(kInfoFile):
keyhash = item[0:32] keyhash = item[0:32]
keyname = "unknown" keyname = "unknown"
# the raw keyhash string is also used to create entropy for the actual # unlike K4PC the keyhash is not used in generating entropy
# CryptProtectData Blob that represents that keys contents # entropy = SHA1(keyhash) + added_entropy
# "entropy" not used for K4Mac only K4PC # entropy = added_entropy
# entropy = SHA1(keyhash)
# the remainder of the first record when decoded with charMap5 # the remainder of the first record when decoded with charMap5
# has the ':' split char followed by the string representation # has the ':' split char followed by the string representation
@@ -507,23 +708,23 @@ def getDBfromFile(kInfoFile):
keyname = "unknown" keyname = "unknown"
for name in names: for name in names:
if encodeHash(name,charMap5) == keyhash: if encodeHash(name,testMap8) == keyhash:
keyname = name keyname = name
break break
if keyname == "unknown": if keyname == "unknown":
keyname = keyhash keyname = keyhash
# the charMap5 encoded contents data has had a length # the testMap8 encoded contents data has had a length
# of chars (always odd) cut off of the front and moved # of chars (always odd) cut off of the front and moved
# to the end to prevent decoding using charMap5 from # to the end to prevent decoding using testMap8 from
# working properly, and thereby preventing the ensuing # working properly, and thereby preventing the ensuing
# CryptUnprotectData call from succeeding. # CryptUnprotectData call from succeeding.
# The offset into the charMap5 encoded contents seems to be: # The offset into the testMap8 encoded contents seems to be:
# len(contents) - largest prime number less than or equal to int(len(content)/3) # len(contents) - largest prime number less than or equal to int(len(content)/3)
# (in other words split "about" 2/3rds of the way through) # (in other words split "about" 2/3rds of the way through)
# move first offsets chars to end to align for decode by charMap5 # move first offsets chars to end to align for decode by testMap8
encdata = "".join(edlst) encdata = "".join(edlst)
contlen = len(encdata) contlen = len(encdata)
@@ -535,14 +736,11 @@ def getDBfromFile(kInfoFile):
encdata = encdata[noffset:] encdata = encdata[noffset:]
encdata = encdata + pfx encdata = encdata + pfx
# decode using charMap5 to get the CryptProtect Data # decode using testMap8 to get the CryptProtect Data
encryptedValue = decode(encdata,charMap5) encryptedValue = decode(encdata,testMap8)
cleartext = CryptUnprotectDataV2(encryptedValue) cleartext = cud.decrypt(encryptedValue)
# Debugging
# print keyname # print keyname
# print cleartext # print cleartext
# print cleartext.encode('hex')
# print
DB[keyname] = cleartext DB[keyname] = cleartext
cnt = cnt + 1 cnt = cnt + 1

View File

@@ -3,7 +3,7 @@
from __future__ import with_statement from __future__ import with_statement
import sys, os import sys, os, re
from struct import pack, unpack, unpack_from from struct import pack, unpack, unpack_from
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
@@ -11,9 +11,7 @@ from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
string_at, Structure, c_void_p, cast string_at, Structure, c_void_p, cast
import _winreg as winreg import _winreg as winreg
MAX_PATH = 255 MAX_PATH = 255
kernel32 = windll.kernel32 kernel32 = windll.kernel32
advapi32 = windll.advapi32 advapi32 = windll.advapi32
crypt32 = windll.crypt32 crypt32 = windll.crypt32
@@ -33,6 +31,32 @@ def SHA1(message):
ctx.update(message) ctx.update(message)
return ctx.digest() return ctx.digest()
def SHA256(message):
ctx = hashlib.sha256()
ctx.update(message)
return ctx.digest()
# For K4PC 1.9.X
# use routines in alfcrypto:
# AES_cbc_encrypt
# AES_set_decrypt_key
# PKCS5_PBKDF2_HMAC_SHA1
from alfcrypto import AES_CBC, KeyIVGen
def UnprotectHeaderData(encryptedData):
passwdData = 'header_key_data'
salt = 'HEADER.2011'
iter = 0x80
keylen = 0x100
key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen)
key = key_iv[0:32]
iv = key_iv[32:48]
aes=AES_CBC()
aes.set_decrypt_key(key, iv)
cleartext = aes.decrypt(encryptedData)
return cleartext
# simple primes table (<= n) calculator # simple primes table (<= n) calculator
def primes(n): def primes(n):
@@ -59,6 +83,10 @@ def primes(n):
# Probably supposed to act as obfuscation # Probably supposed to act as obfuscation
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_" charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
# New maps in K4PC 1.9.0
testMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
testMap6 = "9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
class DrmException(Exception): class DrmException(Exception):
pass pass
@@ -123,7 +151,9 @@ def GetVolumeSerialNumber():
GetVolumeSerialNumber = GetVolumeSerialNumber() GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetIDString(): def GetIDString():
return GetVolumeSerialNumber() vsn = GetVolumeSerialNumber()
print('Using Volume Serial Number for ID: '+vsn)
return vsn
def getLastError(): def getLastError():
GetLastError = kernel32.GetLastError GetLastError = kernel32.GetLastError
@@ -165,7 +195,8 @@ def CryptUnprotectData():
outdata = DataBlob() outdata = DataBlob()
if not _CryptUnprotectData(byref(indata), None, byref(entropy), if not _CryptUnprotectData(byref(indata), None, byref(entropy),
None, None, flags, byref(outdata)): None, None, flags, byref(outdata)):
raise DrmException("Failed to Unprotect Data") # raise DrmException("Failed to Unprotect Data")
return 'failed'
return string_at(outdata.pbData, outdata.cbData) return string_at(outdata.pbData, outdata.cbData)
return CryptUnprotectData return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData() CryptUnprotectData = CryptUnprotectData()
@@ -176,28 +207,45 @@ def getKindleInfoFiles(kInfoFiles):
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0] path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
# some 64 bit machines do not have the proper registry key for some reason
# or the pythonn interface to the 32 vs 64 bit registry is broken
if 'LOCALAPPDATA' in os.environ.keys():
path = os.environ['LOCALAPPDATA']
print('searching for kinfoFiles in ' + path)
found = False
# first look for older kindle-info files # first look for older kindle-info files
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info' kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath): if os.path.isfile(kinfopath):
print('No kindle.info files have not been found.') found = True
else: print('Found K4PC kindle.info file: ' + kinfopath)
kInfoFiles.append(kinfopath) kInfoFiles.append(kinfopath)
# now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file # now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file
kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf' kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
if not os.path.isfile(kinfopath): if os.path.isfile(kinfopath):
print('No K4PC 1.5.X .kinf files have not been found.') found = True
else: print('Found K4PC 1.5.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath) kInfoFiles.append(kinfopath)
# now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file # now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf' kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
if not os.path.isfile(kinfopath): if os.path.isfile(kinfopath):
print('No K4PC 1.6.X .kinf files have not been found.') found = True
else: print('Found K4PC 1.6.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath) kInfoFiles.append(kinfopath)
# now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
if os.path.isfile(kinfopath):
found = True
print('Found K4PC kinf2011 file: ' + kinfopath)
kInfoFiles.append(kinfopath)
if not found:
print('No K4PC kindle.info/kinf/kinf2011 files have been found.')
return kInfoFiles return kInfoFiles
@@ -232,12 +280,97 @@ def getDBfromFile(kInfoFile):
DB = None DB = None
return DB return DB
# else newer style .kinf file if hdr == '/':
# else rainier-2-1-1 .kinf file
# the .kinf file uses "/" to separate it into records
# so remove the trailing "/" to make it easy to use split
data = data[:-1]
items = data.split('/')
# loop through the item records until all are processed
while len(items) > 0:
# get the first item record
item = items.pop(0)
# the first 32 chars of the first record of a group
# is the MD5 hash of the key name encoded by charMap5
keyhash = item[0:32]
# the raw keyhash string is used to create entropy for the actual
# CryptProtectData Blob that represents that keys contents
entropy = SHA1(keyhash)
# the remainder of the first record when decoded with charMap5
# has the ':' split char followed by the string representation
# of the number of records that follow
# and make up the contents
srcnt = decode(item[34:],charMap5)
rcnt = int(srcnt)
# read and store in rcnt records of data
# that make up the contents value
edlst = []
for i in xrange(rcnt):
item = items.pop(0)
edlst.append(item)
keyname = "unknown"
for name in names:
if encodeHash(name,charMap5) == keyhash:
keyname = name
break
if keyname == "unknown":
keyname = keyhash
# the charMap5 encoded contents data has had a length
# of chars (always odd) cut off of the front and moved
# to the end to prevent decoding using charMap5 from
# working properly, and thereby preventing the ensuing
# CryptUnprotectData call from succeeding.
# The offset into the charMap5 encoded contents seems to be:
# len(contents)-largest prime number <= int(len(content)/3)
# (in other words split "about" 2/3rds of the way through)
# move first offsets chars to end to align for decode by charMap5
encdata = "".join(edlst)
contlen = len(encdata)
noffset = contlen - primes(int(contlen/3))[-1]
# now properly split and recombine
# by moving noffset chars from the start of the
# string to the end of the string
pfx = encdata[0:noffset]
encdata = encdata[noffset:]
encdata = encdata + pfx
# decode using Map5 to get the CryptProtect Data
encryptedValue = decode(encdata,charMap5)
DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1)
cnt = cnt + 1
if cnt == 0:
DB = None
return DB
# else newest .kinf2011 style .kinf file
# the .kinf file uses "/" to separate it into records # the .kinf file uses "/" to separate it into records
# so remove the trailing "/" to make it easy to use split # so remove the trailing "/" to make it easy to use split
data = data[:-1] # need to put back the first char read because it it part
# of the added entropy blob
data = hdr + data[:-1]
items = data.split('/') items = data.split('/')
# starts with and encoded and encrypted header blob
headerblob = items.pop(0)
encryptedValue = decode(headerblob, testMap1)
cleartext = UnprotectHeaderData(encryptedValue)
# now extract the pieces that form the added entropy
pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
for m in re.finditer(pattern, cleartext):
added_entropy = m.group(2) + m.group(4)
# loop through the item records until all are processed # loop through the item records until all are processed
while len(items) > 0: while len(items) > 0:
@@ -248,9 +381,9 @@ def getDBfromFile(kInfoFile):
# is the MD5 hash of the key name encoded by charMap5 # is the MD5 hash of the key name encoded by charMap5
keyhash = item[0:32] keyhash = item[0:32]
# the raw keyhash string is also used to create entropy for the actual # the sha1 of raw keyhash string is used to create entropy along
# CryptProtectData Blob that represents that keys contents # with the added entropy provided above from the headerblob
entropy = SHA1(keyhash) entropy = SHA1(keyhash) + added_entropy
# the remainder of the first record when decoded with charMap5 # the remainder of the first record when decoded with charMap5
# has the ':' split char followed by the string representation # has the ':' split char followed by the string representation
@@ -266,43 +399,39 @@ def getDBfromFile(kInfoFile):
item = items.pop(0) item = items.pop(0)
edlst.append(item) edlst.append(item)
# key names now use the new testMap8 encoding
keyname = "unknown" keyname = "unknown"
for name in names: for name in names:
if encodeHash(name,charMap5) == keyhash: if encodeHash(name,testMap8) == keyhash:
keyname = name keyname = name
break break
if keyname == "unknown":
keyname = keyhash
# the charMap5 encoded contents data has had a length # the testMap8 encoded contents data has had a length
# of chars (always odd) cut off of the front and moved # of chars (always odd) cut off of the front and moved
# to the end to prevent decoding using charMap5 from # to the end to prevent decoding using testMap8 from
# working properly, and thereby preventing the ensuing # working properly, and thereby preventing the ensuing
# CryptUnprotectData call from succeeding. # CryptUnprotectData call from succeeding.
# The offset into the charMap5 encoded contents seems to be: # The offset into the testMap8 encoded contents seems to be:
# len(contents) - largest prime number less than or equal to int(len(content)/3) # len(contents)-largest prime number <= int(len(content)/3)
# (in other words split "about" 2/3rds of the way through) # (in other words split "about" 2/3rds of the way through)
# move first offsets chars to end to align for decode by charMap5 # move first offsets chars to end to align for decode by testMap8
# by moving noffset chars from the start of the
# string to the end of the string
encdata = "".join(edlst) encdata = "".join(edlst)
contlen = len(encdata) contlen = len(encdata)
noffset = contlen - primes(int(contlen/3))[-1] noffset = contlen - primes(int(contlen/3))[-1]
# now properly split and recombine
# by moving noffset chars from the start of the
# string to the end of the string
pfx = encdata[0:noffset] pfx = encdata[0:noffset]
encdata = encdata[noffset:] encdata = encdata[noffset:]
encdata = encdata + pfx encdata = encdata + pfx
# decode using Map5 to get the CryptProtect Data # decode using new testMap8 to get the original CryptProtect Data
encryptedValue = decode(encdata,charMap5) encryptedValue = decode(encdata,testMap8)
DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
DB[keyname] = cleartext
cnt = cnt + 1 cnt = cnt + 1
if cnt == 0: if cnt == 0:
DB = None DB = None
return DB return DB

View File

@@ -54,8 +54,14 @@
# 0.30 - Modified interface slightly to work better with new calibre plugin style # 0.30 - Modified interface slightly to work better with new calibre plugin style
# 0.31 - The multibyte encrytion info is true for version 7 files too. # 0.31 - The multibyte encrytion info is true for version 7 files too.
# 0.32 - Added support for "Print Replica" Kindle ebooks # 0.32 - Added support for "Print Replica" Kindle ebooks
# 0.33 - Performance improvements for large files (concatenation)
# 0.34 - Performance improvements in decryption (libalfcrypto)
# 0.35 - add interface to get mobi_version
# 0.36 - fixed problem with TEXtREAd and getBookTitle interface
# 0.37 - Fixed double announcement for stand-alone operation
__version__ = '0.32'
__version__ = '0.37'
import sys import sys
@@ -72,6 +78,7 @@ sys.stdout=Unbuffered(sys.stdout)
import os import os
import struct import struct
import binascii import binascii
from alfcrypto import Pukall_Cipher
class DrmException(Exception): class DrmException(Exception):
pass pass
@@ -83,36 +90,37 @@ class DrmException(Exception):
# Implementation of Pukall Cipher 1 # Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True): def PC1(key, src, decryption=True):
sum1 = 0; return Pukall_Cipher().PC1(key,src,decryption)
sum2 = 0; # sum1 = 0;
keyXorVal = 0; # sum2 = 0;
if len(key)!=16: # keyXorVal = 0;
print "Bad key length!" # if len(key)!=16:
return None # print "Bad key length!"
wkey = [] # return None
for i in xrange(8): # wkey = []
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) # for i in xrange(8):
dst = "" # wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
for i in xrange(len(src)): # dst = ""
temp1 = 0; # for i in xrange(len(src)):
byteXorVal = 0; # temp1 = 0;
for j in xrange(8): # byteXorVal = 0;
temp1 ^= wkey[j] # for j in xrange(8):
sum2 = (sum2+j)*20021 + sum1 # temp1 ^= wkey[j]
sum1 = (temp1*346)&0xFFFF # sum2 = (sum2+j)*20021 + sum1
sum2 = (sum2+sum1)&0xFFFF # sum1 = (temp1*346)&0xFFFF
temp1 = (temp1*20021+1)&0xFFFF # sum2 = (sum2+sum1)&0xFFFF
byteXorVal ^= temp1 ^ sum2 # temp1 = (temp1*20021+1)&0xFFFF
curByte = ord(src[i]) # byteXorVal ^= temp1 ^ sum2
if not decryption: # curByte = ord(src[i])
keyXorVal = curByte * 257; # if not decryption:
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF # keyXorVal = curByte * 257;
if decryption: # curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
keyXorVal = curByte * 257; # if decryption:
for j in xrange(8): # keyXorVal = curByte * 257;
wkey[j] ^= keyXorVal; # for j in xrange(8):
dst+=chr(curByte) # wkey[j] ^= keyXorVal;
return dst # dst+=chr(curByte)
# return dst
def checksumPid(s): def checksumPid(s):
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
@@ -163,9 +171,10 @@ class MobiBook:
off = self.sections[section][0] off = self.sections[section][0]
return self.data_file[off:endoff] return self.data_file[off:endoff]
def __init__(self, infile): def __init__(self, infile, announce = True):
print ('MobiDeDrm v%(__version__)s. ' if announce:
'Copyright 2008-2011 The Dark Reverser et al.' % globals()) print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
# initial sanity check on file # initial sanity check on file
self.data_file = file(infile, 'rb').read() self.data_file = file(infile, 'rb').read()
@@ -193,6 +202,7 @@ class MobiBook:
print "Book has format: ", self.magic print "Book has format: ", self.magic
self.extra_data_flags = 0 self.extra_data_flags = 0
self.mobi_length = 0 self.mobi_length = 0
self.mobi_codepage = 1252
self.mobi_version = -1 self.mobi_version = -1
self.meta_array = {} self.meta_array = {}
return return
@@ -243,18 +253,19 @@ class MobiBook:
65001 : 'utf-8', 65001 : 'utf-8',
} }
title = '' title = ''
if 503 in self.meta_array: codec = 'windows-1252'
title = self.meta_array[503] if self.magic == 'BOOKMOBI':
else : if 503 in self.meta_array:
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c]) title = self.meta_array[503]
tend = toff + tlen else:
title = self.sect[toff:tend] toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
tend = toff + tlen
title = self.sect[toff:tend]
if self.mobi_codepage in codec_map.keys():
codec = codec_map[self.mobi_codepage]
if title == '': if title == '':
title = self.header[:32] title = self.header[:32]
title = title.split("\0")[0] title = title.split("\0")[0]
codec = 'windows-1252'
if self.mobi_codepage in codec_map.keys():
codec = codec_map[self.mobi_codepage]
return unicode(title, codec).encode('utf-8') return unicode(title, codec).encode('utf-8')
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
@@ -320,6 +331,9 @@ class MobiBook:
def getMobiFile(self, outpath): def getMobiFile(self, outpath):
file(outpath,'wb').write(self.mobi_data) file(outpath,'wb').write(self.mobi_data)
def getMobiVersion(self):
return self.mobi_version
def getPrintReplica(self): def getPrintReplica(self):
return self.print_replica return self.print_replica
@@ -367,7 +381,7 @@ class MobiBook:
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.") raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key: if not found_key:
raise DrmException("No key found. Most likely the correct PID has not been given.") raise DrmException("No key found in " + str(len(goodpids)) + " keys tried. Please report this failure for help.")
# kill the drm keys # kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr) self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers # kill the drm pointers
@@ -383,7 +397,8 @@ class MobiBook:
# decrypt sections # decrypt sections
print "Decrypting. Please wait . . .", print "Decrypting. Please wait . . .",
self.mobi_data = self.data_file[:self.sections[1][0]] mobidataList = []
mobidataList.append(self.data_file[:self.sections[1][0]])
for i in xrange(1, self.records+1): for i in xrange(1, self.records+1):
data = self.loadSection(i) data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
@@ -393,34 +408,35 @@ class MobiBook:
decoded_data = PC1(found_key, data[0:len(data) - extra_size]) decoded_data = PC1(found_key, data[0:len(data) - extra_size])
if i==1: if i==1:
self.print_replica = (decoded_data[0:4] == '%MOP') self.print_replica = (decoded_data[0:4] == '%MOP')
self.mobi_data += decoded_data mobidataList.append(decoded_data)
if extra_size > 0: if extra_size > 0:
self.mobi_data += data[-extra_size:] mobidataList.append(data[-extra_size:])
if self.num_sections > self.records+1: if self.num_sections > self.records+1:
self.mobi_data += self.data_file[self.sections[self.records+1][0]:] mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
self.mobi_data = "".join(mobidataList)
print "done" print "done"
return return
def getUnencryptedBook(infile,pid): def getUnencryptedBook(infile,pid,announce=True):
if not os.path.isfile(infile): if not os.path.isfile(infile):
raise DrmException('Input File Not Found') raise DrmException('Input File Not Found')
book = MobiBook(infile) book = MobiBook(infile,announce)
book.processBook([pid]) book.processBook([pid])
return book.mobi_data return book.mobi_data
def getUnencryptedBookWithList(infile,pidlist): def getUnencryptedBookWithList(infile,pidlist,announce=True):
if not os.path.isfile(infile): if not os.path.isfile(infile):
raise DrmException('Input File Not Found') raise DrmException('Input File Not Found')
book = MobiBook(infile) book = MobiBook(infile, announce)
book.processBook(pidlist) book.processBook(pidlist)
return book.mobi_data return book.mobi_data
def main(argv=sys.argv): def main(argv=sys.argv):
print ('MobiDeDrm v%(__version__)s. ' print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2011 The Dark Reverser et al.' % globals()) 'Copyright 2008-2012 The Dark Reverser et al.' % globals())
if len(argv)<3 or len(argv)>4: if len(argv)<3 or len(argv)>4:
print "Removes protection from Kindle/Mobipocket and Kindle/Print Replica ebooks" print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
print "Usage:" print "Usage:"
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0] print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
return 1 return 1
@@ -432,7 +448,7 @@ def main(argv=sys.argv):
else: else:
pidlist = {} pidlist = {}
try: try:
stripped_file = getUnencryptedBookWithList(infile, pidlist) stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
file(outfile, 'wb').write(stripped_file) file(outfile, 'wb').write(stripped_file)
except DrmException, e: except DrmException, e:
print "Error: %s" % e print "Error: %s" % e

View File

@@ -0,0 +1,47 @@
{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\paperw11900\paperh16840\margl1440\margr1440\vieww12360\viewh16560\viewkind0
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\qc\pardirnatural
\f0\b\fs24 \cf0 DeDRM ReadMe
\b0 \
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural
\cf0 \
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\qj\pardirnatural
\cf0 DeDRM is an application that packs all of the python drm-removal software into one easy to use program that remembers preferences and settings.\
It works without manual configuration with Kindle for Mac ebooks and Adobe Adept ePub and PDF ebooks.\
\
To remove the DRM of Kindle ebooks from eInk Kindles, eReader pdb ebooks, Barnes and Noble ePubs, or Mobipocket ebooks, you must first run DeDRM application (by double-clicking it) and set some additional Preferences including:\
\
Kindle (not Kindle Fire): 16 digit Serial Number\
Barnes & Noble ePub: Name and CC number or key file (bnepubkey.b64)\
eReader Social DRM: Name and last 8 digits of CC number\
Mobipocket: 10 digit PID\
\
A final preference is the destination folder for the DRM-free copies of your ebooks that the application produces. This can be either the same folder as the original ebook, or a folder of your choice.\
\
Once these preferences have been set, you can drag and drop ebooks (or folders of ebooks) onto the DeDRM droplet to remove the DRM.\
\
This program requires Mac OS X 10.5 or above. \
\
\
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural
\b \cf0 Installation
\b0 \
Drag the DeDRM application from from tools_v5.3\\DeDRM_Applications\\Macintosh (the location of this ReadMe) to your Applications folder, or anywhere else you find convenient.\
\
\
\b Use
\b0 \
1. To set the preferences, double-click the application and follow the instructions in the dialogs.\
2. Drag & Drop DRMed ebooks or folders of DRMed ebooks onto the application icon when it is not running.\
\
\
\b Troubleshooting\
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural
\b0 \cf0 A log is created on your desktop containing detailed information from all the scripts. If you have any problems decrypting your ebooks, quote the contents of this log in a comment at Apprentice Alf's blog.}

View File

@@ -24,17 +24,17 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>droplet</string> <string>droplet</string>
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string>DeDRM 3.1, Written 20102011 by Apprentice Alf and others.</string> <string>DeDRM 5.3.1, Written 20102012 by Apprentice Alf and others.</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>droplet</string> <string>DeDRM</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>DeDRM 3.1</string> <string>DeDRM 5.3.1</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.1</string> <string>5.3.1</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>dplt</string> <string>dplt</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
@@ -43,16 +43,12 @@
<true/> <true/>
<key>WindowState</key> <key>WindowState</key>
<dict> <dict>
<key>dividerCollapsed</key>
<false/>
<key>eventLogLevel</key>
<integer>-1</integer>
<key>name</key> <key>name</key>
<string>ScriptWindowState</string> <string>ScriptWindowState</string>
<key>positionOfDivider</key> <key>positionOfDivider</key>
<real>460</real> <real>554</real>
<key>savedFrame</key> <key>savedFrame</key>
<string>39 106 1316 746 0 0 1440 878 </string> <string>1691 92 922 818 1440 0 1920 1080 </string>
<key>selectedTabView</key> <key>selectedTabView</key>
<string>event log</string> <string>event log</string>
</dict> </dict>

View File

@@ -2,10 +2,16 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>BuildMachineOSBuild</key>
<string>10K549</string>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>English</string> <string>English</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>DeDRM Progress</string> <string>DeDRM Progress</string>
<key>CFBundleGetInfoString</key>
<string>DeDRM Progress 1.1, Written 2010, 2012 by Apprentice Alf and others.</string>
<key>CFBundleIconFile</key>
<string>DeDRM Progress</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.apprenticealf.DeDRMProgress</string> <string>com.apprenticealf.DeDRMProgress</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
@@ -15,11 +21,25 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>1.1</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>DTCompiler</key>
<string></string>
<key>DTPlatformBuild</key>
<string>10M2518</string>
<key>DTPlatformVersion</key>
<string>PG</string>
<key>DTSDKBuild</key>
<string>9L31a</string>
<key>DTSDKName</key>
<string>macosx10.5</string>
<key>DTXcode</key>
<string>0400</string>
<key>DTXcodeBuild</key>
<string>10M2518</string>
<key>NSAppleScriptEnabled</key> <key>NSAppleScriptEnabled</key>
<string>YES</string> <string>YES</string>
<key>NSMainNibFile</key> <key>NSMainNibFile</key>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBClasses</key>
<array>
<dict>
<key>CLASS</key>
<string>FirstResponder</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSObject</string>
</dict>
</array>
<key>IBVersion</key>
<string>1</string>
</dict>
</plist>

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBFramework Version</key>
<string>680</string>
<key>IBLastKnownRelativeProjectPath</key>
<string>../Display Panel.xcodeproj</string>
<key>IBOldestOS</key>
<integer>5</integer>
<key>IBOpenObjects</key>
<array/>
<key>IBSystem Version</key>
<string>9L31a</string>
<key>targetFramework</key>
<string>IBCocoaFramework</string>
</dict>
</plist>

View File

@@ -0,0 +1,568 @@
#! /usr/bin/env python
"""
Routines for doing AES CBC in one file
Modified by some_updates to extract
and combine only those parts needed for AES CBC
into one simple to add python file
Original Version
Copyright (c) 2002 by Paul A. Lambert
Under:
CryptoPy Artisitic License Version 1.0
See the wonderful pure python package cryptopy-1.2.5
and read its LICENSE.txt for complete license details.
"""
class CryptoError(Exception):
""" Base class for crypto exceptions """
def __init__(self,errorMessage='Error!'):
self.message = errorMessage
def __str__(self):
return self.message
class InitCryptoError(CryptoError):
""" Crypto errors during algorithm initialization """
class BadKeySizeError(InitCryptoError):
""" Bad key size error """
class EncryptError(CryptoError):
""" Error in encryption processing """
class DecryptError(CryptoError):
""" Error in decryption processing """
class DecryptNotBlockAlignedError(DecryptError):
""" Error in decryption processing """
def xorS(a,b):
""" XOR two strings """
assert len(a)==len(b)
x = []
for i in range(len(a)):
x.append( chr(ord(a[i])^ord(b[i])))
return ''.join(x)
def xor(a,b):
""" XOR two strings """
x = []
for i in range(min(len(a),len(b))):
x.append( chr(ord(a[i])^ord(b[i])))
return ''.join(x)
"""
Base 'BlockCipher' and Pad classes for cipher instances.
BlockCipher supports automatic padding and type conversion. The BlockCipher
class was written to make the actual algorithm code more readable and
not for performance.
"""
class BlockCipher:
""" Block ciphers """
def __init__(self):
self.reset()
def reset(self):
self.resetEncrypt()
self.resetDecrypt()
def resetEncrypt(self):
self.encryptBlockCount = 0
self.bytesToEncrypt = ''
def resetDecrypt(self):
self.decryptBlockCount = 0
self.bytesToDecrypt = ''
def encrypt(self, plainText, more = None):
""" Encrypt a string and return a binary string """
self.bytesToEncrypt += plainText # append plainText to any bytes from prior encrypt
numBlocks, numExtraBytes = divmod(len(self.bytesToEncrypt), self.blockSize)
cipherText = ''
for i in range(numBlocks):
bStart = i*self.blockSize
ctBlock = self.encryptBlock(self.bytesToEncrypt[bStart:bStart+self.blockSize])
self.encryptBlockCount += 1
cipherText += ctBlock
if numExtraBytes > 0: # save any bytes that are not block aligned
self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
else:
self.bytesToEncrypt = ''
if more == None: # no more data expected from caller
finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize)
if len(finalBytes) > 0:
ctBlock = self.encryptBlock(finalBytes)
self.encryptBlockCount += 1
cipherText += ctBlock
self.resetEncrypt()
return cipherText
def decrypt(self, cipherText, more = None):
""" Decrypt a string and return a string """
self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt
numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
if more == None: # no more calls to decrypt, should have all the data
if numExtraBytes != 0:
raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt'
# hold back some bytes in case last decrypt has zero len
if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
numBlocks -= 1
numExtraBytes = self.blockSize
plainText = ''
for i in range(numBlocks):
bStart = i*self.blockSize
ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize])
self.decryptBlockCount += 1
plainText += ptBlock
if numExtraBytes > 0: # save any bytes that are not block aligned
self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
else:
self.bytesToEncrypt = ''
if more == None: # last decrypt remove padding
plainText = self.padding.removePad(plainText, self.blockSize)
self.resetDecrypt()
return plainText
class Pad:
def __init__(self):
pass # eventually could put in calculation of min and max size extension
class padWithPadLen(Pad):
""" Pad a binary string with the length of the padding """
def addPad(self, extraBytes, blockSize):
""" Add padding to a binary string to make it an even multiple
of the block size """
blocks, numExtraBytes = divmod(len(extraBytes), blockSize)
padLength = blockSize - numExtraBytes
return extraBytes + padLength*chr(padLength)
def removePad(self, paddedBinaryString, blockSize):
""" Remove padding from a binary string """
if not(0<len(paddedBinaryString)):
raise DecryptNotBlockAlignedError, 'Expected More Data'
return paddedBinaryString[:-ord(paddedBinaryString[-1])]
class noPadding(Pad):
""" No padding. Use this to get ECB behavior from encrypt/decrypt """
def addPad(self, extraBytes, blockSize):
""" Add no padding """
return extraBytes
def removePad(self, paddedBinaryString, blockSize):
""" Remove no padding """
return paddedBinaryString
"""
Rijndael encryption algorithm
This byte oriented implementation is intended to closely
match FIPS specification for readability. It is not implemented
for performance.
"""
class Rijndael(BlockCipher):
""" Rijndael encryption algorithm """
def __init__(self, key = None, padding = padWithPadLen(), keySize=16, blockSize=16 ):
self.name = 'RIJNDAEL'
self.keySize = keySize
self.strength = keySize*8
self.blockSize = blockSize # blockSize is in bytes
self.padding = padding # change default to noPadding() to get normal ECB behavior
assert( keySize%4==0 and NrTable[4].has_key(keySize/4)),'key size must be 16,20,24,29 or 32 bytes'
assert( blockSize%4==0 and NrTable.has_key(blockSize/4)), 'block size must be 16,20,24,29 or 32 bytes'
self.Nb = self.blockSize/4 # Nb is number of columns of 32 bit words
self.Nk = keySize/4 # Nk is the key length in 32-bit words
self.Nr = NrTable[self.Nb][self.Nk] # The number of rounds (Nr) is a function of
# the block (Nb) and key (Nk) sizes.
if key != None:
self.setKey(key)
def setKey(self, key):
""" Set a key and generate the expanded key """
assert( len(key) == (self.Nk*4) ), 'Key length must be same as keySize parameter'
self.__expandedKey = keyExpansion(self, key)
self.reset() # BlockCipher.reset()
def encryptBlock(self, plainTextBlock):
""" Encrypt a block, plainTextBlock must be a array of bytes [Nb by 4] """
self.state = self._toBlock(plainTextBlock)
AddRoundKey(self, self.__expandedKey[0:self.Nb])
for round in range(1,self.Nr): #for round = 1 step 1 to Nr
SubBytes(self)
ShiftRows(self)
MixColumns(self)
AddRoundKey(self, self.__expandedKey[round*self.Nb:(round+1)*self.Nb])
SubBytes(self)
ShiftRows(self)
AddRoundKey(self, self.__expandedKey[self.Nr*self.Nb:(self.Nr+1)*self.Nb])
return self._toBString(self.state)
def decryptBlock(self, encryptedBlock):
""" decrypt a block (array of bytes) """
self.state = self._toBlock(encryptedBlock)
AddRoundKey(self, self.__expandedKey[self.Nr*self.Nb:(self.Nr+1)*self.Nb])
for round in range(self.Nr-1,0,-1):
InvShiftRows(self)
InvSubBytes(self)
AddRoundKey(self, self.__expandedKey[round*self.Nb:(round+1)*self.Nb])
InvMixColumns(self)
InvShiftRows(self)
InvSubBytes(self)
AddRoundKey(self, self.__expandedKey[0:self.Nb])
return self._toBString(self.state)
def _toBlock(self, bs):
""" Convert binary string to array of bytes, state[col][row]"""
assert ( len(bs) == 4*self.Nb ), 'Rijndarl blocks must be of size blockSize'
return [[ord(bs[4*i]),ord(bs[4*i+1]),ord(bs[4*i+2]),ord(bs[4*i+3])] for i in range(self.Nb)]
def _toBString(self, block):
""" Convert block (array of bytes) to binary string """
l = []
for col in block:
for rowElement in col:
l.append(chr(rowElement))
return ''.join(l)
#-------------------------------------
""" Number of rounds Nr = NrTable[Nb][Nk]
Nb Nk=4 Nk=5 Nk=6 Nk=7 Nk=8
------------------------------------- """
NrTable = {4: {4:10, 5:11, 6:12, 7:13, 8:14},
5: {4:11, 5:11, 6:12, 7:13, 8:14},
6: {4:12, 5:12, 6:12, 7:13, 8:14},
7: {4:13, 5:13, 6:13, 7:13, 8:14},
8: {4:14, 5:14, 6:14, 7:14, 8:14}}
#-------------------------------------
def keyExpansion(algInstance, keyString):
""" Expand a string of size keySize into a larger array """
Nk, Nb, Nr = algInstance.Nk, algInstance.Nb, algInstance.Nr # for readability
key = [ord(byte) for byte in keyString] # convert string to list
w = [[key[4*i],key[4*i+1],key[4*i+2],key[4*i+3]] for i in range(Nk)]
for i in range(Nk,Nb*(Nr+1)):
temp = w[i-1] # a four byte column
if (i%Nk) == 0 :
temp = temp[1:]+[temp[0]] # RotWord(temp)
temp = [ Sbox[byte] for byte in temp ]
temp[0] ^= Rcon[i/Nk]
elif Nk > 6 and i%Nk == 4 :
temp = [ Sbox[byte] for byte in temp ] # SubWord(temp)
w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] )
return w
Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!!
0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,
0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91)
#-------------------------------------
def AddRoundKey(algInstance, keyBlock):
""" XOR the algorithm state with a block of key material """
for column in range(algInstance.Nb):
for row in range(4):
algInstance.state[column][row] ^= keyBlock[column][row]
#-------------------------------------
def SubBytes(algInstance):
for column in range(algInstance.Nb):
for row in range(4):
algInstance.state[column][row] = Sbox[algInstance.state[column][row]]
def InvSubBytes(algInstance):
for column in range(algInstance.Nb):
for row in range(4):
algInstance.state[column][row] = InvSbox[algInstance.state[column][row]]
Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,
0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,
0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,
0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,
0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,
0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,
0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,
0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,
0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,
0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,
0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,
0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,
0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,
0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,
0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,
0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,
0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16)
InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,
0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,
0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,
0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,
0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,
0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,
0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,
0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,
0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,
0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,
0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,
0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,
0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,
0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,
0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,
0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,
0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d)
#-------------------------------------
""" For each block size (Nb), the ShiftRow operation shifts row i
by the amount Ci. Note that row 0 is not shifted.
Nb C1 C2 C3
------------------- """
shiftOffset = { 4 : ( 0, 1, 2, 3),
5 : ( 0, 1, 2, 3),
6 : ( 0, 1, 2, 3),
7 : ( 0, 1, 2, 4),
8 : ( 0, 1, 3, 4) }
def ShiftRows(algInstance):
tmp = [0]*algInstance.Nb # list of size Nb
for r in range(1,4): # row 0 reamains unchanged and can be skipped
for c in range(algInstance.Nb):
tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r]
for c in range(algInstance.Nb):
algInstance.state[c][r] = tmp[c]
def InvShiftRows(algInstance):
tmp = [0]*algInstance.Nb # list of size Nb
for r in range(1,4): # row 0 reamains unchanged and can be skipped
for c in range(algInstance.Nb):
tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r]
for c in range(algInstance.Nb):
algInstance.state[c][r] = tmp[c]
#-------------------------------------
def MixColumns(a):
Sprime = [0,0,0,0]
for j in range(a.Nb): # for each column
Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3])
Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3])
Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3])
Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3])
for i in range(4):
a.state[j][i] = Sprime[i]
def InvMixColumns(a):
""" Mix the four bytes of every column in a linear way
This is the opposite operation of Mixcolumn """
Sprime = [0,0,0,0]
for j in range(a.Nb): # for each column
Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3])
Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3])
Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3])
Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3])
for i in range(4):
a.state[j][i] = Sprime[i]
#-------------------------------------
def mul(a, b):
""" Multiply two elements of GF(2^m)
needed for MixColumn and InvMixColumn """
if (a !=0 and b!=0):
return Alogtable[(Logtable[a] + Logtable[b])%255]
else:
return 0
Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3,
100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193,
125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120,
101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142,
150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56,
102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16,
126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186,
43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87,
175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232,
44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160,
127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183,
204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157,
151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209,
83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171,
68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165,
103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7)
Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53,
95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170,
229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49,
83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205,
76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136,
131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154,
181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163,
254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160,
251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65,
195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117,
159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128,
155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84,
252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202,
69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14,
18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23,
57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1)
"""
AES Encryption Algorithm
The AES algorithm is just Rijndael algorithm restricted to the default
blockSize of 128 bits.
"""
class AES(Rijndael):
""" The AES algorithm is the Rijndael block cipher restricted to block
sizes of 128 bits and key sizes of 128, 192 or 256 bits
"""
def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
""" Initialize AES, keySize is in bytes """
if not (keySize == 16 or keySize == 24 or keySize == 32) :
raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes'
Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
self.name = 'AES'
"""
CBC mode of encryption for block ciphers.
This algorithm mode wraps any BlockCipher to make a
Cipher Block Chaining mode.
"""
from random import Random # should change to crypto.random!!!
class CBC(BlockCipher):
""" The CBC class wraps block ciphers to make cipher block chaining (CBC) mode
algorithms. The initialization (IV) is automatic if set to None. Padding
is also automatic based on the Pad class used to initialize the algorithm
"""
def __init__(self, blockCipherInstance, padding = padWithPadLen()):
""" CBC algorithms are created by initializing with a BlockCipher instance """
self.baseCipher = blockCipherInstance
self.name = self.baseCipher.name + '_CBC'
self.blockSize = self.baseCipher.blockSize
self.keySize = self.baseCipher.keySize
self.padding = padding
self.baseCipher.padding = noPadding() # baseCipher should NOT pad!!
self.r = Random() # for IV generation, currently uses
# mediocre standard distro version <----------------
import time
newSeed = time.ctime()+str(self.r) # seed with instance location
self.r.seed(newSeed) # to make unique
self.reset()
def setKey(self, key):
self.baseCipher.setKey(key)
# Overload to reset both CBC state and the wrapped baseCipher
def resetEncrypt(self):
BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class)
self.baseCipher.resetEncrypt() # reset base cipher encrypt state
def resetDecrypt(self):
BlockCipher.resetDecrypt(self) # reset CBC state (super class)
self.baseCipher.resetDecrypt() # reset base cipher decrypt state
def encrypt(self, plainText, iv=None, more=None):
""" CBC encryption - overloads baseCipher to allow optional explicit IV
when iv=None, iv is auto generated!
"""
if self.encryptBlockCount == 0:
self.iv = iv
else:
assert(iv==None), 'IV used only on first call to encrypt'
return BlockCipher.encrypt(self,plainText, more=more)
def decrypt(self, cipherText, iv=None, more=None):
""" CBC decryption - overloads baseCipher to allow optional explicit IV
when iv=None, iv is auto generated!
"""
if self.decryptBlockCount == 0:
self.iv = iv
else:
assert(iv==None), 'IV used only on first call to decrypt'
return BlockCipher.decrypt(self, cipherText, more=more)
def encryptBlock(self, plainTextBlock):
""" CBC block encryption, IV is set with 'encrypt' """
auto_IV = ''
if self.encryptBlockCount == 0:
if self.iv == None:
# generate IV and use
self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)])
self.prior_encr_CT_block = self.iv
auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic
else: # application provided IV
assert(len(self.iv) == self.blockSize ),'IV must be same length as block'
self.prior_encr_CT_block = self.iv
""" encrypt the prior CT XORed with the PT """
ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) )
self.prior_encr_CT_block = ct
return auto_IV+ct
def decryptBlock(self, encryptedBlock):
""" Decrypt a single block """
if self.decryptBlockCount == 0: # first call, process IV
if self.iv == None: # auto decrypt IV?
self.prior_CT_block = encryptedBlock
return ''
else:
assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption"
self.prior_CT_block = self.iv
dct = self.baseCipher.decryptBlock(encryptedBlock)
""" XOR the prior decrypted CT with the prior CT """
dct_XOR_priorCT = xor( self.prior_CT_block, dct )
self.prior_CT_block = encryptedBlock
return dct_XOR_priorCT
"""
AES_CBC Encryption Algorithm
"""
class AES_CBC(CBC):
""" AES encryption in CBC feedback mode """
def __init__(self, key=None, padding=padWithPadLen(), keySize=16):
CBC.__init__( self, AES(key, noPadding(), keySize), padding)
self.name = 'AES_CBC'

View File

@@ -0,0 +1,290 @@
#! /usr/bin/env python
import sys, os
import hmac
from struct import pack
import hashlib
# interface to needed routines libalfcrypto
def _load_libalfcrypto():
import ctypes
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
Structure, c_ulong, create_string_buffer, addressof, string_at, cast, sizeof
pointer_size = ctypes.sizeof(ctypes.c_voidp)
name_of_lib = None
if sys.platform.startswith('darwin'):
name_of_lib = 'libalfcrypto.dylib'
elif sys.platform.startswith('win'):
if pointer_size == 4:
name_of_lib = 'alfcrypto.dll'
else:
name_of_lib = 'alfcrypto64.dll'
else:
if pointer_size == 4:
name_of_lib = 'libalfcrypto32.so'
else:
name_of_lib = 'libalfcrypto64.so'
libalfcrypto = sys.path[0] + os.sep + name_of_lib
if not os.path.isfile(libalfcrypto):
raise Exception('libalfcrypto not found')
libalfcrypto = CDLL(libalfcrypto)
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
def F(restype, name, argtypes):
func = getattr(libalfcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
# aes cbc decryption
#
# struct aes_key_st {
# unsigned long rd_key[4 *(AES_MAXNR + 1)];
# int rounds;
# };
#
# typedef struct aes_key_st AES_KEY;
#
# int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
#
#
# void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
# const unsigned long length, const AES_KEY *key,
# unsigned char *ivec, const int enc);
AES_MAXNR = 14
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, c_int])
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
# Pukall 1 Cipher
# unsigned char *PC1(const unsigned char *key, unsigned int klen, const unsigned char *src,
# unsigned char *dest, unsigned int len, int decryption);
PC1 = F(c_char_p, 'PC1', [c_char_p, c_ulong, c_char_p, c_char_p, c_ulong, c_ulong])
# Topaz Encryption
# typedef struct _TpzCtx {
# unsigned int v[2];
# } TpzCtx;
#
# void topazCryptoInit(TpzCtx *ctx, const unsigned char *key, int klen);
# void topazCryptoDecrypt(const TpzCtx *ctx, const unsigned char *in, unsigned char *out, int len);
class TPZ_CTX(Structure):
_fields_ = [('v', c_long * 2)]
TPZ_CTX_p = POINTER(TPZ_CTX)
topazCryptoInit = F(None, 'topazCryptoInit', [TPZ_CTX_p, c_char_p, c_ulong])
topazCryptoDecrypt = F(None, 'topazCryptoDecrypt', [TPZ_CTX_p, c_char_p, c_char_p, c_ulong])
class AES_CBC(object):
def __init__(self):
self._blocksize = 0
self._keyctx = None
self._iv = 0
def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise Exception('AES CBC improper key used')
return
keyctx = self._keyctx = AES_KEY()
self._iv = iv
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0:
raise Exception('Failed to initialize AES CBC key')
def decrypt(self, data):
out = create_string_buffer(len(data))
mutable_iv = create_string_buffer(self._iv, len(self._iv))
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, mutable_iv, 0)
if rv == 0:
raise Exception('AES CBC decryption failed')
return out.raw
class Pukall_Cipher(object):
def __init__(self):
self.key = None
def PC1(self, key, src, decryption=True):
self.key = key
out = create_string_buffer(len(src))
de = 0
if decryption:
de = 1
rv = PC1(key, len(key), src, out, len(src), de)
return out.raw
class Topaz_Cipher(object):
def __init__(self):
self._ctx = None
def ctx_init(self, key):
tpz_ctx = self._ctx = TPZ_CTX()
topazCryptoInit(tpz_ctx, key, len(key))
return tpz_ctx
def decrypt(self, data, ctx=None):
if ctx == None:
ctx = self._ctx
out = create_string_buffer(len(data))
topazCryptoDecrypt(ctx, data, out, len(data))
return out.raw
print "Using Library AlfCrypto DLL/DYLIB/SO"
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
def _load_python_alfcrypto():
import aescbc
class Pukall_Cipher(object):
def __init__(self):
self.key = None
def PC1(self, key, src, decryption=True):
sum1 = 0;
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
print "Bad key length!"
return None
wkey = []
for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
dst = ""
for i in xrange(len(src)):
temp1 = 0;
byteXorVal = 0;
for j in xrange(8):
temp1 ^= wkey[j]
sum2 = (sum2+j)*20021 + sum1
sum1 = (temp1*346)&0xFFFF
sum2 = (sum2+sum1)&0xFFFF
temp1 = (temp1*20021+1)&0xFFFF
byteXorVal ^= temp1 ^ sum2
curByte = ord(src[i])
if not decryption:
keyXorVal = curByte * 257;
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
if decryption:
keyXorVal = curByte * 257;
for j in xrange(8):
wkey[j] ^= keyXorVal;
dst+=chr(curByte)
return dst
class Topaz_Cipher(object):
def __init__(self):
self._ctx = None
def ctx_init(self, key):
ctx1 = 0x0CAFFE19E
for keyChar in key:
keyByte = ord(keyChar)
ctx2 = ctx1
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
self._ctx = [ctx1, ctx2]
return [ctx1,ctx2]
def decrypt(self, data, ctx=None):
if ctx == None:
ctx = self._ctx
ctx1 = ctx[0]
ctx2 = ctx[1]
plainText = ""
for dataChar in data:
dataByte = ord(dataChar)
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
ctx2 = ctx1
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
plainText += chr(m)
return plainText
class AES_CBC(object):
def __init__(self):
self._key = None
self._iv = None
self.aes = None
def set_decrypt_key(self, userkey, iv):
self._key = userkey
self._iv = iv
self.aes = aescbc.AES_CBC(userkey, aescbc.noPadding(), len(userkey))
def decrypt(self, data):
iv = self._iv
cleartext = self.aes.decrypt(iv + data)
return cleartext
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
def _load_crypto():
AES_CBC = Pukall_Cipher = Topaz_Cipher = None
cryptolist = (_load_libalfcrypto, _load_python_alfcrypto)
for loader in cryptolist:
try:
AES_CBC, Pukall_Cipher, Topaz_Cipher = loader()
break
except (ImportError, Exception):
pass
return AES_CBC, Pukall_Cipher, Topaz_Cipher
AES_CBC, Pukall_Cipher, Topaz_Cipher = _load_crypto()
class KeyIVGen(object):
# this only exists in openssl so we will use pure python implementation instead
# PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
# [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
def pbkdf2(self, passwd, salt, iter, keylen):
def xorstr( a, b ):
if len(a) != len(b):
raise Exception("xorstr(): lengths differ")
return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b)))
def prf( h, data ):
hm = h.copy()
hm.update( data )
return hm.digest()
def pbkdf2_F( h, salt, itercount, blocknum ):
U = prf( h, salt + pack('>i',blocknum ) )
T = U
for i in range(2, itercount+1):
U = prf( h, U )
T = xorstr( T, U )
return T
sha = hashlib.sha1
digest_size = sha().digest_size
# l - number of output blocks to produce
l = keylen / digest_size
if keylen % digest_size != 0:
l += 1
h = hmac.new( passwd, None, sha )
T = ""
for i in range(1, l+1):
T += pbkdf2_F( h, salt, iter, i )
return T[0: keylen]

View File

@@ -0,0 +1,899 @@
#! /usr/bin/python
"""
Comprehensive Mazama Book DRM with Topaz Cryptography V2.2
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdBHJ4CNc6DNFCw4MRCw4SWAK6
M8hYfnNEI0yQmn5Ti+W8biT7EatpauE/5jgQMPBmdNrDr1hbHyHBSP7xeC2qlRWC
B62UCxeu/fpfnvNHDN/wPWWH4jynZ2M6cdcnE5LQ+FfeKqZn7gnG2No1U9h7oOHx
y2/pHuYme7U1TsgSjwIDAQAB
-----END PUBLIC KEY-----
"""
from __future__ import with_statement
import csv
import sys
import os
import getopt
import zlib
from struct import pack
from struct import unpack
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
string_at, Structure, c_void_p, cast
import _winreg as winreg
import Tkinter
import Tkconstants
import tkMessageBox
import traceback
import hashlib
MAX_PATH = 255
kernel32 = windll.kernel32
advapi32 = windll.advapi32
crypt32 = windll.crypt32
global kindleDatabase
global bookFile
global bookPayloadOffset
global bookHeaderRecords
global bookMetadata
global bookKey
global command
#
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
#
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
#
# Exceptions for all the problems that might happen during the script
#
class CMBDTCError(Exception):
pass
class CMBDTCFatal(Exception):
pass
#
# Stolen stuff
#
class DataBlob(Structure):
_fields_ = [('cbData', c_uint),
('pbData', c_void_p)]
DataBlob_p = POINTER(DataBlob)
def GetSystemDirectory():
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
GetSystemDirectoryW.restype = c_uint
def GetSystemDirectory():
buffer = create_unicode_buffer(MAX_PATH + 1)
GetSystemDirectoryW(buffer, len(buffer))
return buffer.value
return GetSystemDirectory
GetSystemDirectory = GetSystemDirectory()
def GetVolumeSerialNumber():
GetVolumeInformationW = kernel32.GetVolumeInformationW
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
POINTER(c_uint), POINTER(c_uint),
POINTER(c_uint), c_wchar_p, c_uint]
GetVolumeInformationW.restype = c_uint
def GetVolumeSerialNumber(path):
vsn = c_uint(0)
GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
return vsn.value
return GetVolumeSerialNumber
GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetUserName():
GetUserNameW = advapi32.GetUserNameW
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
GetUserNameW.restype = c_uint
def GetUserName():
buffer = create_unicode_buffer(32)
size = c_uint(len(buffer))
while not GetUserNameW(buffer, byref(size)):
buffer = create_unicode_buffer(len(buffer) * 2)
size.value = len(buffer)
return buffer.value.encode('utf-16-le')[::2]
return GetUserName
GetUserName = GetUserName()
def CryptUnprotectData():
_CryptUnprotectData = crypt32.CryptUnprotectData
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
c_void_p, c_void_p, c_uint, DataBlob_p]
_CryptUnprotectData.restype = c_uint
def CryptUnprotectData(indata, entropy):
indatab = create_string_buffer(indata)
indata = DataBlob(len(indata), cast(indatab, c_void_p))
entropyb = create_string_buffer(entropy)
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
outdata = DataBlob()
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
None, None, 0, byref(outdata)):
raise CMBDTCFatal("Failed to Unprotect Data")
return string_at(outdata.pbData, outdata.cbData)
return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData()
#
# Returns the MD5 digest of "message"
#
def MD5(message):
ctx = hashlib.md5()
ctx.update(message)
return ctx.digest()
#
# Returns the MD5 digest of "message"
#
def SHA1(message):
ctx = hashlib.sha1()
ctx.update(message)
return ctx.digest()
#
# Open the book file at path
#
def openBook(path):
try:
return open(path,'rb')
except:
raise CMBDTCFatal("Could not open book file: " + path)
#
# Encode the bytes in data with the characters in map
#
def encode(data, map):
result = ""
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
R = value % len(map)
result += map[Q]
result += map[R]
return result
#
# Hash the bytes in data and then encode the digest with the characters in map
#
def encodeHash(data,map):
return encode(MD5(data),map)
#
# Decode the string in data with the characters in map. Returns the decoded bytes
#
def decode(data,map):
result = ""
for i in range (0,len(data),2):
high = map.find(data[i])
low = map.find(data[i+1])
value = (((high * 0x40) ^ 0x80) & 0xFF) + low
result += pack("B",value)
return result
#
# Locate and open the Kindle.info file (Hopefully in the way it is done in the Kindle application)
#
def openKindleInfo():
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
#
# Parse the Kindle.info file and return the records as a list of key-values
#
def parseKindleInfo():
DB = {}
infoReader = openKindleInfo()
infoReader.read(1)
data = infoReader.read()
items = data.split('{')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
#
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string. (Totally not optimal)
#
def findNameForHash(hash):
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
result = ""
for name in names:
if hash == encodeHash(name, charMap2):
result = name
break
return name
#
# Print all the records from the kindle.info file (option -i)
#
def printKindleInfo():
for record in kindleDatabase:
name = findNameForHash(record)
if name != "" :
print (name)
print ("--------------------------\n")
else :
print ("Unknown Record")
print getKindleInfoValueForHash(record)
print "\n"
#
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
#
def getKindleInfoValueForHash(hashedKey):
global kindleDatabase
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
return CryptUnprotectData(encryptedValue,"")
#
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
#
def getKindleInfoValueForKey(key):
return getKindleInfoValueForHash(encodeHash(key,charMap2))
#
# Get a 7 bit encoded number from the book file
#
def bookReadEncodedNumber():
flag = False
data = ord(bookFile.read(1))
if data == 0xFF:
flag = True
data = ord(bookFile.read(1))
if data >= 0x80:
datax = (data & 0x7F)
while data >= 0x80 :
data = ord(bookFile.read(1))
datax = (datax <<7) + (data & 0x7F)
data = datax
if flag:
data = -data
return data
#
# Encode a number in 7 bit format
#
def encodeNumber(number):
result = ""
negative = False
flag = 0
if number < 0 :
number = -number + 1
negative = True
while True:
byte = number & 0x7F
number = number >> 7
byte += flag
result += chr(byte)
flag = 0x80
if number == 0 :
if (byte == 0xFF and negative == False) :
result += chr(0x80)
break
if negative:
result += chr(0xFF)
return result[::-1]
#
# Get a length prefixed string from the file
#
def bookReadString():
stringLength = bookReadEncodedNumber()
return unpack(str(stringLength)+"s",bookFile.read(stringLength))[0]
#
# Returns a length prefixed string
#
def lengthPrefixString(data):
return encodeNumber(len(data))+data
#
# Read and return the data of one header record at the current book file position [[offset,compressedLength,decompressedLength],...]
#
def bookReadHeaderRecordData():
nbValues = bookReadEncodedNumber()
values = []
for i in range (0,nbValues):
values.append([bookReadEncodedNumber(),bookReadEncodedNumber(),bookReadEncodedNumber()])
return values
#
# Read and parse one header record at the current book file position and return the associated data [[offset,compressedLength,decompressedLength],...]
#
def parseTopazHeaderRecord():
if ord(bookFile.read(1)) != 0x63:
raise CMBDTCFatal("Parse Error : Invalid Header")
tag = bookReadString()
record = bookReadHeaderRecordData()
return [tag,record]
#
# Parse the header of a Topaz file, get all the header records and the offset for the payload
#
def parseTopazHeader():
global bookHeaderRecords
global bookPayloadOffset
magic = unpack("4s",bookFile.read(4))[0]
if magic != 'TPZ0':
raise CMBDTCFatal("Parse Error : Invalid Header, not a Topaz file")
nbRecords = bookReadEncodedNumber()
bookHeaderRecords = {}
for i in range (0,nbRecords):
result = parseTopazHeaderRecord()
bookHeaderRecords[result[0]] = result[1]
if ord(bookFile.read(1)) != 0x64 :
raise CMBDTCFatal("Parse Error : Invalid Header")
bookPayloadOffset = bookFile.tell()
#
# Get a record in the book payload, given its name and index. If necessary the record is decrypted. The record is not decompressed
#
def getBookPayloadRecord(name, index):
encrypted = False
try:
recordOffset = bookHeaderRecords[name][index][0]
except:
raise CMBDTCFatal("Parse Error : Invalid Record, record not found")
bookFile.seek(bookPayloadOffset + recordOffset)
tag = bookReadString()
if tag != name :
raise CMBDTCFatal("Parse Error : Invalid Record, record name doesn't match")
recordIndex = bookReadEncodedNumber()
if recordIndex < 0 :
encrypted = True
recordIndex = -recordIndex -1
if recordIndex != index :
raise CMBDTCFatal("Parse Error : Invalid Record, index doesn't match")
if bookHeaderRecords[name][index][2] != 0 :
record = bookFile.read(bookHeaderRecords[name][index][2])
else:
record = bookFile.read(bookHeaderRecords[name][index][1])
if encrypted:
ctx = topazCryptoInit(bookKey)
record = topazCryptoDecrypt(record,ctx)
return record
#
# Extract, decrypt and decompress a book record indicated by name and index and print it or save it in "filename"
#
def extractBookPayloadRecord(name, index, filename):
compressed = False
try:
compressed = bookHeaderRecords[name][index][2] != 0
record = getBookPayloadRecord(name,index)
except:
print("Could not find record")
if compressed:
try:
record = zlib.decompress(record)
except:
raise CMBDTCFatal("Could not decompress record")
if filename != "":
try:
file = open(filename,"wb")
file.write(record)
file.close()
except:
raise CMBDTCFatal("Could not write to destination file")
else:
print(record)
#
# return next record [key,value] from the book metadata from the current book position
#
def readMetadataRecord():
return [bookReadString(),bookReadString()]
#
# Parse the metadata record from the book payload and return a list of [key,values]
#
def parseMetadata():
global bookHeaderRecords
global bookPayloadAddress
global bookMetadata
bookMetadata = {}
bookFile.seek(bookPayloadOffset + bookHeaderRecords["metadata"][0][0])
tag = bookReadString()
if tag != "metadata" :
raise CMBDTCFatal("Parse Error : Record Names Don't Match")
flags = ord(bookFile.read(1))
nbRecords = ord(bookFile.read(1))
for i in range (0,nbRecords) :
record =readMetadataRecord()
bookMetadata[record[0]] = record[1]
#
# Returns two bit at offset from a bit field
#
def getTwoBitsFromBitField(bitField,offset):
byteNumber = offset // 4
bitPosition = 6 - 2*(offset % 4)
return ord(bitField[byteNumber]) >> bitPosition & 3
#
# Returns the six bits at offset from a bit field
#
def getSixBitsFromBitField(bitField,offset):
offset *= 3
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
return value
#
# 8 bits to six bits encoding from hash to generate PID string
#
def encodePID(hash):
global charMap3
PID = ""
for position in range (0,8):
PID += charMap3[getSixBitsFromBitField(hash,position)]
return PID
#
# Context initialisation for the Topaz Crypto
#
def topazCryptoInit(key):
ctx1 = 0x0CAFFE19E
for keyChar in key:
keyByte = ord(keyChar)
ctx2 = ctx1
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
return [ctx1,ctx2]
#
# decrypt data with the context prepared by topazCryptoInit()
#
def topazCryptoDecrypt(data, ctx):
ctx1 = ctx[0]
ctx2 = ctx[1]
plainText = ""
for dataChar in data:
dataByte = ord(dataChar)
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
ctx2 = ctx1
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
plainText += chr(m)
return plainText
#
# Decrypt a payload record with the PID
#
def decryptRecord(data,PID):
ctx = topazCryptoInit(PID)
return topazCryptoDecrypt(data, ctx)
#
# Try to decrypt a dkey record (contains the book PID)
#
def decryptDkeyRecord(data,PID):
record = decryptRecord(data,PID)
fields = unpack("3sB8sB8s3s",record)
if fields[0] != "PID" or fields[5] != "pid" :
raise CMBDTCError("Didn't find PID magic numbers in record")
elif fields[1] != 8 or fields[3] != 8 :
raise CMBDTCError("Record didn't contain correct length fields")
elif fields[2] != PID :
raise CMBDTCError("Record didn't contain PID")
return fields[4]
#
# Decrypt all the book's dkey records (contain the book PID)
#
def decryptDkeyRecords(data,PID):
nbKeyRecords = ord(data[0])
records = []
data = data[1:]
for i in range (0,nbKeyRecords):
length = ord(data[0])
try:
key = decryptDkeyRecord(data[1:length+1],PID)
records.append(key)
except CMBDTCError:
pass
data = data[1+length:]
return records
#
# Encryption table used to generate the device PID
#
def generatePidEncryptionTable() :
table = []
for counter1 in range (0,0x100):
value = counter1
for counter2 in range (0,8):
if (value & 1 == 0) :
value = value >> 1
else :
value = value >> 1
value = value ^ 0xEDB88320
table.append(value)
return table
#
# Seed value used to generate the device PID
#
def generatePidSeed(table,dsn) :
value = 0
for counter in range (0,4) :
index = (ord(dsn[counter]) ^ value) &0xFF
value = (value >> 8) ^ table[index]
return value
#
# Generate the device PID
#
def generateDevicePID(table,dsn,nbRoll):
seed = generatePidSeed(table,dsn)
pidAscii = ""
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
index = 0
for counter in range (0,nbRoll):
pid[index] = pid[index] ^ ord(dsn[counter])
index = (index+1) %8
for counter in range (0,8):
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
pidAscii += charMap4[index]
return pidAscii
#
# Create decrypted book payload
#
def createDecryptedPayload(payload):
# store data to be able to create the header later
headerData= []
currentOffset = 0
# Add social DRM to decrypted files
try:
data = getKindleInfoValueForKey("kindle.name.info")+":"+ getKindleInfoValueForKey("login")
if payload!= None:
payload.write(lengthPrefixString("sdrm"))
payload.write(encodeNumber(0))
payload.write(data)
else:
currentOffset += len(lengthPrefixString("sdrm"))
currentOffset += len(encodeNumber(0))
currentOffset += len(data)
except:
pass
for headerRecord in bookHeaderRecords:
name = headerRecord
newRecord = []
if name != "dkey" :
for index in range (0,len(bookHeaderRecords[name])) :
offset = currentOffset
if payload != None:
# write tag
payload.write(lengthPrefixString(name))
# write data
payload.write(encodeNumber(index))
payload.write(getBookPayloadRecord(name, index))
else :
currentOffset += len(lengthPrefixString(name))
currentOffset += len(encodeNumber(index))
currentOffset += len(getBookPayloadRecord(name, index))
newRecord.append([offset,bookHeaderRecords[name][index][1],bookHeaderRecords[name][index][2]])
headerData.append([name,newRecord])
return headerData
#
# Create decrypted book
#
def createDecryptedBook(outputFile):
outputFile = open(outputFile,"wb")
# Write the payload in a temporary file
headerData = createDecryptedPayload(None)
outputFile.write("TPZ0")
outputFile.write(encodeNumber(len(headerData)))
for header in headerData :
outputFile.write(chr(0x63))
outputFile.write(lengthPrefixString(header[0]))
outputFile.write(encodeNumber(len(header[1])))
for numbers in header[1] :
outputFile.write(encodeNumber(numbers[0]))
outputFile.write(encodeNumber(numbers[1]))
outputFile.write(encodeNumber(numbers[2]))
outputFile.write(chr(0x64))
createDecryptedPayload(outputFile)
outputFile.close()
#
# Set the command to execute by the programm according to cmdLine parameters
#
def setCommand(name) :
global command
if command != "" :
raise CMBDTCFatal("Invalid command line parameters")
else :
command = name
#
# Program usage
#
def usage():
print("\nUsage:")
print("\nCMBDTC.py [options] bookFileName\n")
print("-p Adds a PID to the list of PIDs that are tried to decrypt the book key (can be used several times)")
print("-d Saves a decrypted copy of the book")
print("-r Prints or writes to disk a record indicated in the form name:index (e.g \"img:0\")")
print("-o Output file name to write records and decrypted books")
print("-v Verbose (can be used several times)")
print("-i Prints kindle.info database")
#
# Main
#
def main(argv=sys.argv):
global kindleDatabase
global bookMetadata
global bookKey
global bookFile
global command
progname = os.path.basename(argv[0])
verbose = 0
recordName = ""
recordIndex = 0
outputFile = ""
PIDs = []
kindleDatabase = None
command = ""
try:
opts, args = getopt.getopt(sys.argv[1:], "vdir:o:p:")
except getopt.GetoptError, err:
# print help information and exit:
print str(err) # will print something like "option -a not recognized"
usage()
sys.exit(2)
if len(opts) == 0 and len(args) == 0 :
usage()
sys.exit(2)
for o, a in opts:
if o == "-v":
verbose+=1
if o == "-i":
setCommand("printInfo")
if o =="-o":
if a == None :
raise CMBDTCFatal("Invalid parameter for -o")
outputFile = a
if o =="-r":
setCommand("printRecord")
try:
recordName,recordIndex = a.split(':')
except:
raise CMBDTCFatal("Invalid parameter for -r")
if o =="-p":
PIDs.append(a)
if o =="-d":
setCommand("doit")
if command == "" :
raise CMBDTCFatal("No action supplied on command line")
#
# Read the encrypted database
#
try:
kindleDatabase = parseKindleInfo()
except Exception, message:
if verbose>0:
print(message)
if kindleDatabase != None :
if command == "printInfo" :
printKindleInfo()
#
# Compute the DSN
#
# Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(str(GetVolumeSerialNumber(GetSystemDirectory().split('\\')[0] + '\\')),charMap1)
# Get the current user name
encodedUsername = encodeHash(GetUserName(),charMap1)
# concat, hash and encode
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
if verbose >1:
print("DSN: " + DSN)
#
# Compute the device PID
#
table = generatePidEncryptionTable()
devicePID = generateDevicePID(table,DSN,4)
PIDs.append(devicePID)
if verbose > 0:
print("Device PID: " + devicePID)
#
# Open book and parse metadata
#
if len(args) == 1:
bookFile = openBook(args[0])
parseTopazHeader()
parseMetadata()
#
# Compute book PID
#
# Get the account token
if kindleDatabase != None:
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
if verbose >1:
print("Account Token: " + kindleAccountToken)
keysRecord = bookMetadata["keys"]
keysRecordRecord = bookMetadata[keysRecord]
pidHash = SHA1(DSN+kindleAccountToken+keysRecord+keysRecordRecord)
bookPID = encodePID(pidHash)
PIDs.append(bookPID)
if verbose > 0:
print ("Book PID: " + bookPID )
#
# Decrypt book key
#
dkey = getBookPayloadRecord('dkey', 0)
bookKeys = []
for PID in PIDs :
bookKeys+=decryptDkeyRecords(dkey,PID)
if len(bookKeys) == 0 :
if verbose > 0 :
print ("Book key could not be found. Maybe this book is not registered with this device.")
else :
bookKey = bookKeys[0]
if verbose > 0:
print("Book key: " + bookKey.encode('hex'))
if command == "printRecord" :
extractBookPayloadRecord(recordName,int(recordIndex),outputFile)
if outputFile != "" and verbose>0 :
print("Wrote record to file: "+outputFile)
elif command == "doit" :
if outputFile!="" :
createDecryptedBook(outputFile)
if verbose >0 :
print ("Decrypted book saved. Don't pirate!")
elif verbose > 0:
print("Output file name was not supplied.")
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,59 @@
from PyQt4.Qt import QWidget, QVBoxLayout, QLabel, QLineEdit
from calibre.utils.config import JSONConfig
# This is where all preferences for this plugin will be stored
# You should always prefix your config file name with plugins/,
# so as to ensure you dont accidentally clobber a calibre config file
prefs = JSONConfig('plugins/K4MobiDeDRM')
# Set defaults
prefs.defaults['pids'] = ""
prefs.defaults['serials'] = ""
prefs.defaults['WINEPREFIX'] = None
class ConfigWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
self.l = QVBoxLayout()
self.setLayout(self.l)
self.serialLabel = QLabel('Kindle Serial numbers (separate with commas, no spaces)')
self.l.addWidget(self.serialLabel)
self.serials = QLineEdit(self)
self.serials.setText(prefs['serials'])
self.l.addWidget(self.serials)
self.serialLabel.setBuddy(self.serials)
self.pidLabel = QLabel('Mobipocket PIDs (separate with commas, no spaces)')
self.l.addWidget(self.pidLabel)
self.pids = QLineEdit(self)
self.pids.setText(prefs['pids'])
self.l.addWidget(self.pids)
self.pidLabel.setBuddy(self.serials)
self.wpLabel = QLabel('For Linux only: WINEPREFIX (enter absolute path)')
self.l.addWidget(self.wpLabel)
self.wineprefix = QLineEdit(self)
wineprefix = prefs['WINEPREFIX']
if wineprefix is not None:
self.wineprefix.setText(wineprefix)
else:
self.wineprefix.setText('')
self.l.addWidget(self.wineprefix)
self.wpLabel.setBuddy(self.wineprefix)
def save_settings(self):
prefs['pids'] = str(self.pids.text())
prefs['serials'] = str(self.serials.text())
winepref=str(self.wineprefix.text())
if winepref.strip() != '':
prefs['WINEPREFIX'] = winepref
else:
prefs['WINEPREFIX'] = None

View File

@@ -34,11 +34,11 @@ def readEncodedNumber(file):
data = ord(c) data = ord(c)
if data == 0xFF: if data == 0xFF:
flag = True flag = True
c = file.read(1) c = file.read(1)
if (len(c) == 0): if (len(c) == 0):
return None return None
data = ord(c) data = ord(c)
if data >= 0x80: if data >= 0x80:
datax = (data & 0x7F) datax = (data & 0x7F)
@@ -51,7 +51,7 @@ def readEncodedNumber(file):
data = datax data = datax
if flag: if flag:
data = -data data = -data
return data return data
@@ -59,29 +59,29 @@ def readEncodedNumber(file):
# most significant byte first which has the high bit set # most significant byte first which has the high bit set
def encodeNumber(number): def encodeNumber(number):
result = "" result = ""
negative = False negative = False
flag = 0 flag = 0
if number < 0 : if number < 0 :
number = -number + 1 number = -number + 1
negative = True negative = True
while True: while True:
byte = number & 0x7F byte = number & 0x7F
number = number >> 7 number = number >> 7
byte += flag byte += flag
result += chr(byte) result += chr(byte)
flag = 0x80 flag = 0x80
if number == 0 : if number == 0 :
if (byte == 0xFF and negative == False) : if (byte == 0xFF and negative == False) :
result += chr(0x80) result += chr(0x80)
break break
if negative: if negative:
result += chr(0xFF) result += chr(0xFF)
return result[::-1] return result[::-1]
@@ -214,6 +214,7 @@ class PageParser(object):
'links.title' : (1, 'text', 0, 0), 'links.title' : (1, 'text', 0, 0),
'links.href' : (1, 'text', 0, 0), 'links.href' : (1, 'text', 0, 0),
'links.type' : (1, 'text', 0, 0), 'links.type' : (1, 'text', 0, 0),
'links.id' : (1, 'number', 0, 0),
'paraCont' : (0, 'number', 1, 1), 'paraCont' : (0, 'number', 1, 1),
'paraCont.rootID' : (1, 'number', 0, 0), 'paraCont.rootID' : (1, 'number', 0, 0),
@@ -239,6 +240,7 @@ class PageParser(object):
'group' : (1, 'snippets', 1, 0), 'group' : (1, 'snippets', 1, 0),
'group.type' : (1, 'scalar_text', 0, 0), 'group.type' : (1, 'scalar_text', 0, 0),
'group._tag' : (1, 'scalar_text', 0, 0), 'group._tag' : (1, 'scalar_text', 0, 0),
'group.orientation': (1, 'scalar_text', 0, 0),
'region' : (1, 'snippets', 1, 0), 'region' : (1, 'snippets', 1, 0),
'region.type' : (1, 'scalar_text', 0, 0), 'region.type' : (1, 'scalar_text', 0, 0),
@@ -246,6 +248,7 @@ class PageParser(object):
'region.y' : (1, 'scalar_number', 0, 0), 'region.y' : (1, 'scalar_number', 0, 0),
'region.h' : (1, 'scalar_number', 0, 0), 'region.h' : (1, 'scalar_number', 0, 0),
'region.w' : (1, 'scalar_number', 0, 0), 'region.w' : (1, 'scalar_number', 0, 0),
'region.orientation' : (1, 'scalar_text', 0, 0),
'empty_text_region' : (1, 'snippets', 1, 0), 'empty_text_region' : (1, 'snippets', 1, 0),
@@ -265,6 +268,8 @@ class PageParser(object):
'paragraph.gridSize' : (1, 'scalar_number', 0, 0), 'paragraph.gridSize' : (1, 'scalar_number', 0, 0),
'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0), 'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0),
'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0), 'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0),
'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0),
'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0),
'word_semantic' : (1, 'snippets', 1, 1), 'word_semantic' : (1, 'snippets', 1, 1),
@@ -284,6 +289,8 @@ class PageParser(object):
'_span.gridSize' : (1, 'scalar_number', 0, 0), '_span.gridSize' : (1, 'scalar_number', 0, 0),
'_span.gridBottomCenter' : (1, 'scalar_number', 0, 0), '_span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
'_span.gridTopCenter' : (1, 'scalar_number', 0, 0), '_span.gridTopCenter' : (1, 'scalar_number', 0, 0),
'_span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
'_span.gridEndCenter' : (1, 'scalar_number', 0, 0),
'span' : (1, 'snippets', 1, 0), 'span' : (1, 'snippets', 1, 0),
'span.firstWord' : (1, 'scalar_number', 0, 0), 'span.firstWord' : (1, 'scalar_number', 0, 0),
@@ -291,6 +298,8 @@ class PageParser(object):
'span.gridSize' : (1, 'scalar_number', 0, 0), 'span.gridSize' : (1, 'scalar_number', 0, 0),
'span.gridBottomCenter' : (1, 'scalar_number', 0, 0), 'span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
'span.gridTopCenter' : (1, 'scalar_number', 0, 0), 'span.gridTopCenter' : (1, 'scalar_number', 0, 0),
'span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
'extratokens' : (1, 'snippets', 1, 0), 'extratokens' : (1, 'snippets', 1, 0),
'extratokens.type' : (1, 'scalar_text', 0, 0), 'extratokens.type' : (1, 'scalar_text', 0, 0),
@@ -609,65 +618,70 @@ class PageParser(object):
nodename = fullpathname.pop() nodename = fullpathname.pop()
ilvl = len(fullpathname) ilvl = len(fullpathname)
indent = ' ' * (3 * ilvl) indent = ' ' * (3 * ilvl)
result = indent + '<' + nodename + '>' rlst = []
rlst.append(indent + '<' + nodename + '>')
if len(argList) > 0: if len(argList) > 0:
argres = '' alst = []
for j in argList: for j in argList:
if (argtype == 'text') or (argtype == 'scalar_text') : if (argtype == 'text') or (argtype == 'scalar_text') :
argres += j + '|' alst.append(j + '|')
else : else :
argres += str(j) + ',' alst.append(str(j) + ',')
argres = "".join(alst)
argres = argres[0:-1] argres = argres[0:-1]
if argtype == 'snippets' : if argtype == 'snippets' :
result += 'snippets:' + argres rlst.append('snippets:' + argres)
else : else :
result += argres rlst.append(argres)
if len(subtagList) > 0 : if len(subtagList) > 0 :
result += '\n' rlst.append('\n')
for j in subtagList: for j in subtagList:
if len(j) > 0 : if len(j) > 0 :
result += self.formatTag(j) rlst.append(self.formatTag(j))
result += indent + '</' + nodename + '>\n' rlst.append(indent + '</' + nodename + '>\n')
else: else:
result += '</' + nodename + '>\n' rlst.append('</' + nodename + '>\n')
return result return "".join(rlst)
# flatten tag # flatten tag
def flattenTag(self, node): def flattenTag(self, node):
name = node[0] name = node[0]
subtagList = node[1] subtagList = node[1]
argtype = node[2] argtype = node[2]
argList = node[3] argList = node[3]
result = name rlst = []
rlst.append(name)
if (len(argList) > 0): if (len(argList) > 0):
argres = '' alst = []
for j in argList: for j in argList:
if (argtype == 'text') or (argtype == 'scalar_text') : if (argtype == 'text') or (argtype == 'scalar_text') :
argres += j + '|' alst.append(j + '|')
else : else :
argres += str(j) + '|' alst.append(str(j) + '|')
argres = "".join(alst)
argres = argres[0:-1] argres = argres[0:-1]
if argtype == 'snippets' : if argtype == 'snippets' :
result += '.snippets=' + argres rlst.append('.snippets=' + argres)
else : else :
result += '=' + argres rlst.append('=' + argres)
result += '\n' rlst.append('\n')
for j in subtagList: for j in subtagList:
if len(j) > 0 : if len(j) > 0 :
result += self.flattenTag(j) rlst.append(self.flattenTag(j))
return result return "".join(rlst)
# reduce create xml output # reduce create xml output
def formatDoc(self, flat_xml): def formatDoc(self, flat_xml):
result = '' rlst = []
for j in self.doc : for j in self.doc :
if len(j) > 0: if len(j) > 0:
if flat_xml: if flat_xml:
result += self.flattenTag(j) rlst.append(self.flattenTag(j))
else: else:
result += self.formatTag(j) rlst.append(self.formatTag(j))
result = "".join(rlst)
if self.debug : print result if self.debug : print result
return result return result

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

After

Width:  |  Height:  |  Size: 362 B

View File

@@ -37,10 +37,9 @@ def cli_main(argv=sys.argv):
keypath = argv[1] keypath = argv[1]
with open(keypath, 'rb') as f: with open(keypath, 'rb') as f:
keyder = f.read() keyder = f.read()
print keyder.encode('base64') print keyder.encode('base64')
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(cli_main()) sys.exit(cli_main())

View File

@@ -178,7 +178,7 @@ def sanitizeFileName(s):
def fixKey(key): def fixKey(key):
def fixByte(b): def fixByte(b):
return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80) return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80)
return "".join([chr(fixByte(ord(a))) for a in key]) return "".join([chr(fixByte(ord(a))) for a in key])
def deXOR(text, sp, table): def deXOR(text, sp, table):
r='' r=''
@@ -212,7 +212,7 @@ class EreaderProcessor(object):
for i in xrange(len(data)): for i in xrange(len(data)):
j = (j + shuf) % len(data) j = (j + shuf) % len(data)
r[j] = data[i] r[j] = data[i]
assert len("".join(r)) == len(data) assert len("".join(r)) == len(data)
return "".join(r) return "".join(r)
r = unshuff(input[0:-8], cookie_shuf) r = unshuff(input[0:-8], cookie_shuf)
@@ -399,12 +399,12 @@ class EreaderProcessor(object):
return r return r
def cleanPML(pml): def cleanPML(pml):
# Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255) # Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
pml2 = pml pml2 = pml
for k in xrange(128,256): for k in xrange(128,256):
badChar = chr(k) badChar = chr(k)
pml2 = pml2.replace(badChar, '\\a%03d' % k) pml2 = pml2.replace(badChar, '\\a%03d' % k)
return pml2 return pml2
def convertEreaderToPml(infile, name, cc, outdir): def convertEreaderToPml(infile, name, cc, outdir):
if not os.path.exists(outdir): if not os.path.exists(outdir):
@@ -524,4 +524,3 @@ def main(argv=None):
if __name__ == "__main__": if __name__ == "__main__":
sys.stdout=Unbuffered(sys.stdout) sys.stdout=Unbuffered(sys.stdout)
sys.exit(main()) sys.exit(main())

View File

@@ -292,6 +292,11 @@ class DocParser(object):
if self.fixedimage : if self.fixedimage :
makeImage = makeImage or (pclass.find('cl-f-') >= 0) makeImage = makeImage or (pclass.find('cl-f-') >= 0)
# before creating an image make sure glyph info exists
gidList = self.getData('info.glyph.glyphID',0,-1)
makeImage = makeImage & (len(gidList) > 0)
if not makeImage : if not makeImage :
# standard all word paragraph # standard all word paragraph
for wordnum in xrange(first, last): for wordnum in xrange(first, last):
@@ -382,10 +387,10 @@ class DocParser(object):
ws_last = int(argres) ws_last = int(argres)
elif name.endswith('word.class'): elif name.endswith('word.class'):
(cname, space) = argres.split('-',1) (cname, space) = argres.split('-',1)
if space == '' : space = '0' if space == '' : space = '0'
if (cname == 'spaceafter') and (int(space) > 0) : if (cname == 'spaceafter') and (int(space) > 0) :
word_class = 'sa' word_class = 'sa'
elif name.endswith('word.img.src'): elif name.endswith('word.img.src'):
result.append(('img' + word_class, int(argres))) result.append(('img' + word_class, int(argres)))
@@ -590,8 +595,8 @@ class DocParser(object):
def process(self): def process(self):
htmlpage = ''
tocinfo = '' tocinfo = ''
hlst = []
# get the ocr text # get the ocr text
(pos, argres) = self.findinDoc('info.word.ocrText',0,-1) (pos, argres) = self.findinDoc('info.word.ocrText',0,-1)
@@ -648,8 +653,8 @@ class DocParser(object):
# set anchor for link target on this page # set anchor for link target on this page
if not anchorSet and not first_para_continued: if not anchorSet and not first_para_continued:
htmlpage += '<div style="visibility: hidden; height: 0; width: 0;" id="' hlst.append('<div style="visibility: hidden; height: 0; width: 0;" id="')
htmlpage += self.id + '" title="pagetype_' + pagetype + '"></div>\n' hlst.append(self.id + '" title="pagetype_' + pagetype + '"></div>\n')
anchorSet = True anchorSet = True
# handle groups of graphics with text captions # handle groups of graphics with text captions
@@ -658,12 +663,12 @@ class DocParser(object):
if grptype != None: if grptype != None:
if grptype == 'graphic': if grptype == 'graphic':
gcstr = ' class="' + grptype + '"' gcstr = ' class="' + grptype + '"'
htmlpage += '<div' + gcstr + '>' hlst.append('<div' + gcstr + '>')
inGroup = True inGroup = True
elif (etype == 'grpend'): elif (etype == 'grpend'):
if inGroup: if inGroup:
htmlpage += '</div>\n' hlst.append('</div>\n')
inGroup = False inGroup = False
else: else:
@@ -673,25 +678,25 @@ class DocParser(object):
(pos, simgsrc) = self.findinDoc('img.src',start,end) (pos, simgsrc) = self.findinDoc('img.src',start,end)
if simgsrc: if simgsrc:
if inGroup: if inGroup:
htmlpage += '<img src="img/img%04d.jpg" alt="" />' % int(simgsrc) hlst.append('<img src="img/img%04d.jpg" alt="" />' % int(simgsrc))
else: else:
htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc) hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
elif regtype == 'chapterheading' : elif regtype == 'chapterheading' :
(pclass, pdesc) = self.getParaDescription(start,end, regtype) (pclass, pdesc) = self.getParaDescription(start,end, regtype)
if not breakSet: if not breakSet:
htmlpage += '<div style="page-break-after: always;">&nbsp;</div>\n' hlst.append('<div style="page-break-after: always;">&nbsp;</div>\n')
breakSet = True breakSet = True
tag = 'h1' tag = 'h1'
if pclass and (len(pclass) >= 7): if pclass and (len(pclass) >= 7):
if pclass[3:7] == 'ch1-' : tag = 'h1' if pclass[3:7] == 'ch1-' : tag = 'h1'
if pclass[3:7] == 'ch2-' : tag = 'h2' if pclass[3:7] == 'ch2-' : tag = 'h2'
if pclass[3:7] == 'ch3-' : tag = 'h3' if pclass[3:7] == 'ch3-' : tag = 'h3'
htmlpage += '<' + tag + ' class="' + pclass + '">' hlst.append('<' + tag + ' class="' + pclass + '">')
else: else:
htmlpage += '<' + tag + '>' hlst.append('<' + tag + '>')
htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype) hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
htmlpage += '</' + tag + '>' hlst.append('</' + tag + '>')
elif (regtype == 'text') or (regtype == 'fixed') or (regtype == 'insert') or (regtype == 'listitem'): elif (regtype == 'text') or (regtype == 'fixed') or (regtype == 'insert') or (regtype == 'listitem'):
ptype = 'full' ptype = 'full'
@@ -705,11 +710,11 @@ class DocParser(object):
if pclass[3:6] == 'h1-' : tag = 'h4' if pclass[3:6] == 'h1-' : tag = 'h4'
if pclass[3:6] == 'h2-' : tag = 'h5' if pclass[3:6] == 'h2-' : tag = 'h5'
if pclass[3:6] == 'h3-' : tag = 'h6' if pclass[3:6] == 'h3-' : tag = 'h6'
htmlpage += '<' + tag + ' class="' + pclass + '">' hlst.append('<' + tag + ' class="' + pclass + '">')
htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype) hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
htmlpage += '</' + tag + '>' hlst.append('</' + tag + '>')
else : else :
htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype) hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
elif (regtype == 'tocentry') : elif (regtype == 'tocentry') :
ptype = 'full' ptype = 'full'
@@ -718,7 +723,7 @@ class DocParser(object):
first_para_continued = False first_para_continued = False
(pclass, pdesc) = self.getParaDescription(start,end, regtype) (pclass, pdesc) = self.getParaDescription(start,end, regtype)
tocinfo += self.buildTOCEntry(pdesc) tocinfo += self.buildTOCEntry(pdesc)
htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype) hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
elif (regtype == 'vertical') or (regtype == 'table') : elif (regtype == 'vertical') or (regtype == 'table') :
ptype = 'full' ptype = 'full'
@@ -728,13 +733,13 @@ class DocParser(object):
ptype = 'end' ptype = 'end'
first_para_continued = False first_para_continued = False
(pclass, pdesc) = self.getParaDescription(start, end, regtype) (pclass, pdesc) = self.getParaDescription(start, end, regtype)
htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype) hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
elif (regtype == 'synth_fcvr.center'): elif (regtype == 'synth_fcvr.center'):
(pos, simgsrc) = self.findinDoc('img.src',start,end) (pos, simgsrc) = self.findinDoc('img.src',start,end)
if simgsrc: if simgsrc:
htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc) hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
else : else :
print ' Making region type', regtype, print ' Making region type', regtype,
@@ -760,18 +765,19 @@ class DocParser(object):
if pclass[3:6] == 'h1-' : tag = 'h4' if pclass[3:6] == 'h1-' : tag = 'h4'
if pclass[3:6] == 'h2-' : tag = 'h5' if pclass[3:6] == 'h2-' : tag = 'h5'
if pclass[3:6] == 'h3-' : tag = 'h6' if pclass[3:6] == 'h3-' : tag = 'h6'
htmlpage += '<' + tag + ' class="' + pclass + '">' hlst.append('<' + tag + ' class="' + pclass + '">')
htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype) hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
htmlpage += '</' + tag + '>' hlst.append('</' + tag + '>')
else : else :
htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype) hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
else : else :
print ' a "graphic" region' print ' a "graphic" region'
(pos, simgsrc) = self.findinDoc('img.src',start,end) (pos, simgsrc) = self.findinDoc('img.src',start,end)
if simgsrc: if simgsrc:
htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc) hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
htmlpage = "".join(hlst)
if last_para_continued : if last_para_continued :
if htmlpage[-4:] == '</p>': if htmlpage[-4:] == '</p>':
htmlpage = htmlpage[0:-4] htmlpage = htmlpage[0:-4]

View File

@@ -181,70 +181,69 @@ class PParser(object):
def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi): def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi):
ml = '' mlst = []
pp = PParser(gdict, flat_xml, meta_array) pp = PParser(gdict, flat_xml, meta_array)
ml += '<?xml version="1.0" standalone="no"?>\n' mlst.append('<?xml version="1.0" standalone="no"?>\n')
if (raw): if (raw):
ml += '<!DOCTYPE svg PUBLIC "-//W3C/DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' mlst.append('<!DOCTYPE svg PUBLIC "-//W3C/DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
ml += '<svg width="%fin" height="%fin" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">\n' % (pp.pw / scaledpi, pp.ph / scaledpi, pp.pw -1, pp.ph -1) mlst.append('<svg width="%fin" height="%fin" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">\n' % (pp.pw / scaledpi, pp.ph / scaledpi, pp.pw -1, pp.ph -1))
ml += '<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors']) mlst.append('<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors']))
else: else:
ml += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' mlst.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n')
ml += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" ><head>\n' mlst.append('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" ><head>\n')
ml += '<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors']) mlst.append('<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors']))
ml += '<script><![CDATA[\n' mlst.append('<script><![CDATA[\n')
ml += 'function gd(){var p=window.location.href.replace(/^.*\?dpi=(\d+).*$/i,"$1");return p;}\n' mlst.append('function gd(){var p=window.location.href.replace(/^.*\?dpi=(\d+).*$/i,"$1");return p;}\n')
ml += 'var dpi=%d;\n' % scaledpi mlst.append('var dpi=%d;\n' % scaledpi)
if (previd) : if (previd) :
ml += 'var prevpage="page%04d.xhtml";\n' % (previd) mlst.append('var prevpage="page%04d.xhtml";\n' % (previd))
if (nextid) : if (nextid) :
ml += 'var nextpage="page%04d.xhtml";\n' % (nextid) mlst.append('var nextpage="page%04d.xhtml";\n' % (nextid))
ml += 'var pw=%d;var ph=%d;' % (pp.pw, pp.ph) mlst.append('var pw=%d;var ph=%d;' % (pp.pw, pp.ph))
ml += 'function zoomin(){dpi=dpi*(0.8);setsize();}\n' mlst.append('function zoomin(){dpi=dpi*(0.8);setsize();}\n')
ml += 'function zoomout(){dpi=dpi*1.25;setsize();}\n' mlst.append('function zoomout(){dpi=dpi*1.25;setsize();}\n')
ml += 'function setsize(){var svg=document.getElementById("svgimg");var prev=document.getElementById("prevsvg");var next=document.getElementById("nextsvg");var width=(pw/dpi)+"in";var height=(ph/dpi)+"in";svg.setAttribute("width",width);svg.setAttribute("height",height);prev.setAttribute("height",height);prev.setAttribute("width","50px");next.setAttribute("height",height);next.setAttribute("width","50px");}\n' mlst.append('function setsize(){var svg=document.getElementById("svgimg");var prev=document.getElementById("prevsvg");var next=document.getElementById("nextsvg");var width=(pw/dpi)+"in";var height=(ph/dpi)+"in";svg.setAttribute("width",width);svg.setAttribute("height",height);prev.setAttribute("height",height);prev.setAttribute("width","50px");next.setAttribute("height",height);next.setAttribute("width","50px");}\n')
ml += 'function ppage(){window.location.href=prevpage+"?dpi="+Math.round(dpi);}\n' mlst.append('function ppage(){window.location.href=prevpage+"?dpi="+Math.round(dpi);}\n')
ml += 'function npage(){window.location.href=nextpage+"?dpi="+Math.round(dpi);}\n' mlst.append('function npage(){window.location.href=nextpage+"?dpi="+Math.round(dpi);}\n')
ml += 'var gt=gd();if(gt>0){dpi=gt;}\n' mlst.append('var gt=gd();if(gt>0){dpi=gt;}\n')
ml += 'window.onload=setsize;\n' mlst.append('window.onload=setsize;\n')
ml += ']]></script>\n' mlst.append(']]></script>\n')
ml += '</head>\n' mlst.append('</head>\n')
ml += '<body onLoad="setsize();" style="background-color:#777;text-align:center;">\n' mlst.append('<body onLoad="setsize();" style="background-color:#777;text-align:center;">\n')
ml += '<div style="white-space:nowrap;">\n' mlst.append('<div style="white-space:nowrap;">\n')
if previd == None: if previd == None:
ml += '<a href="javascript:ppage();"><svg id="prevsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"></svg></a>\n' mlst.append('<a href="javascript:ppage();"><svg id="prevsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"></svg></a>\n')
else: else:
ml += '<a href="javascript:ppage();"><svg id="prevsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"><polygon points="5,150,95,5,95,295" fill="#AAAAAA" /></svg></a>\n' mlst.append('<a href="javascript:ppage();"><svg id="prevsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"><polygon points="5,150,95,5,95,295" fill="#AAAAAA" /></svg></a>\n')
ml += '<a href="javascript:npage();"><svg id="svgimg" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" style="background-color:#FFF;border:1px solid black;">' % (pp.pw, pp.ph) mlst.append('<a href="javascript:npage();"><svg id="svgimg" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" style="background-color:#FFF;border:1px solid black;">' % (pp.pw, pp.ph))
if (pp.gid != None): if (pp.gid != None):
ml += '<defs>\n' mlst.append('<defs>\n')
gdefs = pp.getGlyphs() gdefs = pp.getGlyphs()
for j in xrange(0,len(gdefs)): for j in xrange(0,len(gdefs)):
ml += gdefs[j] mlst.append(gdefs[j])
ml += '</defs>\n' mlst.append('</defs>\n')
img = pp.getImages() img = pp.getImages()
if (img != None): if (img != None):
for j in xrange(0,len(img)): for j in xrange(0,len(img)):
ml += img[j] mlst.append(img[j])
if (pp.gid != None): if (pp.gid != None):
for j in xrange(0,len(pp.gid)): for j in xrange(0,len(pp.gid)):
ml += '<use xlink:href="#gl%d" x="%d" y="%d" />\n' % (pp.gid[j], pp.gx[j], pp.gy[j]) mlst.append('<use xlink:href="#gl%d" x="%d" y="%d" />\n' % (pp.gid[j], pp.gx[j], pp.gy[j]))
if (img == None or len(img) == 0) and (pp.gid == None or len(pp.gid) == 0): if (img == None or len(img) == 0) and (pp.gid == None or len(pp.gid) == 0):
xpos = "%d" % (pp.pw // 3) xpos = "%d" % (pp.pw // 3)
ypos = "%d" % (pp.ph // 3) ypos = "%d" % (pp.ph // 3)
ml += '<text x="' + xpos + '" y="' + ypos + '" font-size="' + meta_array['fontSize'] + '" font-family="Helvetica" stroke="black">This page intentionally left blank.</text>\n' mlst.append('<text x="' + xpos + '" y="' + ypos + '" font-size="' + meta_array['fontSize'] + '" font-family="Helvetica" stroke="black">This page intentionally left blank.</text>\n')
if (raw) : if (raw) :
ml += '</svg>' mlst.append('</svg>')
else : else :
ml += '</svg></a>\n' mlst.append('</svg></a>\n')
if nextid == None: if nextid == None:
ml += '<a href="javascript:npage();"><svg id="nextsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"></svg></a>\n' mlst.append('<a href="javascript:npage();"><svg id="nextsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"></svg></a>\n')
else : else :
ml += '<a href="javascript:npage();"><svg id="nextsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"><polygon points="5,5,5,295,95,150" fill="#AAAAAA" /></svg></a>\n' mlst.append('<a href="javascript:npage();"><svg id="nextsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"><polygon points="5,5,5,295,95,150" fill="#AAAAAA" /></svg></a>\n')
ml += '</div>\n' mlst.append('</div>\n')
ml += '<div><a href="javascript:zoomin();">zoom in</a> - <a href="javascript:zoomout();">zoom out</a></div>\n' mlst.append('<div><a href="javascript:zoomin();">zoom in</a> - <a href="javascript:zoomout();">zoom out</a></div>\n')
ml += '</body>\n' mlst.append('</body>\n')
ml += '</html>\n' mlst.append('</html>\n')
return ml return "".join(mlst)

View File

@@ -39,6 +39,8 @@ else :
import flatxml2svg import flatxml2svg
import stylexml2css import stylexml2css
# global switch
buildXML = False
# Get a 7 bit encoded number from a file # Get a 7 bit encoded number from a file
def readEncodedNumber(file): def readEncodedNumber(file):
@@ -48,11 +50,11 @@ def readEncodedNumber(file):
return None return None
data = ord(c) data = ord(c)
if data == 0xFF: if data == 0xFF:
flag = True flag = True
c = file.read(1) c = file.read(1)
if (len(c) == 0): if (len(c) == 0):
return None return None
data = ord(c) data = ord(c)
if data >= 0x80: if data >= 0x80:
datax = (data & 0x7F) datax = (data & 0x7F)
while data >= 0x80 : while data >= 0x80 :
@@ -63,7 +65,7 @@ def readEncodedNumber(file):
datax = (datax <<7) + (data & 0x7F) datax = (datax <<7) + (data & 0x7F)
data = datax data = datax
if flag: if flag:
data = -data data = -data
return data return data
# Get a length prefixed string from the file # Get a length prefixed string from the file
@@ -298,9 +300,10 @@ def generateBook(bookDir, raw, fixedimage):
if not os.path.exists(svgDir) : if not os.path.exists(svgDir) :
os.makedirs(svgDir) os.makedirs(svgDir)
xmlDir = os.path.join(bookDir,'xml') if buildXML:
if not os.path.exists(xmlDir) : xmlDir = os.path.join(bookDir,'xml')
os.makedirs(xmlDir) if not os.path.exists(xmlDir) :
os.makedirs(xmlDir)
otherFile = os.path.join(bookDir,'other0000.dat') otherFile = os.path.join(bookDir,'other0000.dat')
if not os.path.exists(otherFile) : if not os.path.exists(otherFile) :
@@ -348,23 +351,38 @@ def generateBook(bookDir, raw, fixedimage):
authors = authors.replace('>','&gt;') authors = authors.replace('>','&gt;')
meta_array['Authors'] = authors meta_array['Authors'] = authors
xname = os.path.join(xmlDir, 'metadata.xml') if buildXML:
metastr = '' xname = os.path.join(xmlDir, 'metadata.xml')
for key in meta_array: mlst = []
metastr += '<meta name="' + key + '" content="' + meta_array[key] + '" />\n' for key in meta_array:
file(xname, 'wb').write(metastr) mlst.append('<meta name="' + key + '" content="' + meta_array[key] + '" />\n')
metastr = "".join(mlst)
mlst = None
file(xname, 'wb').write(metastr)
print 'Processing StyleSheet' print 'Processing StyleSheet'
# get some scaling info from metadata to use while processing styles # get some scaling info from metadata to use while processing styles
# and first page info
fontsize = '135' fontsize = '135'
if 'fontSize' in meta_array: if 'fontSize' in meta_array:
fontsize = meta_array['fontSize'] fontsize = meta_array['fontSize']
# also get the size of a normal text page # also get the size of a normal text page
# get the total number of pages unpacked as a safety check
filenames = os.listdir(pageDir)
numfiles = len(filenames)
spage = '1' spage = '1'
if 'firstTextPage' in meta_array: if 'firstTextPage' in meta_array:
spage = meta_array['firstTextPage'] spage = meta_array['firstTextPage']
pnum = int(spage) pnum = int(spage)
if pnum >= numfiles or pnum < 0:
# metadata is wrong so just select a page near the front
# 10% of the book to get a normal text page
pnum = int(0.10 * numfiles)
# print "first normal text page is", spage
# get page height and width from first text page for use in stylesheet scaling # get page height and width from first text page for use in stylesheet scaling
pname = 'page%04d.dat' % (pnum + 1) pname = 'page%04d.dat' % (pnum + 1)
@@ -404,8 +422,9 @@ def generateBook(bookDir, raw, fixedimage):
# now get the css info # now get the css info
cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw) cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw)
file(xname, 'wb').write(cssstr) file(xname, 'wb').write(cssstr)
xname = os.path.join(xmlDir, 'other0000.xml') if buildXML:
file(xname, 'wb').write(convert2xml.getXML(dict, otherFile)) xname = os.path.join(xmlDir, 'other0000.xml')
file(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
print 'Processing Glyphs' print 'Processing Glyphs'
gd = GlyphDict() gd = GlyphDict()
@@ -425,8 +444,9 @@ def generateBook(bookDir, raw, fixedimage):
fname = os.path.join(glyphsDir,filename) fname = os.path.join(glyphsDir,filename)
flat_xml = convert2xml.fromData(dict, fname) flat_xml = convert2xml.fromData(dict, fname)
xname = os.path.join(xmlDir, filename.replace('.dat','.xml')) if buildXML:
file(xname, 'wb').write(convert2xml.getXML(dict, fname)) xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
file(xname, 'wb').write(convert2xml.getXML(dict, fname))
gp = GParser(flat_xml) gp = GParser(flat_xml)
for i in xrange(0, gp.count): for i in xrange(0, gp.count):
@@ -441,25 +461,25 @@ def generateBook(bookDir, raw, fixedimage):
glyfile.close() glyfile.close()
print " " print " "
# build up tocentries while processing html
tocentries = ''
# start up the html # start up the html
# also build up tocentries while processing html
htmlFileName = "book.html" htmlFileName = "book.html"
htmlstr = '<?xml version="1.0" encoding="utf-8"?>\n' hlst = []
htmlstr += '<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.1 Strict//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11-strict.dtd">\n' hlst.append('<?xml version="1.0" encoding="utf-8"?>\n')
htmlstr += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' hlst.append('<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.1 Strict//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11-strict.dtd">\n')
htmlstr += '<head>\n' hlst.append('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n')
htmlstr += '<meta http-equiv="content-type" content="text/html; charset=utf-8"/>\n' hlst.append('<head>\n')
htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n' hlst.append('<meta http-equiv="content-type" content="text/html; charset=utf-8"/>\n')
htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' hlst.append('<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n')
htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' hlst.append('<meta name="Author" content="' + meta_array['Authors'] + '" />\n')
hlst.append('<meta name="Title" content="' + meta_array['Title'] + '" />\n')
if 'ASIN' in meta_array: if 'ASIN' in meta_array:
htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' hlst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
if 'GUID' in meta_array: if 'GUID' in meta_array:
htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' hlst.append('<meta name="GUID" content="' + meta_array['GUID'] + '" />\n')
htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n' hlst.append('<link href="style.css" rel="stylesheet" type="text/css" />\n')
htmlstr += '</head>\n<body>\n' hlst.append('</head>\n<body>\n')
print 'Processing Pages' print 'Processing Pages'
# Books are at 1440 DPI. This is rendering at twice that size for # Books are at 1440 DPI. This is rendering at twice that size for
@@ -471,6 +491,7 @@ def generateBook(bookDir, raw, fixedimage):
numfiles = len(filenames) numfiles = len(filenames)
xmllst = [] xmllst = []
elst = []
for filename in filenames: for filename in filenames:
# print ' ', filename # print ' ', filename
@@ -481,45 +502,51 @@ def generateBook(bookDir, raw, fixedimage):
# keep flat_xml for later svg processing # keep flat_xml for later svg processing
xmllst.append(flat_xml) xmllst.append(flat_xml)
xname = os.path.join(xmlDir, filename.replace('.dat','.xml')) if buildXML:
file(xname, 'wb').write(convert2xml.getXML(dict, fname)) xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
file(xname, 'wb').write(convert2xml.getXML(dict, fname))
# first get the html # first get the html
pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage) pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage)
tocentries += tocinfo elst.append(tocinfo)
htmlstr += pagehtml hlst.append(pagehtml)
# finish up the html string and output it # finish up the html string and output it
htmlstr += '</body>\n</html>\n' hlst.append('</body>\n</html>\n')
htmlstr = "".join(hlst)
hlst = None
file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr) file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr)
print " " print " "
print 'Extracting Table of Contents from Amazon OCR' print 'Extracting Table of Contents from Amazon OCR'
# first create a table of contents file for the svg images # first create a table of contents file for the svg images
tochtml = '<?xml version="1.0" encoding="utf-8"?>\n' tlst = []
tochtml += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' tlst.append('<?xml version="1.0" encoding="utf-8"?>\n')
tochtml += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >' tlst.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n')
tochtml += '<head>\n' tlst.append('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >')
tochtml += '<title>' + meta_array['Title'] + '</title>\n' tlst.append('<head>\n')
tochtml += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' tlst.append('<title>' + meta_array['Title'] + '</title>\n')
tochtml += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' tlst.append('<meta name="Author" content="' + meta_array['Authors'] + '" />\n')
tlst.append('<meta name="Title" content="' + meta_array['Title'] + '" />\n')
if 'ASIN' in meta_array: if 'ASIN' in meta_array:
tochtml += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' tlst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
if 'GUID' in meta_array: if 'GUID' in meta_array:
tochtml += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' tlst.append('<meta name="GUID" content="' + meta_array['GUID'] + '" />\n')
tochtml += '</head>\n' tlst.append('</head>\n')
tochtml += '<body>\n' tlst.append('<body>\n')
tochtml += '<h2>Table of Contents</h2>\n' tlst.append('<h2>Table of Contents</h2>\n')
start = pageidnums[0] start = pageidnums[0]
if (raw): if (raw):
startname = 'page%04d.svg' % start startname = 'page%04d.svg' % start
else: else:
startname = 'page%04d.xhtml' % start startname = 'page%04d.xhtml' % start
tochtml += '<h3><a href="' + startname + '">Start of Book</a></h3>\n' tlst.append('<h3><a href="' + startname + '">Start of Book</a></h3>\n')
# build up a table of contents for the svg xhtml output # build up a table of contents for the svg xhtml output
tocentries = "".join(elst)
elst = None
toclst = tocentries.split('\n') toclst = tocentries.split('\n')
toclst.pop() toclst.pop()
for entry in toclst: for entry in toclst:
@@ -530,30 +557,32 @@ def generateBook(bookDir, raw, fixedimage):
fname = 'page%04d.svg' % id fname = 'page%04d.svg' % id
else: else:
fname = 'page%04d.xhtml' % id fname = 'page%04d.xhtml' % id
tochtml += '<h3><a href="'+ fname + '">' + title + '</a></h3>\n' tlst.append('<h3><a href="'+ fname + '">' + title + '</a></h3>\n')
tochtml += '</body>\n' tlst.append('</body>\n')
tochtml += '</html>\n' tlst.append('</html>\n')
tochtml = "".join(tlst)
file(os.path.join(svgDir, 'toc.xhtml'), 'wb').write(tochtml) file(os.path.join(svgDir, 'toc.xhtml'), 'wb').write(tochtml)
# now create index_svg.xhtml that points to all required files # now create index_svg.xhtml that points to all required files
svgindex = '<?xml version="1.0" encoding="utf-8"?>\n' slst = []
svgindex += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' slst.append('<?xml version="1.0" encoding="utf-8"?>\n')
svgindex += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >' slst.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n')
svgindex += '<head>\n' slst.append('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >')
svgindex += '<title>' + meta_array['Title'] + '</title>\n' slst.append('<head>\n')
svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' slst.append('<title>' + meta_array['Title'] + '</title>\n')
svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' slst.append('<meta name="Author" content="' + meta_array['Authors'] + '" />\n')
slst.append('<meta name="Title" content="' + meta_array['Title'] + '" />\n')
if 'ASIN' in meta_array: if 'ASIN' in meta_array:
svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' slst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
if 'GUID' in meta_array: if 'GUID' in meta_array:
svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' slst.append('<meta name="GUID" content="' + meta_array['GUID'] + '" />\n')
svgindex += '</head>\n' slst.append('</head>\n')
svgindex += '<body>\n' slst.append('<body>\n')
print "Building svg images of each book page" print "Building svg images of each book page"
svgindex += '<h2>List of Pages</h2>\n' slst.append('<h2>List of Pages</h2>\n')
svgindex += '<div>\n' slst.append('<div>\n')
idlst = sorted(pageIDMap.keys()) idlst = sorted(pageIDMap.keys())
numids = len(idlst) numids = len(idlst)
cnt = len(idlst) cnt = len(idlst)
@@ -566,49 +595,54 @@ def generateBook(bookDir, raw, fixedimage):
nextid = None nextid = None
print '.', print '.',
pagelst = pageIDMap[pageid] pagelst = pageIDMap[pageid]
flat_svg = '' flst = []
for page in pagelst: for page in pagelst:
flat_svg += xmllst[page] flst.append(xmllst[page])
flat_svg = "".join(flst)
flst=None
svgxml = flatxml2svg.convert2SVG(gd, flat_svg, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi) svgxml = flatxml2svg.convert2SVG(gd, flat_svg, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi)
if (raw) : if (raw) :
pfile = open(os.path.join(svgDir,'page%04d.svg' % pageid),'w') pfile = open(os.path.join(svgDir,'page%04d.svg' % pageid),'w')
svgindex += '<a href="svg/page%04d.svg">Page %d</a>\n' % (pageid, pageid) slst.append('<a href="svg/page%04d.svg">Page %d</a>\n' % (pageid, pageid))
else : else :
pfile = open(os.path.join(svgDir,'page%04d.xhtml' % pageid), 'w') pfile = open(os.path.join(svgDir,'page%04d.xhtml' % pageid), 'w')
svgindex += '<a href="svg/page%04d.xhtml">Page %d</a>\n' % (pageid, pageid) slst.append('<a href="svg/page%04d.xhtml">Page %d</a>\n' % (pageid, pageid))
previd = pageid previd = pageid
pfile.write(svgxml) pfile.write(svgxml)
pfile.close() pfile.close()
counter += 1 counter += 1
svgindex += '</div>\n' slst.append('</div>\n')
svgindex += '<h2><a href="svg/toc.xhtml">Table of Contents</a></h2>\n' slst.append('<h2><a href="svg/toc.xhtml">Table of Contents</a></h2>\n')
svgindex += '</body>\n</html>\n' slst.append('</body>\n</html>\n')
svgindex = "".join(slst)
slst = None
file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex) file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex)
print " " print " "
# build the opf file # build the opf file
opfname = os.path.join(bookDir, 'book.opf') opfname = os.path.join(bookDir, 'book.opf')
opfstr = '<?xml version="1.0" encoding="utf-8"?>\n' olst = []
opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n' olst.append('<?xml version="1.0" encoding="utf-8"?>\n')
olst.append('<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n')
# adding metadata # adding metadata
opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n' olst.append(' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n')
if 'GUID' in meta_array: if 'GUID' in meta_array:
opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n' olst.append(' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n')
if 'ASIN' in meta_array: if 'ASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n' olst.append(' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n')
if 'oASIN' in meta_array: if 'oASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n' olst.append(' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n')
opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n' olst.append(' <dc:title>' + meta_array['Title'] + '</dc:title>\n')
opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n' olst.append(' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n')
opfstr += ' <dc:language>en</dc:language>\n' olst.append(' <dc:language>en</dc:language>\n')
opfstr += ' <dc:date>' + meta_array['UpdateTime'] + '</dc:date>\n' olst.append(' <dc:date>' + meta_array['UpdateTime'] + '</dc:date>\n')
if isCover: if isCover:
opfstr += ' <meta name="cover" content="bookcover"/>\n' olst.append(' <meta name="cover" content="bookcover"/>\n')
opfstr += ' </metadata>\n' olst.append(' </metadata>\n')
opfstr += '<manifest>\n' olst.append('<manifest>\n')
opfstr += ' <item id="book" href="book.html" media-type="application/xhtml+xml"/>\n' olst.append(' <item id="book" href="book.html" media-type="application/xhtml+xml"/>\n')
opfstr += ' <item id="stylesheet" href="style.css" media-type="text/css"/>\n' olst.append(' <item id="stylesheet" href="style.css" media-type="text/css"/>\n')
# adding image files to manifest # adding image files to manifest
filenames = os.listdir(imgDir) filenames = os.listdir(imgDir)
filenames = sorted(filenames) filenames = sorted(filenames)
@@ -618,17 +652,19 @@ def generateBook(bookDir, raw, fixedimage):
imgext = 'jpeg' imgext = 'jpeg'
if imgext == '.svg': if imgext == '.svg':
imgext = 'svg+xml' imgext = 'svg+xml'
opfstr += ' <item id="' + imgname + '" href="img/' + filename + '" media-type="image/' + imgext + '"/>\n' olst.append(' <item id="' + imgname + '" href="img/' + filename + '" media-type="image/' + imgext + '"/>\n')
if isCover: if isCover:
opfstr += ' <item id="bookcover" href="cover.jpg" media-type="image/jpeg" />\n' olst.append(' <item id="bookcover" href="cover.jpg" media-type="image/jpeg" />\n')
opfstr += '</manifest>\n' olst.append('</manifest>\n')
# adding spine # adding spine
opfstr += '<spine>\n <itemref idref="book" />\n</spine>\n' olst.append('<spine>\n <itemref idref="book" />\n</spine>\n')
if isCover: if isCover:
opfstr += ' <guide>\n' olst.append(' <guide>\n')
opfstr += ' <reference href="cover.jpg" type="cover" title="Cover"/>\n' olst.append(' <reference href="cover.jpg" type="cover" title="Cover"/>\n')
opfstr += ' </guide>\n' olst.append(' </guide>\n')
opfstr += '</package>\n' olst.append('</package>\n')
opfstr = "".join(olst)
olst = None
file(opfname, 'wb').write(opfstr) file(opfname, 'wb').write(opfstr)
print 'Processing Complete' print 'Processing Complete'
@@ -649,7 +685,6 @@ def usage():
def main(argv): def main(argv):
bookDir = '' bookDir = ''
if len(argv) == 0: if len(argv) == 0:
argv = sys.argv argv = sys.argv

View File

@@ -0,0 +1,77 @@
#!/usr/bin/python
#
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
# Changelog
# 1.00 - Initial version
__version__ = '1.00'
import sys
class Unbuffered:
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
sys.stdout=Unbuffered(sys.stdout)
import os
import struct
import binascii
import kgenpids
import topazextract
import mobidedrm
from alfcrypto import Pukall_Cipher
class DrmException(Exception):
pass
def getK4PCpids(path_to_ebook):
# Return Kindle4PC PIDs. Assumes that the caller checked that we are not on Linux, which will raise an exception
mobi = True
magic3 = file(path_to_ebook,'rb').read(3)
if magic3 == 'TPZ':
mobi = False
if mobi:
mb = mobidedrm.MobiBook(path_to_ebook,False)
else:
mb = topazextract.TopazBook(path_to_ebook)
md1, md2 = mb.getPIDMetaInfo()
return kgenpids.getPidList(md1, md2, True, [], [], [])
def main(argv=sys.argv):
print ('getk4pcpids.py v%(__version__)s. '
'Copyright 2012 Apprentic Alf' % globals())
if len(argv)<2 or len(argv)>3:
print "Gets the possible book-specific PIDs from K4PC for a particular book"
print "Usage:"
print " %s <bookfile> [<outfile>]" % sys.argv[0]
return 1
else:
infile = argv[1]
try:
pidlist = getK4PCpids(infile)
except DrmException, e:
print "Error: %s" % e
return 1
pidstring = ','.join(pidlist)
print "Possible PIDs are: ", pidstring
if len(argv) is 3:
outfile = argv[2]
file(outfile, 'w').write(pidstring)
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -74,7 +74,7 @@ def _load_crypto_libcrypto():
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
c_int]) c_int])
class AES(object): class AES(object):
def __init__(self, userkey, iv): def __init__(self, userkey, iv):
self._blocksize = len(userkey) self._blocksize = len(userkey)
self._iv = iv self._iv = iv
key = self._key = AES_KEY() key = self._key = AES_KEY()
@@ -82,7 +82,7 @@ def _load_crypto_libcrypto():
if rv < 0: if rv < 0:
raise IGNOBLEError('Failed to initialize AES Encrypt key') raise IGNOBLEError('Failed to initialize AES Encrypt key')
def encrypt(self, data): def encrypt(self, data):
out = create_string_buffer(len(data)) out = create_string_buffer(len(data))
rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1) rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1)
if rv == 0: if rv == 0:

View File

@@ -427,8 +427,8 @@ def extractKeyfile(keypath):
print "Key generation Error: " + str(e) print "Key generation Error: " + str(e)
return 1 return 1
except Exception, e: except Exception, e:
print "General Error: " + str(e) print "General Error: " + str(e)
return 1 return 1
if not success: if not success:
return 1 return 1
return 0 return 0

View File

@@ -1,5 +1,5 @@
#! /usr/bin/env python #! /usr/bin/env python
# ineptpdf.pyw, version 7.9 # ineptpdf.pyw, version 7.11
from __future__ import with_statement from __future__ import with_statement
@@ -34,6 +34,8 @@ from __future__ import with_statement
# 7.7 - On Windows try PyCrypto first and OpenSSL next # 7.7 - On Windows try PyCrypto first and OpenSSL next
# 7.8 - Modify interface to allow use of import # 7.8 - Modify interface to allow use of import
# 7.9 - Bug fix for some session key errors when len(bookkey) > length required # 7.9 - Bug fix for some session key errors when len(bookkey) > length required
# 7.10 - Various tweaks to fix minor problems.
# 7.11 - More tweaks to fix minor problems.
""" """
Decrypts Adobe ADEPT-encrypted PDF files. Decrypts Adobe ADEPT-encrypted PDF files.
@@ -293,6 +295,7 @@ def _load_crypto_pycrypto():
return self._arc4.decrypt(data) return self._arc4.decrypt(data)
class AES(object): class AES(object):
MODE_CBC = _AES.MODE_CBC
@classmethod @classmethod
def new(cls, userkey, mode, iv): def new(cls, userkey, mode, iv):
self = AES() self = AES()
@@ -1029,25 +1032,25 @@ def stream_value(x):
# ascii85decode(data) # ascii85decode(data)
def ascii85decode(data): def ascii85decode(data):
n = b = 0 n = b = 0
out = '' out = ''
for c in data: for c in data:
if '!' <= c and c <= 'u': if '!' <= c and c <= 'u':
n += 1 n += 1
b = b*85+(ord(c)-33) b = b*85+(ord(c)-33)
if n == 5: if n == 5:
out += struct.pack('>L',b) out += struct.pack('>L',b)
n = b = 0 n = b = 0
elif c == 'z': elif c == 'z':
assert n == 0 assert n == 0
out += '\0\0\0\0' out += '\0\0\0\0'
elif c == '~': elif c == '~':
if n: if n:
for _ in range(5-n): for _ in range(5-n):
b = b*85+84 b = b*85+84
out += struct.pack('>L',b)[:n-1] out += struct.pack('>L',b)[:n-1]
break break
return out return out
## PDFStream type ## PDFStream type
@@ -2073,7 +2076,7 @@ class PDFSerializer(object):
### are no longer useful, as we have extracted all objects from ### are no longer useful, as we have extracted all objects from
### them. Therefore leave them out from the output. ### them. Therefore leave them out from the output.
if obj.dic.get('Type') == LITERAL_OBJSTM and not gen_xref_stm: if obj.dic.get('Type') == LITERAL_OBJSTM and not gen_xref_stm:
self.write('(deleted)') self.write('(deleted)')
else: else:
data = obj.get_decdata() data = obj.get_decdata()
self.serialize_object(obj.dic) self.serialize_object(obj.dic)
@@ -2199,11 +2202,19 @@ class DecryptionDialog(Tkinter.Frame):
def decryptBook(keypath, inpath, outpath): def decryptBook(keypath, inpath, outpath):
with open(inpath, 'rb') as inf: with open(inpath, 'rb') as inf:
serializer = PDFSerializer(inf, keypath) try:
serializer = PDFSerializer(inf, keypath)
except:
print "Error serializing pdf. Probably wrong key."
return 1
# hope this will fix the 'bad file descriptor' problem # hope this will fix the 'bad file descriptor' problem
with open(outpath, 'wb') as outf: with open(outpath, 'wb') as outf:
# help construct to make sure the method runs to the end # help construct to make sure the method runs to the end
serializer.dump(outf) try:
serializer.dump(outf)
except:
print "error writing pdf."
return 1
return 0 return 0

View File

@@ -265,8 +265,8 @@ def findNameForHash(hash):
result = "" result = ""
for name in names: for name in names:
if hash == encodeHash(name, charMap2): if hash == encodeHash(name, charMap2):
result = name result = name
break break
return result return result
# Print all the records from the kindle.info file (option -i) # Print all the records from the kindle.info file (option -i)
@@ -293,9 +293,9 @@ def getTwoBitsFromBitField(bitField,offset):
# Returns the six bits at offset from a bit field # Returns the six bits at offset from a bit field
def getSixBitsFromBitField(bitField,offset): def getSixBitsFromBitField(bitField,offset):
offset *= 3 offset *= 3
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2) value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
return value return value
# 8 bits to six bits encoding from hash to generate PID string # 8 bits to six bits encoding from hash to generate PID string
def encodePID(hash): def encodePID(hash):

View File

@@ -17,7 +17,7 @@ from __future__ import with_statement
# and many many others # and many many others
__version__ = '3.9' __version__ = '4.4'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -34,6 +34,8 @@ import string
import re import re
import traceback import traceback
buildXML = False
class DrmException(Exception): class DrmException(Exception):
pass pass
@@ -56,7 +58,7 @@ else:
# borrowed from calibre from calibre/src/calibre/__init__.py # borrowed from calibre from calibre/src/calibre/__init__.py
# added in removal of non-printing chars # added in removal of non-printing chars
# and removal of . at start # and removal of . at start
# convert spaces to underscores # convert underscores to spaces (we're OK with spaces in file names)
def cleanup_name(name): def cleanup_name(name):
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]') _filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
substitute='_' substitute='_'
@@ -71,10 +73,12 @@ def cleanup_name(name):
# Mac and Unix don't like file names that begin with a full stop # Mac and Unix don't like file names that begin with a full stop
if len(one) > 0 and one[0] == '.': if len(one) > 0 and one[0] == '.':
one = substitute+one[1:] one = substitute+one[1:]
one = one.replace(' ','_') one = one.replace('_',' ')
return one return one
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
global buildXML
# handle the obvious cases at the beginning # handle the obvious cases at the beginning
if not os.path.isfile(infile): if not os.path.isfile(infile):
print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: Input file does not exist" print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: Input file does not exist"
@@ -95,11 +99,20 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
title = mb.getBookTitle() title = mb.getBookTitle()
print "Processing Book: ", title print "Processing Book: ", title
filenametitle = cleanup_name(title) filenametitle = cleanup_name(title)
outfilename = bookname outfilename = cleanup_name(bookname)
if len(outfilename)<=8 or len(filenametitle)<=8:
outfilename = outfilename + "_" + filenametitle # generate 'sensible' filename, that will sort with the original name,
elif outfilename[:8] != filenametitle[:8]: # but is close to the name from the file.
outfilename = outfilename[:8] + "_" + filenametitle outlength = len(outfilename)
comparelength = min(8,min(outlength,len(filenametitle)))
copylength = min(max(outfilename.find(' '),8),len(outfilename))
if outlength==0:
outfilename = filenametitle
elif comparelength > 0:
if outfilename[:comparelength] == filenametitle[:comparelength]:
outfilename = filenametitle
else:
outfilename = outfilename[:copylength] + " " + filenametitle
# avoid excessively long file names # avoid excessively long file names
if len(outfilename)>150: if len(outfilename)>150:
@@ -125,6 +138,8 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
if mobi: if mobi:
if mb.getPrintReplica(): if mb.getPrintReplica():
outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw4') outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw4')
elif mb.getMobiVersion() >= 8:
outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw3')
else: else:
outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi') outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi')
mb.getMobiFile(outfile) mb.getMobiFile(outfile)
@@ -139,9 +154,10 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip') zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip')
mb.getSVGZip(zipname) mb.getSVGZip(zipname)
print " Creating XML ZIP Archive" if buildXML:
zipname = os.path.join(outdir, outfilename + '_XML' + '.zip') print " Creating XML ZIP Archive"
mb.getXMLZip(zipname) zipname = os.path.join(outdir, outfilename + '_XML' + '.zip')
mb.getXMLZip(zipname)
# remove internal temporary directory of Topaz pieces # remove internal temporary directory of Topaz pieces
mb.cleanup() mb.cleanup()
@@ -166,7 +182,7 @@ def main(argv=sys.argv):
pids = [] pids = []
print ('K4MobiDeDrm v%(__version__)s ' print ('K4MobiDeDrm v%(__version__)s '
'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals()) 'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
try: try:
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:") opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
@@ -195,8 +211,8 @@ def main(argv=sys.argv):
# try with built in Kindle Info files # try with built in Kindle Info files
k4 = True k4 = True
if sys.platform.startswith('linux'): if sys.platform.startswith('linux'):
k4 = False k4 = False
kInfoFiles = None kInfoFiles = None
infile = args[0] infile = args[0]
outdir = args[1] outdir = args[1]
return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids) return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids)
@@ -205,4 +221,3 @@ def main(argv=sys.argv):
if __name__ == '__main__': if __name__ == '__main__':
sys.stdout=Unbuffered(sys.stdout) sys.stdout=Unbuffered(sys.stdout)
sys.exit(main()) sys.exit(main())

View File

@@ -5,7 +5,8 @@ from __future__ import with_statement
import sys import sys
import os import os
import os.path import os.path
import re
import copy
import subprocess import subprocess
from struct import pack, unpack, unpack_from from struct import pack, unpack, unpack_from
@@ -24,6 +25,25 @@ def _load_crypto_libcrypto():
raise DrmException('libcrypto not found') raise DrmException('libcrypto not found')
libcrypto = CDLL(libcrypto) libcrypto = CDLL(libcrypto)
# From OpenSSL's crypto aes header
#
# AES_ENCRYPT 1
# AES_DECRYPT 0
# AES_MAXNR 14 (in bytes)
# AES_BLOCK_SIZE 16 (in bytes)
#
# struct aes_key_st {
# unsigned long rd_key[4 *(AES_MAXNR + 1)];
# int rounds;
# };
# typedef struct aes_key_st AES_KEY;
#
# int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
#
# note: the ivec string, and output buffer are both mutable
# void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
# const unsigned long length, const AES_KEY *key, unsigned char *ivec, const int enc);
AES_MAXNR = 14 AES_MAXNR = 14
c_char_pp = POINTER(c_char_p) c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int) c_int_p = POINTER(c_int)
@@ -42,6 +62,12 @@ def _load_crypto_libcrypto():
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p]) AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
# From OpenSSL's Crypto evp/p5_crpt2.c
#
# int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen,
# const unsigned char *salt, int saltlen, int iter,
# int keylen, unsigned char *out);
PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
[c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p]) [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
@@ -49,7 +75,7 @@ def _load_crypto_libcrypto():
def __init__(self): def __init__(self):
self._blocksize = 0 self._blocksize = 0
self._keyctx = None self._keyctx = None
self.iv = 0 self._iv = 0
def set_decrypt_key(self, userkey, iv): def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey) self._blocksize = len(userkey)
@@ -57,14 +83,17 @@ def _load_crypto_libcrypto():
raise DrmException('AES improper key used') raise DrmException('AES improper key used')
return return
keyctx = self._keyctx = AES_KEY() keyctx = self._keyctx = AES_KEY()
self.iv = iv self._iv = iv
self._userkey = userkey
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0: if rv < 0:
raise DrmException('Failed to initialize AES key') raise DrmException('Failed to initialize AES key')
def decrypt(self, data): def decrypt(self, data):
out = create_string_buffer(len(data)) out = create_string_buffer(len(data))
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0) mutable_iv = create_string_buffer(self._iv, len(self._iv))
keyctx = self._keyctx
rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
if rv == 0: if rv == 0:
raise DrmException('AES decryption failed') raise DrmException('AES decryption failed')
return out.raw return out.raw
@@ -113,11 +142,15 @@ def SHA256(message):
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
# For kinf approach of K4PC/K4Mac # For kinf approach of K4Mac 1.6.X or later
# On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" # On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
# For Mac they seem to re-use charMap2 here # For Mac they seem to re-use charMap2 here
charMap5 = charMap2 charMap5 = charMap2
# new in K4M 1.9.X
testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
def encode(data, map): def encode(data, map):
result = "" result = ""
for char in data: for char in data:
@@ -144,7 +177,7 @@ def decode(data,map):
result += pack("B",value) result += pack("B",value)
return result return result
# For .kinf approach of K4PC and now K4Mac # For K4M 1.6.X and later
# generate table of prime number less than or equal to int n # generate table of prime number less than or equal to int n
def primes(n): def primes(n):
if n==2: return [2] if n==2: return [2]
@@ -200,7 +233,7 @@ def GetVolumeSerialNumber():
def GetUserHomeAppSupKindleDirParitionName(): def GetUserHomeAppSupKindleDirParitionName():
home = os.getenv('HOME') home = os.getenv('HOME')
dpath = home + '/Library/Application Support/Kindle' dpath = home + '/Library'
cmdline = '/sbin/mount' cmdline = '/sbin/mount'
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
@@ -320,33 +353,15 @@ def GetUserName():
username = os.getenv('USER') username = os.getenv('USER')
return username return username
# implements an Pseudo Mac Version of Windows built-in Crypto routine
# used by Kindle for Mac versions < 1.6.0
def CryptUnprotectData(encryptedData):
sernum = GetVolumeSerialNumber()
if sernum == '':
sernum = '9999999999'
sp = sernum + '!@#' + GetUserName()
passwdData = encode(SHA256(sp),charMap1)
salt = '16743'
iter = 0x3e8
keylen = 0x80
crp = LibCrypto()
key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
key = key_iv[0:32]
iv = key_iv[32:48]
crp.set_decrypt_key(key,iv)
cleartext = crp.decrypt(encryptedData)
cleartext = decode(cleartext,charMap1)
return cleartext
def isNewInstall(): def isNewInstall():
home = os.getenv('HOME') home = os.getenv('HOME')
# soccer game fan anyone # soccer game fan anyone
dpath = home + '/Library/Application Support/Kindle/storage/.pes2011' dpath = home + '/Library/Application Support/Kindle/storage/.pes2011'
# print dpath, os.path.exists(dpath) # print dpath, os.path.exists(dpath)
if os.path.exists(dpath):
return True
dpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.pes2011'
# print dpath, os.path.exists(dpath)
if os.path.exists(dpath): if os.path.exists(dpath):
return True return True
return False return False
@@ -374,45 +389,136 @@ def GetIDString():
if isNewInstall(): if isNewInstall():
mungedmac = GetMACAddressMunged() mungedmac = GetMACAddressMunged()
if len(mungedmac) > 7: if len(mungedmac) > 7:
print('Using Munged MAC Address for ID: '+mungedmac)
return mungedmac return mungedmac
sernum = GetVolumeSerialNumber() sernum = GetVolumeSerialNumber()
if len(sernum) > 7: if len(sernum) > 7:
print('Using Volume Serial Number for ID: '+sernum)
return sernum return sernum
diskpart = GetUserHomeAppSupKindleDirParitionName() diskpart = GetUserHomeAppSupKindleDirParitionName()
uuidnum = GetDiskPartitionUUID(diskpart) uuidnum = GetDiskPartitionUUID(diskpart)
if len(uuidnum) > 7: if len(uuidnum) > 7:
print('Using Disk Partition UUID for ID: '+uuidnum)
return uuidnum return uuidnum
mungedmac = GetMACAddressMunged() mungedmac = GetMACAddressMunged()
if len(mungedmac) > 7: if len(mungedmac) > 7:
print('Using Munged MAC Address for ID: '+mungedmac)
return mungedmac return mungedmac
print('Using Fixed constant 9999999999 for ID.')
return '9999999999' return '9999999999'
# implements an Pseudo Mac Version of Windows built-in Crypto routine
# used by Kindle for Mac versions < 1.6.0
class CryptUnprotectData(object):
def __init__(self):
sernum = GetVolumeSerialNumber()
if sernum == '':
sernum = '9999999999'
sp = sernum + '!@#' + GetUserName()
passwdData = encode(SHA256(sp),charMap1)
salt = '16743'
self.crp = LibCrypto()
iter = 0x3e8
keylen = 0x80
key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
self.key = key_iv[0:32]
self.iv = key_iv[32:48]
self.crp.set_decrypt_key(self.key, self.iv)
def decrypt(self, encryptedData):
cleartext = self.crp.decrypt(encryptedData)
cleartext = decode(cleartext,charMap1)
return cleartext
# implements an Pseudo Mac Version of Windows built-in Crypto routine # implements an Pseudo Mac Version of Windows built-in Crypto routine
# used for Kindle for Mac Versions >= 1.6.0 # used for Kindle for Mac Versions >= 1.6.0
def CryptUnprotectDataV2(encryptedData): class CryptUnprotectDataV2(object):
sp = GetUserName() + ':&%:' + GetIDString() def __init__(self):
passwdData = encode(SHA256(sp),charMap5) sp = GetUserName() + ':&%:' + GetIDString()
# salt generation as per the code passwdData = encode(SHA256(sp),charMap5)
salt = 0x0512981d * 2 * 1 * 1 # salt generation as per the code
salt = str(salt) + GetUserName() salt = 0x0512981d * 2 * 1 * 1
salt = encode(salt,charMap5) salt = str(salt) + GetUserName()
salt = encode(salt,charMap5)
self.crp = LibCrypto()
iter = 0x800
keylen = 0x400
key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
self.key = key_iv[0:32]
self.iv = key_iv[32:48]
self.crp.set_decrypt_key(self.key, self.iv)
def decrypt(self, encryptedData):
cleartext = self.crp.decrypt(encryptedData)
cleartext = decode(cleartext, charMap5)
return cleartext
# unprotect the new header blob in .kinf2011
# used in Kindle for Mac Version >= 1.9.0
def UnprotectHeaderData(encryptedData):
passwdData = 'header_key_data'
salt = 'HEADER.2011'
iter = 0x80
keylen = 0x100
crp = LibCrypto() crp = LibCrypto()
iter = 0x800
keylen = 0x400
key_iv = crp.keyivgen(passwdData, salt, iter, keylen) key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
key = key_iv[0:32] key = key_iv[0:32]
iv = key_iv[32:48] iv = key_iv[32:48]
crp.set_decrypt_key(key,iv) crp.set_decrypt_key(key,iv)
cleartext = crp.decrypt(encryptedData) cleartext = crp.decrypt(encryptedData)
cleartext = decode(cleartext, charMap5)
return cleartext return cleartext
# implements an Pseudo Mac Version of Windows built-in Crypto routine
# used for Kindle for Mac Versions >= 1.9.0
class CryptUnprotectDataV3(object):
def __init__(self, entropy):
sp = GetUserName() + '+@#$%+' + GetIDString()
passwdData = encode(SHA256(sp),charMap2)
salt = entropy
self.crp = LibCrypto()
iter = 0x800
keylen = 0x400
key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
self.key = key_iv[0:32]
self.iv = key_iv[32:48]
self.crp.set_decrypt_key(self.key, self.iv)
def decrypt(self, encryptedData):
cleartext = self.crp.decrypt(encryptedData)
cleartext = decode(cleartext, charMap2)
return cleartext
# Locate the .kindle-info files # Locate the .kindle-info files
def getKindleInfoFiles(kInfoFiles): def getKindleInfoFiles(kInfoFiles):
# first search for current .kindle-info files
home = os.getenv('HOME') home = os.getenv('HOME')
# search for any .kinf2011 files in new location (Sep 2012)
cmdline = 'find "' + home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support" -name ".kinf2011"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
out1, out2 = p1.communicate()
reslst = out1.split('\n')
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found k4Mac kinf2011 file: ' + resline)
found = True
# search for any .kinf2011 files
cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
out1, out2 = p1.communicate()
reslst = out1.split('\n')
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found k4Mac kinf2011 file: ' + resline)
found = True
# search for any .kindle-info files
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"' cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
@@ -423,8 +529,9 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst: for resline in reslst:
if os.path.isfile(resline): if os.path.isfile(resline):
kInfoFiles.append(resline) kInfoFiles.append(resline)
print('Found K4Mac kindle-info file: ' + resline)
found = True found = True
# add any .kinf files # search for any .rainier*-kinf files
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"' cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
@@ -433,9 +540,10 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst: for resline in reslst:
if os.path.isfile(resline): if os.path.isfile(resline):
kInfoFiles.append(resline) kInfoFiles.append(resline)
print('Found k4Mac kinf file: ' + resline)
found = True found = True
if not found: if not found:
print('No kindle-info files have been found.') print('No k4Mac kindle-info/kinf/kinf2011 files have been found.')
return kInfoFiles return kInfoFiles
# determine type of kindle info provided and return a # determine type of kindle info provided and return a
@@ -449,7 +557,9 @@ def getDBfromFile(kInfoFile):
data = infoReader.read() data = infoReader.read()
if data.find('[') != -1 : if data.find('[') != -1 :
# older style kindle-info file # older style kindle-info file
cud = CryptUnprotectData()
items = data.split('[') items = data.split('[')
for item in items: for item in items:
if item != '': if item != '':
@@ -462,19 +572,111 @@ def getDBfromFile(kInfoFile):
if keyname == "unknown": if keyname == "unknown":
keyname = keyhash keyname = keyhash
encryptedValue = decode(rawdata,charMap2) encryptedValue = decode(rawdata,charMap2)
cleartext = CryptUnprotectData(encryptedValue) cleartext = cud.decrypt(encryptedValue)
DB[keyname] = cleartext DB[keyname] = cleartext
cnt = cnt + 1 cnt = cnt + 1
if cnt == 0: if cnt == 0:
DB = None DB = None
return DB return DB
# else newer style .kinf file used by K4Mac >= 1.6.0 if hdr == '/':
# the .kinf file uses "/" to separate it into records
# so remove the trailing "/" to make it easy to use split # else newer style .kinf file used by K4Mac >= 1.6.0
# the .kinf file uses "/" to separate it into records
# so remove the trailing "/" to make it easy to use split
data = data[:-1]
items = data.split('/')
cud = CryptUnprotectDataV2()
# loop through the item records until all are processed
while len(items) > 0:
# get the first item record
item = items.pop(0)
# the first 32 chars of the first record of a group
# is the MD5 hash of the key name encoded by charMap5
keyhash = item[0:32]
keyname = "unknown"
# the raw keyhash string is also used to create entropy for the actual
# CryptProtectData Blob that represents that keys contents
# "entropy" not used for K4Mac only K4PC
# entropy = SHA1(keyhash)
# the remainder of the first record when decoded with charMap5
# has the ':' split char followed by the string representation
# of the number of records that follow
# and make up the contents
srcnt = decode(item[34:],charMap5)
rcnt = int(srcnt)
# read and store in rcnt records of data
# that make up the contents value
edlst = []
for i in xrange(rcnt):
item = items.pop(0)
edlst.append(item)
keyname = "unknown"
for name in names:
if encodeHash(name,charMap5) == keyhash:
keyname = name
break
if keyname == "unknown":
keyname = keyhash
# the charMap5 encoded contents data has had a length
# of chars (always odd) cut off of the front and moved
# to the end to prevent decoding using charMap5 from
# working properly, and thereby preventing the ensuing
# CryptUnprotectData call from succeeding.
# The offset into the charMap5 encoded contents seems to be:
# len(contents) - largest prime number less than or equal to int(len(content)/3)
# (in other words split "about" 2/3rds of the way through)
# move first offsets chars to end to align for decode by charMap5
encdata = "".join(edlst)
contlen = len(encdata)
# now properly split and recombine
# by moving noffset chars from the start of the
# string to the end of the string
noffset = contlen - primes(int(contlen/3))[-1]
pfx = encdata[0:noffset]
encdata = encdata[noffset:]
encdata = encdata + pfx
# decode using charMap5 to get the CryptProtect Data
encryptedValue = decode(encdata,charMap5)
cleartext = cud.decrypt(encryptedValue)
DB[keyname] = cleartext
cnt = cnt + 1
if cnt == 0:
DB = None
return DB
# the latest .kinf2011 version for K4M 1.9.1
# put back the hdr char, it is needed
data = hdr + data
data = data[:-1] data = data[:-1]
items = data.split('/') items = data.split('/')
# the headerblob is the encrypted information needed to build the entropy string
headerblob = items.pop(0)
encryptedValue = decode(headerblob, charMap1)
cleartext = UnprotectHeaderData(encryptedValue)
# now extract the pieces in the same way
# this version is different from K4PC it scales the build number by multipying by 735
pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
for m in re.finditer(pattern, cleartext):
entropy = str(int(m.group(2)) * 0x2df) + m.group(4)
cud = CryptUnprotectDataV3(entropy)
# loop through the item records until all are processed # loop through the item records until all are processed
while len(items) > 0: while len(items) > 0:
@@ -486,10 +688,9 @@ def getDBfromFile(kInfoFile):
keyhash = item[0:32] keyhash = item[0:32]
keyname = "unknown" keyname = "unknown"
# the raw keyhash string is also used to create entropy for the actual # unlike K4PC the keyhash is not used in generating entropy
# CryptProtectData Blob that represents that keys contents # entropy = SHA1(keyhash) + added_entropy
# "entropy" not used for K4Mac only K4PC # entropy = added_entropy
# entropy = SHA1(keyhash)
# the remainder of the first record when decoded with charMap5 # the remainder of the first record when decoded with charMap5
# has the ':' split char followed by the string representation # has the ':' split char followed by the string representation
@@ -507,23 +708,23 @@ def getDBfromFile(kInfoFile):
keyname = "unknown" keyname = "unknown"
for name in names: for name in names:
if encodeHash(name,charMap5) == keyhash: if encodeHash(name,testMap8) == keyhash:
keyname = name keyname = name
break break
if keyname == "unknown": if keyname == "unknown":
keyname = keyhash keyname = keyhash
# the charMap5 encoded contents data has had a length # the testMap8 encoded contents data has had a length
# of chars (always odd) cut off of the front and moved # of chars (always odd) cut off of the front and moved
# to the end to prevent decoding using charMap5 from # to the end to prevent decoding using testMap8 from
# working properly, and thereby preventing the ensuing # working properly, and thereby preventing the ensuing
# CryptUnprotectData call from succeeding. # CryptUnprotectData call from succeeding.
# The offset into the charMap5 encoded contents seems to be: # The offset into the testMap8 encoded contents seems to be:
# len(contents) - largest prime number less than or equal to int(len(content)/3) # len(contents) - largest prime number less than or equal to int(len(content)/3)
# (in other words split "about" 2/3rds of the way through) # (in other words split "about" 2/3rds of the way through)
# move first offsets chars to end to align for decode by charMap5 # move first offsets chars to end to align for decode by testMap8
encdata = "".join(edlst) encdata = "".join(edlst)
contlen = len(encdata) contlen = len(encdata)
@@ -535,14 +736,11 @@ def getDBfromFile(kInfoFile):
encdata = encdata[noffset:] encdata = encdata[noffset:]
encdata = encdata + pfx encdata = encdata + pfx
# decode using charMap5 to get the CryptProtect Data # decode using testMap8 to get the CryptProtect Data
encryptedValue = decode(encdata,charMap5) encryptedValue = decode(encdata,testMap8)
cleartext = CryptUnprotectDataV2(encryptedValue) cleartext = cud.decrypt(encryptedValue)
# Debugging
# print keyname # print keyname
# print cleartext # print cleartext
# print cleartext.encode('hex')
# print
DB[keyname] = cleartext DB[keyname] = cleartext
cnt = cnt + 1 cnt = cnt + 1

View File

@@ -3,7 +3,7 @@
from __future__ import with_statement from __future__ import with_statement
import sys, os import sys, os, re
from struct import pack, unpack, unpack_from from struct import pack, unpack, unpack_from
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
@@ -11,9 +11,7 @@ from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
string_at, Structure, c_void_p, cast string_at, Structure, c_void_p, cast
import _winreg as winreg import _winreg as winreg
MAX_PATH = 255 MAX_PATH = 255
kernel32 = windll.kernel32 kernel32 = windll.kernel32
advapi32 = windll.advapi32 advapi32 = windll.advapi32
crypt32 = windll.crypt32 crypt32 = windll.crypt32
@@ -33,6 +31,32 @@ def SHA1(message):
ctx.update(message) ctx.update(message)
return ctx.digest() return ctx.digest()
def SHA256(message):
ctx = hashlib.sha256()
ctx.update(message)
return ctx.digest()
# For K4PC 1.9.X
# use routines in alfcrypto:
# AES_cbc_encrypt
# AES_set_decrypt_key
# PKCS5_PBKDF2_HMAC_SHA1
from alfcrypto import AES_CBC, KeyIVGen
def UnprotectHeaderData(encryptedData):
passwdData = 'header_key_data'
salt = 'HEADER.2011'
iter = 0x80
keylen = 0x100
key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen)
key = key_iv[0:32]
iv = key_iv[32:48]
aes=AES_CBC()
aes.set_decrypt_key(key, iv)
cleartext = aes.decrypt(encryptedData)
return cleartext
# simple primes table (<= n) calculator # simple primes table (<= n) calculator
def primes(n): def primes(n):
@@ -59,6 +83,10 @@ def primes(n):
# Probably supposed to act as obfuscation # Probably supposed to act as obfuscation
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_" charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
# New maps in K4PC 1.9.0
testMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
testMap6 = "9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
class DrmException(Exception): class DrmException(Exception):
pass pass
@@ -123,7 +151,9 @@ def GetVolumeSerialNumber():
GetVolumeSerialNumber = GetVolumeSerialNumber() GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetIDString(): def GetIDString():
return GetVolumeSerialNumber() vsn = GetVolumeSerialNumber()
print('Using Volume Serial Number for ID: '+vsn)
return vsn
def getLastError(): def getLastError():
GetLastError = kernel32.GetLastError GetLastError = kernel32.GetLastError
@@ -165,7 +195,8 @@ def CryptUnprotectData():
outdata = DataBlob() outdata = DataBlob()
if not _CryptUnprotectData(byref(indata), None, byref(entropy), if not _CryptUnprotectData(byref(indata), None, byref(entropy),
None, None, flags, byref(outdata)): None, None, flags, byref(outdata)):
raise DrmException("Failed to Unprotect Data") # raise DrmException("Failed to Unprotect Data")
return 'failed'
return string_at(outdata.pbData, outdata.cbData) return string_at(outdata.pbData, outdata.cbData)
return CryptUnprotectData return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData() CryptUnprotectData = CryptUnprotectData()
@@ -176,28 +207,45 @@ def getKindleInfoFiles(kInfoFiles):
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0] path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
# some 64 bit machines do not have the proper registry key for some reason
# or the pythonn interface to the 32 vs 64 bit registry is broken
if 'LOCALAPPDATA' in os.environ.keys():
path = os.environ['LOCALAPPDATA']
print('searching for kinfoFiles in ' + path)
found = False
# first look for older kindle-info files # first look for older kindle-info files
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info' kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath): if os.path.isfile(kinfopath):
print('No kindle.info files have not been found.') found = True
else: print('Found K4PC kindle.info file: ' + kinfopath)
kInfoFiles.append(kinfopath) kInfoFiles.append(kinfopath)
# now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file # now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file
kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf' kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
if not os.path.isfile(kinfopath): if os.path.isfile(kinfopath):
print('No K4PC 1.5.X .kinf files have not been found.') found = True
else: print('Found K4PC 1.5.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath) kInfoFiles.append(kinfopath)
# now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file # now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf' kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
if not os.path.isfile(kinfopath): if os.path.isfile(kinfopath):
print('No K4PC 1.6.X .kinf files have not been found.') found = True
else: print('Found K4PC 1.6.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath) kInfoFiles.append(kinfopath)
# now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
if os.path.isfile(kinfopath):
found = True
print('Found K4PC kinf2011 file: ' + kinfopath)
kInfoFiles.append(kinfopath)
if not found:
print('No K4PC kindle.info/kinf/kinf2011 files have been found.')
return kInfoFiles return kInfoFiles
@@ -232,12 +280,97 @@ def getDBfromFile(kInfoFile):
DB = None DB = None
return DB return DB
# else newer style .kinf file if hdr == '/':
# else rainier-2-1-1 .kinf file
# the .kinf file uses "/" to separate it into records
# so remove the trailing "/" to make it easy to use split
data = data[:-1]
items = data.split('/')
# loop through the item records until all are processed
while len(items) > 0:
# get the first item record
item = items.pop(0)
# the first 32 chars of the first record of a group
# is the MD5 hash of the key name encoded by charMap5
keyhash = item[0:32]
# the raw keyhash string is used to create entropy for the actual
# CryptProtectData Blob that represents that keys contents
entropy = SHA1(keyhash)
# the remainder of the first record when decoded with charMap5
# has the ':' split char followed by the string representation
# of the number of records that follow
# and make up the contents
srcnt = decode(item[34:],charMap5)
rcnt = int(srcnt)
# read and store in rcnt records of data
# that make up the contents value
edlst = []
for i in xrange(rcnt):
item = items.pop(0)
edlst.append(item)
keyname = "unknown"
for name in names:
if encodeHash(name,charMap5) == keyhash:
keyname = name
break
if keyname == "unknown":
keyname = keyhash
# the charMap5 encoded contents data has had a length
# of chars (always odd) cut off of the front and moved
# to the end to prevent decoding using charMap5 from
# working properly, and thereby preventing the ensuing
# CryptUnprotectData call from succeeding.
# The offset into the charMap5 encoded contents seems to be:
# len(contents)-largest prime number <= int(len(content)/3)
# (in other words split "about" 2/3rds of the way through)
# move first offsets chars to end to align for decode by charMap5
encdata = "".join(edlst)
contlen = len(encdata)
noffset = contlen - primes(int(contlen/3))[-1]
# now properly split and recombine
# by moving noffset chars from the start of the
# string to the end of the string
pfx = encdata[0:noffset]
encdata = encdata[noffset:]
encdata = encdata + pfx
# decode using Map5 to get the CryptProtect Data
encryptedValue = decode(encdata,charMap5)
DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1)
cnt = cnt + 1
if cnt == 0:
DB = None
return DB
# else newest .kinf2011 style .kinf file
# the .kinf file uses "/" to separate it into records # the .kinf file uses "/" to separate it into records
# so remove the trailing "/" to make it easy to use split # so remove the trailing "/" to make it easy to use split
data = data[:-1] # need to put back the first char read because it it part
# of the added entropy blob
data = hdr + data[:-1]
items = data.split('/') items = data.split('/')
# starts with and encoded and encrypted header blob
headerblob = items.pop(0)
encryptedValue = decode(headerblob, testMap1)
cleartext = UnprotectHeaderData(encryptedValue)
# now extract the pieces that form the added entropy
pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
for m in re.finditer(pattern, cleartext):
added_entropy = m.group(2) + m.group(4)
# loop through the item records until all are processed # loop through the item records until all are processed
while len(items) > 0: while len(items) > 0:
@@ -248,9 +381,9 @@ def getDBfromFile(kInfoFile):
# is the MD5 hash of the key name encoded by charMap5 # is the MD5 hash of the key name encoded by charMap5
keyhash = item[0:32] keyhash = item[0:32]
# the raw keyhash string is also used to create entropy for the actual # the sha1 of raw keyhash string is used to create entropy along
# CryptProtectData Blob that represents that keys contents # with the added entropy provided above from the headerblob
entropy = SHA1(keyhash) entropy = SHA1(keyhash) + added_entropy
# the remainder of the first record when decoded with charMap5 # the remainder of the first record when decoded with charMap5
# has the ':' split char followed by the string representation # has the ':' split char followed by the string representation
@@ -266,43 +399,39 @@ def getDBfromFile(kInfoFile):
item = items.pop(0) item = items.pop(0)
edlst.append(item) edlst.append(item)
# key names now use the new testMap8 encoding
keyname = "unknown" keyname = "unknown"
for name in names: for name in names:
if encodeHash(name,charMap5) == keyhash: if encodeHash(name,testMap8) == keyhash:
keyname = name keyname = name
break break
if keyname == "unknown":
keyname = keyhash
# the charMap5 encoded contents data has had a length # the testMap8 encoded contents data has had a length
# of chars (always odd) cut off of the front and moved # of chars (always odd) cut off of the front and moved
# to the end to prevent decoding using charMap5 from # to the end to prevent decoding using testMap8 from
# working properly, and thereby preventing the ensuing # working properly, and thereby preventing the ensuing
# CryptUnprotectData call from succeeding. # CryptUnprotectData call from succeeding.
# The offset into the charMap5 encoded contents seems to be: # The offset into the testMap8 encoded contents seems to be:
# len(contents) - largest prime number less than or equal to int(len(content)/3) # len(contents)-largest prime number <= int(len(content)/3)
# (in other words split "about" 2/3rds of the way through) # (in other words split "about" 2/3rds of the way through)
# move first offsets chars to end to align for decode by charMap5 # move first offsets chars to end to align for decode by testMap8
# by moving noffset chars from the start of the
# string to the end of the string
encdata = "".join(edlst) encdata = "".join(edlst)
contlen = len(encdata) contlen = len(encdata)
noffset = contlen - primes(int(contlen/3))[-1] noffset = contlen - primes(int(contlen/3))[-1]
# now properly split and recombine
# by moving noffset chars from the start of the
# string to the end of the string
pfx = encdata[0:noffset] pfx = encdata[0:noffset]
encdata = encdata[noffset:] encdata = encdata[noffset:]
encdata = encdata + pfx encdata = encdata + pfx
# decode using Map5 to get the CryptProtect Data # decode using new testMap8 to get the original CryptProtect Data
encryptedValue = decode(encdata,charMap5) encryptedValue = decode(encdata,testMap8)
DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
DB[keyname] = cleartext
cnt = cnt + 1 cnt = cnt + 1
if cnt == 0: if cnt == 0:
DB = None DB = None
return DB return DB

View File

@@ -91,9 +91,9 @@ def getTwoBitsFromBitField(bitField,offset):
# Returns the six bits at offset from a bit field # Returns the six bits at offset from a bit field
def getSixBitsFromBitField(bitField,offset): def getSixBitsFromBitField(bitField,offset):
offset *= 3 offset *= 3
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2) value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
return value return value
# 8 bits to six bits encoding from hash to generate PID string # 8 bits to six bits encoding from hash to generate PID string
def encodePID(hash): def encodePID(hash):
@@ -121,8 +121,8 @@ def generatePidEncryptionTable() :
def generatePidSeed(table,dsn) : def generatePidSeed(table,dsn) :
value = 0 value = 0
for counter in range (0,4) : for counter in range (0,4) :
index = (ord(dsn[counter]) ^ value) &0xFF index = (ord(dsn[counter]) ^ value) &0xFF
value = (value >> 8) ^ table[index] value = (value >> 8) ^ table[index]
return value return value
# Generate the device PID # Generate the device PID
@@ -141,7 +141,7 @@ def generateDevicePID(table,dsn,nbRoll):
return pidAscii return pidAscii
def crc32(s): def crc32(s):
return (~binascii.crc32(s,-1))&0xFFFFFFFF return (~binascii.crc32(s,-1))&0xFFFFFFFF
# convert from 8 digit PID to 10 digit PID with checksum # convert from 8 digit PID to 10 digit PID with checksum
def checksumPid(s): def checksumPid(s):
@@ -258,13 +258,19 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
def getPidList(md1, md2, k4, pids, serials, kInfoFiles): def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
pidlst = [] pidlst = []
if kInfoFiles is None: if kInfoFiles is None:
kInfoFiles = [] kInfoFiles = []
if k4: if k4:
kInfoFiles = getKindleInfoFiles(kInfoFiles) kInfoFiles = getKindleInfoFiles(kInfoFiles)
for infoFile in kInfoFiles: for infoFile in kInfoFiles:
pidlst = getK4Pids(pidlst, md1, md2, infoFile) try:
pidlst = getK4Pids(pidlst, md1, md2, infoFile)
except Exception, message:
print("Error getting PIDs from " + infoFile + ": " + message)
for serialnum in serials: for serialnum in serials:
pidlst = getKindlePid(pidlst, md1, md2, serialnum) try:
pidlst = getKindlePid(pidlst, md1, md2, serialnum)
except Exception, message:
print("Error getting PIDs from " + serialnum + ": " + message)
for pid in pids: for pid in pids:
pidlst.append(pid) pidlst.append(pid)
return pidlst return pidlst

View File

@@ -0,0 +1,91 @@
#!/usr/bin/python
# Mobipocket PID calculator v0.2 for Amazon Kindle.
# Copyright (c) 2007, 2009 Igor Skochinsky <skochinsky@mail.ru>
# History:
# 0.1 Initial release
# 0.2 Added support for generating PID for iPhone (thanks to mbp)
# 0.3 changed to autoflush stdout, fixed return code usage
class Unbuffered:
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
import sys
sys.stdout=Unbuffered(sys.stdout)
import binascii
if sys.hexversion >= 0x3000000:
print "This script is incompatible with Python 3.x. Please install Python 2.6.x from python.org"
sys.exit(2)
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
def crc32(s):
return (~binascii.crc32(s,-1))&0xFFFFFFFF
def checksumPid(s):
crc = crc32(s)
crc = crc ^ (crc >> 16)
res = s
l = len(letters)
for i in (0,1):
b = crc & 0xff
pos = (b // l) ^ (b % l)
res += letters[pos%l]
crc >>= 8
return res
def pidFromSerial(s, l):
crc = crc32(s)
arr1 = [0]*l
for i in xrange(len(s)):
arr1[i%l] ^= ord(s[i])
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
for i in xrange(l):
arr1[i] ^= crc_bytes[i&3]
pid = ""
for i in xrange(l):
b = arr1[i] & 0xff
pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
return pid
def main(argv=sys.argv):
print "Mobipocket PID calculator for Amazon Kindle. Copyright (c) 2007, 2009 Igor Skochinsky"
if len(sys.argv)==2:
serial = sys.argv[1]
else:
print "Usage: kindlepid.py <Kindle Serial Number>/<iPhone/iPod Touch UDID>"
return 1
if len(serial)==16:
if serial.startswith("B"):
print "Kindle serial number detected"
else:
print "Warning: unrecognized serial number. Please recheck input."
return 1
pid = pidFromSerial(serial,7)+"*"
print "Mobipocket PID for Kindle serial# "+serial+" is "+checksumPid(pid)
return 0
elif len(serial)==40:
print "iPhone serial number (UDID) detected"
pid = pidFromSerial(serial,8)
print "Mobipocket PID for iPhone serial# "+serial+" is "+checksumPid(pid)
return 0
else:
print "Warning: unrecognized serial number. Please recheck input."
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -54,8 +54,14 @@
# 0.30 - Modified interface slightly to work better with new calibre plugin style # 0.30 - Modified interface slightly to work better with new calibre plugin style
# 0.31 - The multibyte encrytion info is true for version 7 files too. # 0.31 - The multibyte encrytion info is true for version 7 files too.
# 0.32 - Added support for "Print Replica" Kindle ebooks # 0.32 - Added support for "Print Replica" Kindle ebooks
# 0.33 - Performance improvements for large files (concatenation)
# 0.34 - Performance improvements in decryption (libalfcrypto)
# 0.35 - add interface to get mobi_version
# 0.36 - fixed problem with TEXtREAd and getBookTitle interface
# 0.37 - Fixed double announcement for stand-alone operation
__version__ = '0.32'
__version__ = '0.37'
import sys import sys
@@ -72,6 +78,7 @@ sys.stdout=Unbuffered(sys.stdout)
import os import os
import struct import struct
import binascii import binascii
from alfcrypto import Pukall_Cipher
class DrmException(Exception): class DrmException(Exception):
pass pass
@@ -83,36 +90,37 @@ class DrmException(Exception):
# Implementation of Pukall Cipher 1 # Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True): def PC1(key, src, decryption=True):
sum1 = 0; return Pukall_Cipher().PC1(key,src,decryption)
sum2 = 0; # sum1 = 0;
keyXorVal = 0; # sum2 = 0;
if len(key)!=16: # keyXorVal = 0;
print "Bad key length!" # if len(key)!=16:
return None # print "Bad key length!"
wkey = [] # return None
for i in xrange(8): # wkey = []
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) # for i in xrange(8):
dst = "" # wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
for i in xrange(len(src)): # dst = ""
temp1 = 0; # for i in xrange(len(src)):
byteXorVal = 0; # temp1 = 0;
for j in xrange(8): # byteXorVal = 0;
temp1 ^= wkey[j] # for j in xrange(8):
sum2 = (sum2+j)*20021 + sum1 # temp1 ^= wkey[j]
sum1 = (temp1*346)&0xFFFF # sum2 = (sum2+j)*20021 + sum1
sum2 = (sum2+sum1)&0xFFFF # sum1 = (temp1*346)&0xFFFF
temp1 = (temp1*20021+1)&0xFFFF # sum2 = (sum2+sum1)&0xFFFF
byteXorVal ^= temp1 ^ sum2 # temp1 = (temp1*20021+1)&0xFFFF
curByte = ord(src[i]) # byteXorVal ^= temp1 ^ sum2
if not decryption: # curByte = ord(src[i])
keyXorVal = curByte * 257; # if not decryption:
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF # keyXorVal = curByte * 257;
if decryption: # curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
keyXorVal = curByte * 257; # if decryption:
for j in xrange(8): # keyXorVal = curByte * 257;
wkey[j] ^= keyXorVal; # for j in xrange(8):
dst+=chr(curByte) # wkey[j] ^= keyXorVal;
return dst # dst+=chr(curByte)
# return dst
def checksumPid(s): def checksumPid(s):
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
@@ -163,9 +171,10 @@ class MobiBook:
off = self.sections[section][0] off = self.sections[section][0]
return self.data_file[off:endoff] return self.data_file[off:endoff]
def __init__(self, infile): def __init__(self, infile, announce = True):
print ('MobiDeDrm v%(__version__)s. ' if announce:
'Copyright 2008-2011 The Dark Reverser et al.' % globals()) print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
# initial sanity check on file # initial sanity check on file
self.data_file = file(infile, 'rb').read() self.data_file = file(infile, 'rb').read()
@@ -193,6 +202,7 @@ class MobiBook:
print "Book has format: ", self.magic print "Book has format: ", self.magic
self.extra_data_flags = 0 self.extra_data_flags = 0
self.mobi_length = 0 self.mobi_length = 0
self.mobi_codepage = 1252
self.mobi_version = -1 self.mobi_version = -1
self.meta_array = {} self.meta_array = {}
return return
@@ -243,18 +253,19 @@ class MobiBook:
65001 : 'utf-8', 65001 : 'utf-8',
} }
title = '' title = ''
if 503 in self.meta_array: codec = 'windows-1252'
title = self.meta_array[503] if self.magic == 'BOOKMOBI':
else : if 503 in self.meta_array:
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c]) title = self.meta_array[503]
tend = toff + tlen else:
title = self.sect[toff:tend] toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
tend = toff + tlen
title = self.sect[toff:tend]
if self.mobi_codepage in codec_map.keys():
codec = codec_map[self.mobi_codepage]
if title == '': if title == '':
title = self.header[:32] title = self.header[:32]
title = title.split("\0")[0] title = title.split("\0")[0]
codec = 'windows-1252'
if self.mobi_codepage in codec_map.keys():
codec = codec_map[self.mobi_codepage]
return unicode(title, codec).encode('utf-8') return unicode(title, codec).encode('utf-8')
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
@@ -320,6 +331,9 @@ class MobiBook:
def getMobiFile(self, outpath): def getMobiFile(self, outpath):
file(outpath,'wb').write(self.mobi_data) file(outpath,'wb').write(self.mobi_data)
def getMobiVersion(self):
return self.mobi_version
def getPrintReplica(self): def getPrintReplica(self):
return self.print_replica return self.print_replica
@@ -367,7 +381,7 @@ class MobiBook:
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.") raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key: if not found_key:
raise DrmException("No key found. Most likely the correct PID has not been given.") raise DrmException("No key found in " + str(len(goodpids)) + " keys tried. Please report this failure for help.")
# kill the drm keys # kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr) self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers # kill the drm pointers
@@ -383,7 +397,8 @@ class MobiBook:
# decrypt sections # decrypt sections
print "Decrypting. Please wait . . .", print "Decrypting. Please wait . . .",
self.mobi_data = self.data_file[:self.sections[1][0]] mobidataList = []
mobidataList.append(self.data_file[:self.sections[1][0]])
for i in xrange(1, self.records+1): for i in xrange(1, self.records+1):
data = self.loadSection(i) data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
@@ -393,34 +408,35 @@ class MobiBook:
decoded_data = PC1(found_key, data[0:len(data) - extra_size]) decoded_data = PC1(found_key, data[0:len(data) - extra_size])
if i==1: if i==1:
self.print_replica = (decoded_data[0:4] == '%MOP') self.print_replica = (decoded_data[0:4] == '%MOP')
self.mobi_data += decoded_data mobidataList.append(decoded_data)
if extra_size > 0: if extra_size > 0:
self.mobi_data += data[-extra_size:] mobidataList.append(data[-extra_size:])
if self.num_sections > self.records+1: if self.num_sections > self.records+1:
self.mobi_data += self.data_file[self.sections[self.records+1][0]:] mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
self.mobi_data = "".join(mobidataList)
print "done" print "done"
return return
def getUnencryptedBook(infile,pid): def getUnencryptedBook(infile,pid,announce=True):
if not os.path.isfile(infile): if not os.path.isfile(infile):
raise DrmException('Input File Not Found') raise DrmException('Input File Not Found')
book = MobiBook(infile) book = MobiBook(infile,announce)
book.processBook([pid]) book.processBook([pid])
return book.mobi_data return book.mobi_data
def getUnencryptedBookWithList(infile,pidlist): def getUnencryptedBookWithList(infile,pidlist,announce=True):
if not os.path.isfile(infile): if not os.path.isfile(infile):
raise DrmException('Input File Not Found') raise DrmException('Input File Not Found')
book = MobiBook(infile) book = MobiBook(infile, announce)
book.processBook(pidlist) book.processBook(pidlist)
return book.mobi_data return book.mobi_data
def main(argv=sys.argv): def main(argv=sys.argv):
print ('MobiDeDrm v%(__version__)s. ' print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2011 The Dark Reverser et al.' % globals()) 'Copyright 2008-2012 The Dark Reverser et al.' % globals())
if len(argv)<3 or len(argv)>4: if len(argv)<3 or len(argv)>4:
print "Removes protection from Kindle/Mobipocket and Kindle/Print Replica ebooks" print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
print "Usage:" print "Usage:"
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0] print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
return 1 return 1
@@ -432,7 +448,7 @@ def main(argv=sys.argv):
else: else:
pidlist = {} pidlist = {}
try: try:
stripped_file = getUnencryptedBookWithList(infile, pidlist) stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
file(outfile, 'wb').write(stripped_file) file(outfile, 'wb').write(stripped_file)
except DrmException, e: except DrmException, e:
print "Error: %s" % e print "Error: %s" % e

View File

@@ -87,4 +87,3 @@ def load_libcrypto():
return ''.join(result) return ''.join(result)
return DES return DES

View File

@@ -0,0 +1,68 @@
# A simple implementation of pbkdf2 using stock python modules. See RFC2898
# for details. Basically, it derives a key from a password and salt.
# Copyright 2004 Matt Johnston <matt @ ucc asn au>
# Copyright 2009 Daniel Holth <dholth@fastmail.fm>
# This code may be freely used and modified for any purpose.
# Revision history
# v0.1 October 2004 - Initial release
# v0.2 8 March 2007 - Make usable with hashlib in Python 2.5 and use
# v0.3 "" the correct digest_size rather than always 20
# v0.4 Oct 2009 - Rescue from chandler svn, test and optimize.
import sys
import hmac
from struct import pack
try:
# only in python 2.5
import hashlib
sha = hashlib.sha1
md5 = hashlib.md5
sha256 = hashlib.sha256
except ImportError: # pragma: NO COVERAGE
# fallback
import sha
import md5
# this is what you want to call.
def pbkdf2( password, salt, itercount, keylen, hashfn = sha ):
try:
# depending whether the hashfn is from hashlib or sha/md5
digest_size = hashfn().digest_size
except TypeError: # pragma: NO COVERAGE
digest_size = hashfn.digest_size
# l - number of output blocks to produce
l = keylen / digest_size
if keylen % digest_size != 0:
l += 1
h = hmac.new( password, None, hashfn )
T = ""
for i in range(1, l+1):
T += pbkdf2_F( h, salt, itercount, i )
return T[0: keylen]
def xorstr( a, b ):
if len(a) != len(b):
raise ValueError("xorstr(): lengths differ")
return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b)))
def prf( h, data ):
hm = h.copy()
hm.update( data )
return hm.digest()
# Helper as per the spec. h is a hmac which has been created seeded with the
# password, it will be copy()ed and not modified.
def pbkdf2_F( h, salt, itercount, blocknum ):
U = prf( h, salt + pack('>i',blocknum ) )
T = U
for i in range(2, itercount+1):
U = prf( h, U )
T = xorstr( T, U )
return T

View File

@@ -6,6 +6,7 @@ import csv
import sys import sys
import os import os
import getopt import getopt
import re
from struct import pack from struct import pack
from struct import unpack from struct import unpack
@@ -82,12 +83,19 @@ class DocParser(object):
return startpos return startpos
# returns a vector of integers for the tagpath # returns a vector of integers for the tagpath
def getData(self, tagpath, pos, end): def getData(self, tagpath, pos, end, clean=False):
if clean:
digits_only = re.compile(r'''([0-9]+)''')
argres=[] argres=[]
(foundat, argt) = self.findinDoc(tagpath, pos, end) (foundat, argt) = self.findinDoc(tagpath, pos, end)
if (argt != None) and (len(argt) > 0) : if (argt != None) and (len(argt) > 0) :
argList = argt.split('|') argList = argt.split('|')
argres = [ int(strval) for strval in argList] for strval in argList:
if clean:
m = re.search(digits_only, strval)
if m != None:
strval = m.group()
argres.append(int(strval))
return argres return argres
def process(self): def process(self):
@@ -157,6 +165,9 @@ class DocParser(object):
elif attr == 'line-space': elif attr == 'line-space':
scale = self.fontsize * 2.0 scale = self.fontsize * 2.0
if val == "":
val = 0
if not ((attr == 'hang') and (int(val) == 0)) : if not ((attr == 'hang') and (int(val) == 0)) :
pv = float(val)/scale pv = float(val)/scale
cssargs[attr] = (self.attr_val_map[attr], pv) cssargs[attr] = (self.attr_val_map[attr], pv)
@@ -251,5 +262,5 @@ def convert2CSS(flatxml, fontsize, ph, pw):
def getpageIDMap(flatxml): def getpageIDMap(flatxml):
dp = DocParser(flatxml, 0, 0, 0) dp = DocParser(flatxml, 0, 0, 0)
pageidnumbers = dp.getData('info.original.pid', 0, -1) pageidnumbers = dp.getData('info.original.pid', 0, -1, True)
return pageidnumbers return pageidnumbers

View File

@@ -52,7 +52,7 @@ class Process(object):
self.__stdout_thread = threading.Thread( self.__stdout_thread = threading.Thread(
name="stdout-thread", name="stdout-thread",
target=self.__reader, args=(self.__collected_outdata, target=self.__reader, args=(self.__collected_outdata,
self.__process.stdout)) self.__process.stdout))
self.__stdout_thread.setDaemon(True) self.__stdout_thread.setDaemon(True)
self.__stdout_thread.start() self.__stdout_thread.start()
@@ -60,7 +60,7 @@ class Process(object):
self.__stderr_thread = threading.Thread( self.__stderr_thread = threading.Thread(
name="stderr-thread", name="stderr-thread",
target=self.__reader, args=(self.__collected_errdata, target=self.__reader, args=(self.__collected_errdata,
self.__process.stderr)) self.__process.stderr))
self.__stderr_thread.setDaemon(True) self.__stderr_thread.setDaemon(True)
self.__stderr_thread.start() self.__stderr_thread.start()
@@ -146,4 +146,3 @@ class Process(object):
self.__quit = True self.__quit = True
self.__inputsem.release() self.__inputsem.release()
self.__lock.release() self.__lock.release()

View File

@@ -16,10 +16,13 @@ if 'calibre' in sys.modules:
else: else:
inCalibre = False inCalibre = False
buildXML = False
import os, csv, getopt import os, csv, getopt
import zlib, zipfile, tempfile, shutil import zlib, zipfile, tempfile, shutil
from struct import pack from struct import pack
from struct import unpack from struct import unpack
from alfcrypto import Topaz_Cipher
class TpzDRMError(Exception): class TpzDRMError(Exception):
pass pass
@@ -28,11 +31,8 @@ class TpzDRMError(Exception):
# local support routines # local support routines
if inCalibre: if inCalibre:
from calibre_plugins.k4mobidedrm import kgenpids from calibre_plugins.k4mobidedrm import kgenpids
from calibre_plugins.k4mobidedrm import genbook
else: else:
import kgenpids import kgenpids
import genbook
# recursive zip creation support routine # recursive zip creation support routine
def zipUpDir(myzip, tdir, localname): def zipUpDir(myzip, tdir, localname):
@@ -58,8 +58,8 @@ def bookReadEncodedNumber(fo):
flag = False flag = False
data = ord(fo.read(1)) data = ord(fo.read(1))
if data == 0xFF: if data == 0xFF:
flag = True flag = True
data = ord(fo.read(1)) data = ord(fo.read(1))
if data >= 0x80: if data >= 0x80:
datax = (data & 0x7F) datax = (data & 0x7F)
while data >= 0x80 : while data >= 0x80 :
@@ -67,7 +67,7 @@ def bookReadEncodedNumber(fo):
datax = (datax <<7) + (data & 0x7F) datax = (datax <<7) + (data & 0x7F)
data = datax data = datax
if flag: if flag:
data = -data data = -data
return data return data
# Get a length prefixed string from file # Get a length prefixed string from file
@@ -81,25 +81,28 @@ def bookReadString(fo):
# Context initialisation for the Topaz Crypto # Context initialisation for the Topaz Crypto
def topazCryptoInit(key): def topazCryptoInit(key):
ctx1 = 0x0CAFFE19E return Topaz_Cipher().ctx_init(key)
for keyChar in key:
keyByte = ord(keyChar) # ctx1 = 0x0CAFFE19E
ctx2 = ctx1 # for keyChar in key:
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF ) # keyByte = ord(keyChar)
return [ctx1,ctx2] # ctx2 = ctx1
# ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
# return [ctx1,ctx2]
# decrypt data with the context prepared by topazCryptoInit() # decrypt data with the context prepared by topazCryptoInit()
def topazCryptoDecrypt(data, ctx): def topazCryptoDecrypt(data, ctx):
ctx1 = ctx[0] return Topaz_Cipher().decrypt(data, ctx)
ctx2 = ctx[1] # ctx1 = ctx[0]
plainText = "" # ctx2 = ctx[1]
for dataChar in data: # plainText = ""
dataByte = ord(dataChar) # for dataChar in data:
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF # dataByte = ord(dataChar)
ctx2 = ctx1 # m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF) # ctx2 = ctx1
plainText += chr(m) # ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
return plainText # plainText += chr(m)
# return plainText
# Decrypt data with the PID # Decrypt data with the PID
def decryptRecord(data,PID): def decryptRecord(data,PID):
@@ -265,6 +268,11 @@ class TopazBook:
self.createBookDirectory() self.createBookDirectory()
self.extractFiles() self.extractFiles()
print "Successfully Extracted Topaz contents" print "Successfully Extracted Topaz contents"
if inCalibre:
from calibre_plugins.k4mobidedrm import genbook
else:
import genbook
rv = genbook.generateBook(self.outdir, raw, fixedimage) rv = genbook.generateBook(self.outdir, raw, fixedimage)
if rv == 0: if rv == 0:
print "\nBook Successfully generated" print "\nBook Successfully generated"
@@ -294,6 +302,11 @@ class TopazBook:
self.createBookDirectory() self.createBookDirectory()
self.extractFiles() self.extractFiles()
print "Successfully Extracted Topaz contents" print "Successfully Extracted Topaz contents"
if inCalibre:
from calibre_plugins.k4mobidedrm import genbook
else:
import genbook
rv = genbook.generateBook(self.outdir, raw, fixedimage) rv = genbook.generateBook(self.outdir, raw, fixedimage)
if rv == 0: if rv == 0:
print "\nBook Successfully generated" print "\nBook Successfully generated"
@@ -371,8 +384,7 @@ class TopazBook:
def cleanup(self): def cleanup(self):
if os.path.isdir(self.outdir): if os.path.isdir(self.outdir):
pass shutil.rmtree(self.outdir, True)
# shutil.rmtree(self.outdir, True)
def usage(progname): def usage(progname):
print "Removes DRM protection from Topaz ebooks and extract the contents" print "Removes DRM protection from Topaz ebooks and extract the contents"
@@ -382,6 +394,7 @@ def usage(progname):
# Main # Main
def main(argv=sys.argv): def main(argv=sys.argv):
global buildXML
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
k4 = False k4 = False
pids = [] pids = []
@@ -443,9 +456,10 @@ def main(argv=sys.argv):
zipname = os.path.join(outdir, bookname + '_SVG' + '.zip') zipname = os.path.join(outdir, bookname + '_SVG' + '.zip')
tb.getSVGZip(zipname) tb.getSVGZip(zipname)
print " Creating XML ZIP Archive" if buildXML:
zipname = os.path.join(outdir, bookname + '_XML' + '.zip') print " Creating XML ZIP Archive"
tb.getXMLZip(zipname) zipname = os.path.join(outdir, bookname + '_XML' + '.zip')
tb.getXMLZip(zipname)
# removing internal temporary directory of pieces # removing internal temporary directory of pieces
tb.cleanup() tb.cleanup()
@@ -466,4 +480,3 @@ def main(argv=sys.argv):
if __name__ == '__main__': if __name__ == '__main__':
sys.stdout=Unbuffered(sys.stdout) sys.stdout=Unbuffered(sys.stdout)
sys.exit(main()) sys.exit(main())

View File

@@ -30,7 +30,7 @@ class fixZip:
self.inzip = zipfile.ZipFile(zinput,'r') self.inzip = zipfile.ZipFile(zinput,'r')
self.outzip = zipfile.ZipFile(zoutput,'w') self.outzip = zipfile.ZipFile(zoutput,'w')
# open the input zip for reading only as a raw file # open the input zip for reading only as a raw file
self.bzf = file(zinput,'rb') self.bzf = file(zinput,'rb')
def getlocalname(self, zi): def getlocalname(self, zi):
local_header_offset = zi.header_offset local_header_offset = zi.header_offset
@@ -152,5 +152,3 @@ def main(argv=sys.argv):
if __name__ == '__main__' : if __name__ == '__main__' :
sys.exit(main()) sys.exit(main())

View File

@@ -1,51 +1,44 @@
ReadMe_DeDRM_WinApp_vX.X ReadMe_DeDRM_v5.3_WinApp
----------------------- -----------------------
DeDRM_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto theDeDRM_Drop_Target to have the DRM removed. It repackages the"tools" python software in one easy to use program. DeDRM_v5.3_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto the DeDRM_Drop_Target to have the DRM removed. It repackages the"tools" python software in one easy to use program that remembers preferences and settings.
It should work out of the box with Kindle for PC ebooks and Adobe Adept epub and pdf ebooks. It should work out of the box with Kindle for PC ebooks and Adobe Adept epub and pdf ebooks.
To remove the DRM from standalone Kindle ebooks, eReader pdb ebooks, Barnes and Noble epubs, and Mobipocket ebooks requires the user to double-click the DeDRM_Drop_Target and set some additional Preferences including: To remove the DRM from standalone Kindle ebooks, eReader pdb ebooks, Barnes and Noble epubs, and Mobipocket ebooks requires the user to double-click the DeDRM_Drop_Target and set some additional Preferences including:
Kindle 16 digit Serial Number eInk Kindle: 16 digit Serial Number
Barnes & Noble key files (bnepubkey.b64) Barnes & Noble: key file (bnepubkey.b64)
eReader Social DRM: (Name:Last 8 digits of CC number) eReader Social DRM: Name:Last 8 digits of CC number
MobiPocket, Kindle for iPhone/iPad/iPodTouch 10 digit PID MobiPocket: 10 digit PID
Once these preferences have been set, the user can simply drag and drop ebooks onto the DeDRM_Drop_Target to remove the DRM. Once these preferences have been set, the user can simply drag and drop ebooks onto the DeDRM_Drop_Target to remove the DRM.
This program requires that the proper 32 bit version of Python 2.X (tested with Python 2.5 through Python 2.7) and PyCrypto be installed on your computer before it will work. See below for where to get theese programs for Windows. This program requires that the proper 32 bit version of Python 2.X (tested with Python 2.5 through Python 2.7) and PyCrypto be installed on your computer before it will work. See below for where to get theese programs for Windows.
Installation Installation
------------ ------------
1. Download the latest DeDRM_WinApp_vx.x.zip and fully Extract its contents. 1. In tools_v5.3\DeDRM_Applications\Windows, right click on DeDRM_5.3_Win.zip and fully extract its contents using "Extract All...", saving to your "My Documents" folder.
2. Move the resulting DeDRM_WinApp_vX.X folder to whereever you keep you other programs. 2. Open the DeDRM_5.3_Win folder you've just created, and make a short-cut of the DeDRM_Drop_Target.bat file (right-click/Create Shortcut). Drag the shortcut file onto your Desktop.
(I typically use an "Applications" folder inside of my home directory)
3. Open the folder, and create a short-cut to DeDRM_Drop_Target and move that short-cut to your Desktop.
4. To set the preferences simply double-click on your just created short-cut.
3. To set the preferences simply double-click on your just created short-cut.
If you already have a correct version of Python and PyCrypto installed and in your path, you are ready to go! If you already have a correct version of Python and PyCrypto installed and in your path, you are ready to go!
If not, see below.
If not, see where you can get these additional pieces.
Installing Python on Windows Installing Python on Windows
---------------------------- ----------------------------
I strongly recommend installing ActiveStates Active Python, Community Edition for Windows (x86) 32 bits. This is a free, full version of the Python. It comes with some important additional modules that are not included in the bare-bones version from www.python.org unless you choose to install everything. I strongly recommend fully installing ActiveStates Active Python, free Community Edition for Windows (x86) 32 bits. This is a free, full version of the Python. It comes with some important additional modules that are not included in the bare-bones version from www.python.org unless you choose to install everything.
1. Download ActivePython 2.7.1 for Windows (x86) (or later 2.7 version for Windows (x86) ) from http://www.activestate.com/activepython/downloads. Do not download the ActivePython 2.7.1 for Windows (64-bit, x64) verson, even if you are running 64-bit Windows. 1. Download ActivePython 2.7.X for Windows (x86) (or later 2.7 version for Windows (x86) ) from http://www.activestate.com/activepython/downloads. Do not download the ActivePython 2.7.X for Windows (64-bit, x64) verson, even if you are running 64-bit Windows.
2. When it has finished downloading, run the installer. Accept the default options. 2. When it has finished downloading, run the installer. Accept the default options.
Installing PyCrypto on Windows Installing PyCrypto on Windows
------------------------------ ------------------------------
PyCrypto is a set of encryption/decryption routines that work with Python. The sources are freely available, and compiled versions are available from several sources. You must install a version that is for 32-bit Windows and Python 2.7. I recommend the installer linked from Michael Foords blog. PyCrypto is a set of encryption/decryption routines that work with Python. The sources are freely available, and compiled versions are available from several sources. You must install a version that is for 32-bit Windows and Python 2.7. I recommend the installer linked from Michael Foords blog.

View File

@@ -4,6 +4,8 @@
import sys import sys
import os, os.path import os, os.path
sys.path.append(sys.path[0]+os.sep+'lib') sys.path.append(sys.path[0]+os.sep+'lib')
os.environ['PYTHONIOENCODING'] = "utf-8"
import shutil import shutil
import Tkinter import Tkinter
from Tkinter import * from Tkinter import *
@@ -18,6 +20,9 @@ from subasyncio import Process
import re import re
import simpleprefs import simpleprefs
__version__ = '5.2'
class DrmException(Exception): class DrmException(Exception):
pass pass
@@ -33,7 +38,7 @@ class MainApp(Tk):
['serials', 'seriallist.txt'], ['serials', 'seriallist.txt'],
['sdrms' , 'sdrmlist.txt' ], ['sdrms' , 'sdrmlist.txt' ],
['outdir' , 'outdir.txt' ]] ['outdir' , 'outdir.txt' ]]
self.po = simpleprefs.SimplePrefs('DeDRM',description) self.po = simpleprefs.SimplePrefs("DeDRM",description)
if self.dnd: if self.dnd:
self.cd = ConvDialog(self) self.cd = ConvDialog(self)
prefs = self.getPreferences() prefs = self.getPreferences()
@@ -90,7 +95,7 @@ class PrefsDialog(Toplevel):
Toplevel.__init__(self, mainapp) Toplevel.__init__(self, mainapp)
self.withdraw() self.withdraw()
self.protocol("WM_DELETE_WINDOW", self.withdraw) self.protocol("WM_DELETE_WINDOW", self.withdraw)
self.title("DeDRM") self.title("DeDRM " + __version__)
self.prefs_array = prefs_array self.prefs_array = prefs_array
self.status = Tkinter.Label(self, text='Setting Preferences') self.status = Tkinter.Label(self, text='Setting Preferences')
self.status.pack(fill=Tkconstants.X, expand=1) self.status.pack(fill=Tkconstants.X, expand=1)
@@ -107,7 +112,6 @@ class PrefsDialog(Toplevel):
keyfile = os.path.join(prefdir,'adeptkey.der') keyfile = os.path.join(prefdir,'adeptkey.der')
if os.path.isfile(keyfile): if os.path.isfile(keyfile):
path = keyfile path = keyfile
path = path.encode('utf-8')
self.adkpath.insert(0, path) self.adkpath.insert(0, path)
button = Tkinter.Button(body, text="...", command=self.get_adkpath) button = Tkinter.Button(body, text="...", command=self.get_adkpath)
button.grid(row=0, column=2) button.grid(row=0, column=2)
@@ -119,7 +123,6 @@ class PrefsDialog(Toplevel):
keyfile = os.path.join(prefdir,'bnepubkey.b64') keyfile = os.path.join(prefdir,'bnepubkey.b64')
if os.path.isfile(keyfile): if os.path.isfile(keyfile):
path = keyfile path = keyfile
path = path.encode('utf-8')
self.bnkpath.insert(0, path) self.bnkpath.insert(0, path)
button = Tkinter.Button(body, text="...", command=self.get_bnkpath) button = Tkinter.Button(body, text="...", command=self.get_bnkpath)
button.grid(row=1, column=2) button.grid(row=1, column=2)
@@ -135,7 +138,6 @@ class PrefsDialog(Toplevel):
path = infofile path = infofile
elif os.path.isfile(ainfofile): elif os.path.isfile(ainfofile):
path = ainfofile path = ainfofile
path = path.encode('utf-8')
self.altinfopath.insert(0, path) self.altinfopath.insert(0, path)
button = Tkinter.Button(body, text="...", command=self.get_altinfopath) button = Tkinter.Button(body, text="...", command=self.get_altinfopath)
button.grid(row=2, column=2) button.grid(row=2, column=2)
@@ -166,7 +168,6 @@ class PrefsDialog(Toplevel):
self.outpath.grid(row=6, column=1, sticky=sticky) self.outpath.grid(row=6, column=1, sticky=sticky)
if 'outdir' in self.prefs_array: if 'outdir' in self.prefs_array:
dpath = self.prefs_array['outdir'] dpath = self.prefs_array['outdir']
dpath = dpath.encode('utf-8')
self.outpath.insert(0, dpath) self.outpath.insert(0, dpath)
button = Tkinter.Button(body, text="...", command=self.get_outpath) button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=6, column=2) button.grid(row=6, column=2)
@@ -263,6 +264,7 @@ class PrefsDialog(Toplevel):
filetypes=[('ePub Files','.epub'), filetypes=[('ePub Files','.epub'),
('Kindle','.azw'), ('Kindle','.azw'),
('Kindle','.azw1'), ('Kindle','.azw1'),
('Kindle','.azw3'),
('Kindle','.azw4'), ('Kindle','.azw4'),
('Kindle','.tpz'), ('Kindle','.tpz'),
('Kindle','.mobi'), ('Kindle','.mobi'),
@@ -414,7 +416,6 @@ class ConvDialog(Toplevel):
# post output from subprocess in scrolled text widget # post output from subprocess in scrolled text widget
def showCmdOutput(self, msg): def showCmdOutput(self, msg):
if msg and msg !='': if msg and msg !='':
msg = msg.encode('utf-8')
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
msg = msg.replace('\r\n','\n') msg = msg.replace('\r\n','\n')
self.stext.insert(Tkconstants.END,msg) self.stext.insert(Tkconstants.END,msg)
@@ -466,7 +467,7 @@ class ConvDialog(Toplevel):
if ext == '.pdb': if ext == '.pdb':
self.p2 = processPDB(apphome, infile, outdir, rscpath) self.p2 = processPDB(apphome, infile, outdir, rscpath)
return 0 return 0
if ext in ['.azw', '.azw1', '.azw4', '.prc', '.mobi', '.tpz']: if ext in ['.azw', '.azw1', '.azw3', '.azw4', '.prc', '.mobi', '.tpz']:
self.p2 = processK4MOBI(apphome, infile, outdir, rscpath) self.p2 = processK4MOBI(apphome, infile, outdir, rscpath)
return 0 return 0
if ext == '.pdf': if ext == '.pdf':
@@ -477,13 +478,17 @@ class ConvDialog(Toplevel):
# run as a subprocess via pipes and collect stdout, stderr, and return value # run as a subprocess via pipes and collect stdout, stderr, and return value
def runit(apphome, ncmd, nparms): def runit(apphome, ncmd, nparms):
cmdline = 'python ' + '"' + os.path.join(apphome, ncmd) + '" ' pengine = sys.executable
if sys.platform.startswith('win'): if pengine is None or pengine == '':
search_path = os.environ['PATH'] pengine = 'python'
search_path = search_path.lower() pengine = os.path.normpath(pengine)
if search_path.find('python') < 0: cmdline = pengine + ' "' + os.path.join(apphome, ncmd) + '" '
# if no python hope that win registry finds what is associated with py extension # if sys.platform.startswith('win'):
cmdline = '"' + os.path.join(apphome, ncmd) + '" ' # search_path = os.environ['PATH']
# search_path = search_path.lower()
# if search_path.find('python') < 0:
# # if no python hope that win registry finds what is associated with py extension
# cmdline = pengine + ' "' + os.path.join(apphome, ncmd) + '" '
cmdline += nparms cmdline += nparms
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = subasyncio.Process(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) p2 = subasyncio.Process(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
@@ -561,6 +566,8 @@ def main(argv=sys.argv):
infilelst = argv[1:] infilelst = argv[1:]
filenames = [] filenames = []
for infile in infilelst: for infile in infilelst:
infile = infile.decode(sys.getfilesystemencoding())
print infile
infile = infile.replace('"','') infile = infile.replace('"','')
infile = os.path.abspath(infile) infile = os.path.abspath(infile)
if os.path.isdir(infile): if os.path.isdir(infile):
@@ -585,4 +592,3 @@ def main(argv=sys.argv):
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) sys.exit(main())

View File

@@ -0,0 +1,568 @@
#! /usr/bin/env python
"""
Routines for doing AES CBC in one file
Modified by some_updates to extract
and combine only those parts needed for AES CBC
into one simple to add python file
Original Version
Copyright (c) 2002 by Paul A. Lambert
Under:
CryptoPy Artisitic License Version 1.0
See the wonderful pure python package cryptopy-1.2.5
and read its LICENSE.txt for complete license details.
"""
class CryptoError(Exception):
""" Base class for crypto exceptions """
def __init__(self,errorMessage='Error!'):
self.message = errorMessage
def __str__(self):
return self.message
class InitCryptoError(CryptoError):
""" Crypto errors during algorithm initialization """
class BadKeySizeError(InitCryptoError):
""" Bad key size error """
class EncryptError(CryptoError):
""" Error in encryption processing """
class DecryptError(CryptoError):
""" Error in decryption processing """
class DecryptNotBlockAlignedError(DecryptError):
""" Error in decryption processing """
def xorS(a,b):
""" XOR two strings """
assert len(a)==len(b)
x = []
for i in range(len(a)):
x.append( chr(ord(a[i])^ord(b[i])))
return ''.join(x)
def xor(a,b):
""" XOR two strings """
x = []
for i in range(min(len(a),len(b))):
x.append( chr(ord(a[i])^ord(b[i])))
return ''.join(x)
"""
Base 'BlockCipher' and Pad classes for cipher instances.
BlockCipher supports automatic padding and type conversion. The BlockCipher
class was written to make the actual algorithm code more readable and
not for performance.
"""
class BlockCipher:
""" Block ciphers """
def __init__(self):
self.reset()
def reset(self):
self.resetEncrypt()
self.resetDecrypt()
def resetEncrypt(self):
self.encryptBlockCount = 0
self.bytesToEncrypt = ''
def resetDecrypt(self):
self.decryptBlockCount = 0
self.bytesToDecrypt = ''
def encrypt(self, plainText, more = None):
""" Encrypt a string and return a binary string """
self.bytesToEncrypt += plainText # append plainText to any bytes from prior encrypt
numBlocks, numExtraBytes = divmod(len(self.bytesToEncrypt), self.blockSize)
cipherText = ''
for i in range(numBlocks):
bStart = i*self.blockSize
ctBlock = self.encryptBlock(self.bytesToEncrypt[bStart:bStart+self.blockSize])
self.encryptBlockCount += 1
cipherText += ctBlock
if numExtraBytes > 0: # save any bytes that are not block aligned
self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
else:
self.bytesToEncrypt = ''
if more == None: # no more data expected from caller
finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize)
if len(finalBytes) > 0:
ctBlock = self.encryptBlock(finalBytes)
self.encryptBlockCount += 1
cipherText += ctBlock
self.resetEncrypt()
return cipherText
def decrypt(self, cipherText, more = None):
""" Decrypt a string and return a string """
self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt
numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
if more == None: # no more calls to decrypt, should have all the data
if numExtraBytes != 0:
raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt'
# hold back some bytes in case last decrypt has zero len
if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
numBlocks -= 1
numExtraBytes = self.blockSize
plainText = ''
for i in range(numBlocks):
bStart = i*self.blockSize
ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize])
self.decryptBlockCount += 1
plainText += ptBlock
if numExtraBytes > 0: # save any bytes that are not block aligned
self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
else:
self.bytesToEncrypt = ''
if more == None: # last decrypt remove padding
plainText = self.padding.removePad(plainText, self.blockSize)
self.resetDecrypt()
return plainText
class Pad:
def __init__(self):
pass # eventually could put in calculation of min and max size extension
class padWithPadLen(Pad):
""" Pad a binary string with the length of the padding """
def addPad(self, extraBytes, blockSize):
""" Add padding to a binary string to make it an even multiple
of the block size """
blocks, numExtraBytes = divmod(len(extraBytes), blockSize)
padLength = blockSize - numExtraBytes
return extraBytes + padLength*chr(padLength)
def removePad(self, paddedBinaryString, blockSize):
""" Remove padding from a binary string """
if not(0<len(paddedBinaryString)):
raise DecryptNotBlockAlignedError, 'Expected More Data'
return paddedBinaryString[:-ord(paddedBinaryString[-1])]
class noPadding(Pad):
""" No padding. Use this to get ECB behavior from encrypt/decrypt """
def addPad(self, extraBytes, blockSize):
""" Add no padding """
return extraBytes
def removePad(self, paddedBinaryString, blockSize):
""" Remove no padding """
return paddedBinaryString
"""
Rijndael encryption algorithm
This byte oriented implementation is intended to closely
match FIPS specification for readability. It is not implemented
for performance.
"""
class Rijndael(BlockCipher):
""" Rijndael encryption algorithm """
def __init__(self, key = None, padding = padWithPadLen(), keySize=16, blockSize=16 ):
self.name = 'RIJNDAEL'
self.keySize = keySize
self.strength = keySize*8
self.blockSize = blockSize # blockSize is in bytes
self.padding = padding # change default to noPadding() to get normal ECB behavior
assert( keySize%4==0 and NrTable[4].has_key(keySize/4)),'key size must be 16,20,24,29 or 32 bytes'
assert( blockSize%4==0 and NrTable.has_key(blockSize/4)), 'block size must be 16,20,24,29 or 32 bytes'
self.Nb = self.blockSize/4 # Nb is number of columns of 32 bit words
self.Nk = keySize/4 # Nk is the key length in 32-bit words
self.Nr = NrTable[self.Nb][self.Nk] # The number of rounds (Nr) is a function of
# the block (Nb) and key (Nk) sizes.
if key != None:
self.setKey(key)
def setKey(self, key):
""" Set a key and generate the expanded key """
assert( len(key) == (self.Nk*4) ), 'Key length must be same as keySize parameter'
self.__expandedKey = keyExpansion(self, key)
self.reset() # BlockCipher.reset()
def encryptBlock(self, plainTextBlock):
""" Encrypt a block, plainTextBlock must be a array of bytes [Nb by 4] """
self.state = self._toBlock(plainTextBlock)
AddRoundKey(self, self.__expandedKey[0:self.Nb])
for round in range(1,self.Nr): #for round = 1 step 1 to Nr
SubBytes(self)
ShiftRows(self)
MixColumns(self)
AddRoundKey(self, self.__expandedKey[round*self.Nb:(round+1)*self.Nb])
SubBytes(self)
ShiftRows(self)
AddRoundKey(self, self.__expandedKey[self.Nr*self.Nb:(self.Nr+1)*self.Nb])
return self._toBString(self.state)
def decryptBlock(self, encryptedBlock):
""" decrypt a block (array of bytes) """
self.state = self._toBlock(encryptedBlock)
AddRoundKey(self, self.__expandedKey[self.Nr*self.Nb:(self.Nr+1)*self.Nb])
for round in range(self.Nr-1,0,-1):
InvShiftRows(self)
InvSubBytes(self)
AddRoundKey(self, self.__expandedKey[round*self.Nb:(round+1)*self.Nb])
InvMixColumns(self)
InvShiftRows(self)
InvSubBytes(self)
AddRoundKey(self, self.__expandedKey[0:self.Nb])
return self._toBString(self.state)
def _toBlock(self, bs):
""" Convert binary string to array of bytes, state[col][row]"""
assert ( len(bs) == 4*self.Nb ), 'Rijndarl blocks must be of size blockSize'
return [[ord(bs[4*i]),ord(bs[4*i+1]),ord(bs[4*i+2]),ord(bs[4*i+3])] for i in range(self.Nb)]
def _toBString(self, block):
""" Convert block (array of bytes) to binary string """
l = []
for col in block:
for rowElement in col:
l.append(chr(rowElement))
return ''.join(l)
#-------------------------------------
""" Number of rounds Nr = NrTable[Nb][Nk]
Nb Nk=4 Nk=5 Nk=6 Nk=7 Nk=8
------------------------------------- """
NrTable = {4: {4:10, 5:11, 6:12, 7:13, 8:14},
5: {4:11, 5:11, 6:12, 7:13, 8:14},
6: {4:12, 5:12, 6:12, 7:13, 8:14},
7: {4:13, 5:13, 6:13, 7:13, 8:14},
8: {4:14, 5:14, 6:14, 7:14, 8:14}}
#-------------------------------------
def keyExpansion(algInstance, keyString):
""" Expand a string of size keySize into a larger array """
Nk, Nb, Nr = algInstance.Nk, algInstance.Nb, algInstance.Nr # for readability
key = [ord(byte) for byte in keyString] # convert string to list
w = [[key[4*i],key[4*i+1],key[4*i+2],key[4*i+3]] for i in range(Nk)]
for i in range(Nk,Nb*(Nr+1)):
temp = w[i-1] # a four byte column
if (i%Nk) == 0 :
temp = temp[1:]+[temp[0]] # RotWord(temp)
temp = [ Sbox[byte] for byte in temp ]
temp[0] ^= Rcon[i/Nk]
elif Nk > 6 and i%Nk == 4 :
temp = [ Sbox[byte] for byte in temp ] # SubWord(temp)
w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] )
return w
Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!!
0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,
0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91)
#-------------------------------------
def AddRoundKey(algInstance, keyBlock):
""" XOR the algorithm state with a block of key material """
for column in range(algInstance.Nb):
for row in range(4):
algInstance.state[column][row] ^= keyBlock[column][row]
#-------------------------------------
def SubBytes(algInstance):
for column in range(algInstance.Nb):
for row in range(4):
algInstance.state[column][row] = Sbox[algInstance.state[column][row]]
def InvSubBytes(algInstance):
for column in range(algInstance.Nb):
for row in range(4):
algInstance.state[column][row] = InvSbox[algInstance.state[column][row]]
Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,
0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,
0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,
0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,
0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,
0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,
0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,
0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,
0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,
0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,
0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,
0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,
0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,
0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,
0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,
0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,
0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16)
InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,
0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,
0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,
0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,
0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,
0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,
0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,
0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,
0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,
0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,
0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,
0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,
0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,
0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,
0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,
0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,
0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d)
#-------------------------------------
""" For each block size (Nb), the ShiftRow operation shifts row i
by the amount Ci. Note that row 0 is not shifted.
Nb C1 C2 C3
------------------- """
shiftOffset = { 4 : ( 0, 1, 2, 3),
5 : ( 0, 1, 2, 3),
6 : ( 0, 1, 2, 3),
7 : ( 0, 1, 2, 4),
8 : ( 0, 1, 3, 4) }
def ShiftRows(algInstance):
tmp = [0]*algInstance.Nb # list of size Nb
for r in range(1,4): # row 0 reamains unchanged and can be skipped
for c in range(algInstance.Nb):
tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r]
for c in range(algInstance.Nb):
algInstance.state[c][r] = tmp[c]
def InvShiftRows(algInstance):
tmp = [0]*algInstance.Nb # list of size Nb
for r in range(1,4): # row 0 reamains unchanged and can be skipped
for c in range(algInstance.Nb):
tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r]
for c in range(algInstance.Nb):
algInstance.state[c][r] = tmp[c]
#-------------------------------------
def MixColumns(a):
Sprime = [0,0,0,0]
for j in range(a.Nb): # for each column
Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3])
Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3])
Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3])
Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3])
for i in range(4):
a.state[j][i] = Sprime[i]
def InvMixColumns(a):
""" Mix the four bytes of every column in a linear way
This is the opposite operation of Mixcolumn """
Sprime = [0,0,0,0]
for j in range(a.Nb): # for each column
Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3])
Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3])
Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3])
Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3])
for i in range(4):
a.state[j][i] = Sprime[i]
#-------------------------------------
def mul(a, b):
""" Multiply two elements of GF(2^m)
needed for MixColumn and InvMixColumn """
if (a !=0 and b!=0):
return Alogtable[(Logtable[a] + Logtable[b])%255]
else:
return 0
Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3,
100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193,
125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120,
101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142,
150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56,
102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16,
126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186,
43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87,
175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232,
44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160,
127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183,
204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157,
151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209,
83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171,
68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165,
103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7)
Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53,
95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170,
229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49,
83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205,
76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136,
131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154,
181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163,
254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160,
251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65,
195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117,
159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128,
155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84,
252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202,
69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14,
18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23,
57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1)
"""
AES Encryption Algorithm
The AES algorithm is just Rijndael algorithm restricted to the default
blockSize of 128 bits.
"""
class AES(Rijndael):
""" The AES algorithm is the Rijndael block cipher restricted to block
sizes of 128 bits and key sizes of 128, 192 or 256 bits
"""
def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
""" Initialize AES, keySize is in bytes """
if not (keySize == 16 or keySize == 24 or keySize == 32) :
raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes'
Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
self.name = 'AES'
"""
CBC mode of encryption for block ciphers.
This algorithm mode wraps any BlockCipher to make a
Cipher Block Chaining mode.
"""
from random import Random # should change to crypto.random!!!
class CBC(BlockCipher):
""" The CBC class wraps block ciphers to make cipher block chaining (CBC) mode
algorithms. The initialization (IV) is automatic if set to None. Padding
is also automatic based on the Pad class used to initialize the algorithm
"""
def __init__(self, blockCipherInstance, padding = padWithPadLen()):
""" CBC algorithms are created by initializing with a BlockCipher instance """
self.baseCipher = blockCipherInstance
self.name = self.baseCipher.name + '_CBC'
self.blockSize = self.baseCipher.blockSize
self.keySize = self.baseCipher.keySize
self.padding = padding
self.baseCipher.padding = noPadding() # baseCipher should NOT pad!!
self.r = Random() # for IV generation, currently uses
# mediocre standard distro version <----------------
import time
newSeed = time.ctime()+str(self.r) # seed with instance location
self.r.seed(newSeed) # to make unique
self.reset()
def setKey(self, key):
self.baseCipher.setKey(key)
# Overload to reset both CBC state and the wrapped baseCipher
def resetEncrypt(self):
BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class)
self.baseCipher.resetEncrypt() # reset base cipher encrypt state
def resetDecrypt(self):
BlockCipher.resetDecrypt(self) # reset CBC state (super class)
self.baseCipher.resetDecrypt() # reset base cipher decrypt state
def encrypt(self, plainText, iv=None, more=None):
""" CBC encryption - overloads baseCipher to allow optional explicit IV
when iv=None, iv is auto generated!
"""
if self.encryptBlockCount == 0:
self.iv = iv
else:
assert(iv==None), 'IV used only on first call to encrypt'
return BlockCipher.encrypt(self,plainText, more=more)
def decrypt(self, cipherText, iv=None, more=None):
""" CBC decryption - overloads baseCipher to allow optional explicit IV
when iv=None, iv is auto generated!
"""
if self.decryptBlockCount == 0:
self.iv = iv
else:
assert(iv==None), 'IV used only on first call to decrypt'
return BlockCipher.decrypt(self, cipherText, more=more)
def encryptBlock(self, plainTextBlock):
""" CBC block encryption, IV is set with 'encrypt' """
auto_IV = ''
if self.encryptBlockCount == 0:
if self.iv == None:
# generate IV and use
self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)])
self.prior_encr_CT_block = self.iv
auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic
else: # application provided IV
assert(len(self.iv) == self.blockSize ),'IV must be same length as block'
self.prior_encr_CT_block = self.iv
""" encrypt the prior CT XORed with the PT """
ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) )
self.prior_encr_CT_block = ct
return auto_IV+ct
def decryptBlock(self, encryptedBlock):
""" Decrypt a single block """
if self.decryptBlockCount == 0: # first call, process IV
if self.iv == None: # auto decrypt IV?
self.prior_CT_block = encryptedBlock
return ''
else:
assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption"
self.prior_CT_block = self.iv
dct = self.baseCipher.decryptBlock(encryptedBlock)
""" XOR the prior decrypted CT with the prior CT """
dct_XOR_priorCT = xor( self.prior_CT_block, dct )
self.prior_CT_block = encryptedBlock
return dct_XOR_priorCT
"""
AES_CBC Encryption Algorithm
"""
class AES_CBC(CBC):
""" AES encryption in CBC feedback mode """
def __init__(self, key=None, padding=padWithPadLen(), keySize=16):
CBC.__init__( self, AES(key, noPadding(), keySize), padding)
self.name = 'AES_CBC'

View File

@@ -0,0 +1,290 @@
#! /usr/bin/env python
import sys, os
import hmac
from struct import pack
import hashlib
# interface to needed routines libalfcrypto
def _load_libalfcrypto():
import ctypes
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
Structure, c_ulong, create_string_buffer, addressof, string_at, cast, sizeof
pointer_size = ctypes.sizeof(ctypes.c_voidp)
name_of_lib = None
if sys.platform.startswith('darwin'):
name_of_lib = 'libalfcrypto.dylib'
elif sys.platform.startswith('win'):
if pointer_size == 4:
name_of_lib = 'alfcrypto.dll'
else:
name_of_lib = 'alfcrypto64.dll'
else:
if pointer_size == 4:
name_of_lib = 'libalfcrypto32.so'
else:
name_of_lib = 'libalfcrypto64.so'
libalfcrypto = sys.path[0] + os.sep + name_of_lib
if not os.path.isfile(libalfcrypto):
raise Exception('libalfcrypto not found')
libalfcrypto = CDLL(libalfcrypto)
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
def F(restype, name, argtypes):
func = getattr(libalfcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
# aes cbc decryption
#
# struct aes_key_st {
# unsigned long rd_key[4 *(AES_MAXNR + 1)];
# int rounds;
# };
#
# typedef struct aes_key_st AES_KEY;
#
# int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
#
#
# void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
# const unsigned long length, const AES_KEY *key,
# unsigned char *ivec, const int enc);
AES_MAXNR = 14
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, c_int])
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
# Pukall 1 Cipher
# unsigned char *PC1(const unsigned char *key, unsigned int klen, const unsigned char *src,
# unsigned char *dest, unsigned int len, int decryption);
PC1 = F(c_char_p, 'PC1', [c_char_p, c_ulong, c_char_p, c_char_p, c_ulong, c_ulong])
# Topaz Encryption
# typedef struct _TpzCtx {
# unsigned int v[2];
# } TpzCtx;
#
# void topazCryptoInit(TpzCtx *ctx, const unsigned char *key, int klen);
# void topazCryptoDecrypt(const TpzCtx *ctx, const unsigned char *in, unsigned char *out, int len);
class TPZ_CTX(Structure):
_fields_ = [('v', c_long * 2)]
TPZ_CTX_p = POINTER(TPZ_CTX)
topazCryptoInit = F(None, 'topazCryptoInit', [TPZ_CTX_p, c_char_p, c_ulong])
topazCryptoDecrypt = F(None, 'topazCryptoDecrypt', [TPZ_CTX_p, c_char_p, c_char_p, c_ulong])
class AES_CBC(object):
def __init__(self):
self._blocksize = 0
self._keyctx = None
self._iv = 0
def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise Exception('AES CBC improper key used')
return
keyctx = self._keyctx = AES_KEY()
self._iv = iv
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0:
raise Exception('Failed to initialize AES CBC key')
def decrypt(self, data):
out = create_string_buffer(len(data))
mutable_iv = create_string_buffer(self._iv, len(self._iv))
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, mutable_iv, 0)
if rv == 0:
raise Exception('AES CBC decryption failed')
return out.raw
class Pukall_Cipher(object):
def __init__(self):
self.key = None
def PC1(self, key, src, decryption=True):
self.key = key
out = create_string_buffer(len(src))
de = 0
if decryption:
de = 1
rv = PC1(key, len(key), src, out, len(src), de)
return out.raw
class Topaz_Cipher(object):
def __init__(self):
self._ctx = None
def ctx_init(self, key):
tpz_ctx = self._ctx = TPZ_CTX()
topazCryptoInit(tpz_ctx, key, len(key))
return tpz_ctx
def decrypt(self, data, ctx=None):
if ctx == None:
ctx = self._ctx
out = create_string_buffer(len(data))
topazCryptoDecrypt(ctx, data, out, len(data))
return out.raw
print "Using Library AlfCrypto DLL/DYLIB/SO"
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
def _load_python_alfcrypto():
import aescbc
class Pukall_Cipher(object):
def __init__(self):
self.key = None
def PC1(self, key, src, decryption=True):
sum1 = 0;
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
print "Bad key length!"
return None
wkey = []
for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
dst = ""
for i in xrange(len(src)):
temp1 = 0;
byteXorVal = 0;
for j in xrange(8):
temp1 ^= wkey[j]
sum2 = (sum2+j)*20021 + sum1
sum1 = (temp1*346)&0xFFFF
sum2 = (sum2+sum1)&0xFFFF
temp1 = (temp1*20021+1)&0xFFFF
byteXorVal ^= temp1 ^ sum2
curByte = ord(src[i])
if not decryption:
keyXorVal = curByte * 257;
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
if decryption:
keyXorVal = curByte * 257;
for j in xrange(8):
wkey[j] ^= keyXorVal;
dst+=chr(curByte)
return dst
class Topaz_Cipher(object):
def __init__(self):
self._ctx = None
def ctx_init(self, key):
ctx1 = 0x0CAFFE19E
for keyChar in key:
keyByte = ord(keyChar)
ctx2 = ctx1
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
self._ctx = [ctx1, ctx2]
return [ctx1,ctx2]
def decrypt(self, data, ctx=None):
if ctx == None:
ctx = self._ctx
ctx1 = ctx[0]
ctx2 = ctx[1]
plainText = ""
for dataChar in data:
dataByte = ord(dataChar)
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
ctx2 = ctx1
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
plainText += chr(m)
return plainText
class AES_CBC(object):
def __init__(self):
self._key = None
self._iv = None
self.aes = None
def set_decrypt_key(self, userkey, iv):
self._key = userkey
self._iv = iv
self.aes = aescbc.AES_CBC(userkey, aescbc.noPadding(), len(userkey))
def decrypt(self, data):
iv = self._iv
cleartext = self.aes.decrypt(iv + data)
return cleartext
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
def _load_crypto():
AES_CBC = Pukall_Cipher = Topaz_Cipher = None
cryptolist = (_load_libalfcrypto, _load_python_alfcrypto)
for loader in cryptolist:
try:
AES_CBC, Pukall_Cipher, Topaz_Cipher = loader()
break
except (ImportError, Exception):
pass
return AES_CBC, Pukall_Cipher, Topaz_Cipher
AES_CBC, Pukall_Cipher, Topaz_Cipher = _load_crypto()
class KeyIVGen(object):
# this only exists in openssl so we will use pure python implementation instead
# PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
# [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
def pbkdf2(self, passwd, salt, iter, keylen):
def xorstr( a, b ):
if len(a) != len(b):
raise Exception("xorstr(): lengths differ")
return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b)))
def prf( h, data ):
hm = h.copy()
hm.update( data )
return hm.digest()
def pbkdf2_F( h, salt, itercount, blocknum ):
U = prf( h, salt + pack('>i',blocknum ) )
T = U
for i in range(2, itercount+1):
U = prf( h, U )
T = xorstr( T, U )
return T
sha = hashlib.sha1
digest_size = sha().digest_size
# l - number of output blocks to produce
l = keylen / digest_size
if keylen % digest_size != 0:
l += 1
h = hmac.new( passwd, None, sha )
T = ""
for i in range(1, l+1):
T += pbkdf2_F( h, salt, iter, i )
return T[0: keylen]

View File

@@ -0,0 +1,899 @@
#! /usr/bin/python
"""
Comprehensive Mazama Book DRM with Topaz Cryptography V2.2
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdBHJ4CNc6DNFCw4MRCw4SWAK6
M8hYfnNEI0yQmn5Ti+W8biT7EatpauE/5jgQMPBmdNrDr1hbHyHBSP7xeC2qlRWC
B62UCxeu/fpfnvNHDN/wPWWH4jynZ2M6cdcnE5LQ+FfeKqZn7gnG2No1U9h7oOHx
y2/pHuYme7U1TsgSjwIDAQAB
-----END PUBLIC KEY-----
"""
from __future__ import with_statement
import csv
import sys
import os
import getopt
import zlib
from struct import pack
from struct import unpack
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
string_at, Structure, c_void_p, cast
import _winreg as winreg
import Tkinter
import Tkconstants
import tkMessageBox
import traceback
import hashlib
MAX_PATH = 255
kernel32 = windll.kernel32
advapi32 = windll.advapi32
crypt32 = windll.crypt32
global kindleDatabase
global bookFile
global bookPayloadOffset
global bookHeaderRecords
global bookMetadata
global bookKey
global command
#
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
#
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
#
# Exceptions for all the problems that might happen during the script
#
class CMBDTCError(Exception):
pass
class CMBDTCFatal(Exception):
pass
#
# Stolen stuff
#
class DataBlob(Structure):
_fields_ = [('cbData', c_uint),
('pbData', c_void_p)]
DataBlob_p = POINTER(DataBlob)
def GetSystemDirectory():
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
GetSystemDirectoryW.restype = c_uint
def GetSystemDirectory():
buffer = create_unicode_buffer(MAX_PATH + 1)
GetSystemDirectoryW(buffer, len(buffer))
return buffer.value
return GetSystemDirectory
GetSystemDirectory = GetSystemDirectory()
def GetVolumeSerialNumber():
GetVolumeInformationW = kernel32.GetVolumeInformationW
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
POINTER(c_uint), POINTER(c_uint),
POINTER(c_uint), c_wchar_p, c_uint]
GetVolumeInformationW.restype = c_uint
def GetVolumeSerialNumber(path):
vsn = c_uint(0)
GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
return vsn.value
return GetVolumeSerialNumber
GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetUserName():
GetUserNameW = advapi32.GetUserNameW
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
GetUserNameW.restype = c_uint
def GetUserName():
buffer = create_unicode_buffer(32)
size = c_uint(len(buffer))
while not GetUserNameW(buffer, byref(size)):
buffer = create_unicode_buffer(len(buffer) * 2)
size.value = len(buffer)
return buffer.value.encode('utf-16-le')[::2]
return GetUserName
GetUserName = GetUserName()
def CryptUnprotectData():
_CryptUnprotectData = crypt32.CryptUnprotectData
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
c_void_p, c_void_p, c_uint, DataBlob_p]
_CryptUnprotectData.restype = c_uint
def CryptUnprotectData(indata, entropy):
indatab = create_string_buffer(indata)
indata = DataBlob(len(indata), cast(indatab, c_void_p))
entropyb = create_string_buffer(entropy)
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
outdata = DataBlob()
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
None, None, 0, byref(outdata)):
raise CMBDTCFatal("Failed to Unprotect Data")
return string_at(outdata.pbData, outdata.cbData)
return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData()
#
# Returns the MD5 digest of "message"
#
def MD5(message):
ctx = hashlib.md5()
ctx.update(message)
return ctx.digest()
#
# Returns the MD5 digest of "message"
#
def SHA1(message):
ctx = hashlib.sha1()
ctx.update(message)
return ctx.digest()
#
# Open the book file at path
#
def openBook(path):
try:
return open(path,'rb')
except:
raise CMBDTCFatal("Could not open book file: " + path)
#
# Encode the bytes in data with the characters in map
#
def encode(data, map):
result = ""
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
R = value % len(map)
result += map[Q]
result += map[R]
return result
#
# Hash the bytes in data and then encode the digest with the characters in map
#
def encodeHash(data,map):
return encode(MD5(data),map)
#
# Decode the string in data with the characters in map. Returns the decoded bytes
#
def decode(data,map):
result = ""
for i in range (0,len(data),2):
high = map.find(data[i])
low = map.find(data[i+1])
value = (((high * 0x40) ^ 0x80) & 0xFF) + low
result += pack("B",value)
return result
#
# Locate and open the Kindle.info file (Hopefully in the way it is done in the Kindle application)
#
def openKindleInfo():
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
#
# Parse the Kindle.info file and return the records as a list of key-values
#
def parseKindleInfo():
DB = {}
infoReader = openKindleInfo()
infoReader.read(1)
data = infoReader.read()
items = data.split('{')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
#
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string. (Totally not optimal)
#
def findNameForHash(hash):
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
result = ""
for name in names:
if hash == encodeHash(name, charMap2):
result = name
break
return name
#
# Print all the records from the kindle.info file (option -i)
#
def printKindleInfo():
for record in kindleDatabase:
name = findNameForHash(record)
if name != "" :
print (name)
print ("--------------------------\n")
else :
print ("Unknown Record")
print getKindleInfoValueForHash(record)
print "\n"
#
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
#
def getKindleInfoValueForHash(hashedKey):
global kindleDatabase
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
return CryptUnprotectData(encryptedValue,"")
#
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
#
def getKindleInfoValueForKey(key):
return getKindleInfoValueForHash(encodeHash(key,charMap2))
#
# Get a 7 bit encoded number from the book file
#
def bookReadEncodedNumber():
flag = False
data = ord(bookFile.read(1))
if data == 0xFF:
flag = True
data = ord(bookFile.read(1))
if data >= 0x80:
datax = (data & 0x7F)
while data >= 0x80 :
data = ord(bookFile.read(1))
datax = (datax <<7) + (data & 0x7F)
data = datax
if flag:
data = -data
return data
#
# Encode a number in 7 bit format
#
def encodeNumber(number):
result = ""
negative = False
flag = 0
if number < 0 :
number = -number + 1
negative = True
while True:
byte = number & 0x7F
number = number >> 7
byte += flag
result += chr(byte)
flag = 0x80
if number == 0 :
if (byte == 0xFF and negative == False) :
result += chr(0x80)
break
if negative:
result += chr(0xFF)
return result[::-1]
#
# Get a length prefixed string from the file
#
def bookReadString():
stringLength = bookReadEncodedNumber()
return unpack(str(stringLength)+"s",bookFile.read(stringLength))[0]
#
# Returns a length prefixed string
#
def lengthPrefixString(data):
return encodeNumber(len(data))+data
#
# Read and return the data of one header record at the current book file position [[offset,compressedLength,decompressedLength],...]
#
def bookReadHeaderRecordData():
nbValues = bookReadEncodedNumber()
values = []
for i in range (0,nbValues):
values.append([bookReadEncodedNumber(),bookReadEncodedNumber(),bookReadEncodedNumber()])
return values
#
# Read and parse one header record at the current book file position and return the associated data [[offset,compressedLength,decompressedLength],...]
#
def parseTopazHeaderRecord():
if ord(bookFile.read(1)) != 0x63:
raise CMBDTCFatal("Parse Error : Invalid Header")
tag = bookReadString()
record = bookReadHeaderRecordData()
return [tag,record]
#
# Parse the header of a Topaz file, get all the header records and the offset for the payload
#
def parseTopazHeader():
global bookHeaderRecords
global bookPayloadOffset
magic = unpack("4s",bookFile.read(4))[0]
if magic != 'TPZ0':
raise CMBDTCFatal("Parse Error : Invalid Header, not a Topaz file")
nbRecords = bookReadEncodedNumber()
bookHeaderRecords = {}
for i in range (0,nbRecords):
result = parseTopazHeaderRecord()
bookHeaderRecords[result[0]] = result[1]
if ord(bookFile.read(1)) != 0x64 :
raise CMBDTCFatal("Parse Error : Invalid Header")
bookPayloadOffset = bookFile.tell()
#
# Get a record in the book payload, given its name and index. If necessary the record is decrypted. The record is not decompressed
#
def getBookPayloadRecord(name, index):
encrypted = False
try:
recordOffset = bookHeaderRecords[name][index][0]
except:
raise CMBDTCFatal("Parse Error : Invalid Record, record not found")
bookFile.seek(bookPayloadOffset + recordOffset)
tag = bookReadString()
if tag != name :
raise CMBDTCFatal("Parse Error : Invalid Record, record name doesn't match")
recordIndex = bookReadEncodedNumber()
if recordIndex < 0 :
encrypted = True
recordIndex = -recordIndex -1
if recordIndex != index :
raise CMBDTCFatal("Parse Error : Invalid Record, index doesn't match")
if bookHeaderRecords[name][index][2] != 0 :
record = bookFile.read(bookHeaderRecords[name][index][2])
else:
record = bookFile.read(bookHeaderRecords[name][index][1])
if encrypted:
ctx = topazCryptoInit(bookKey)
record = topazCryptoDecrypt(record,ctx)
return record
#
# Extract, decrypt and decompress a book record indicated by name and index and print it or save it in "filename"
#
def extractBookPayloadRecord(name, index, filename):
compressed = False
try:
compressed = bookHeaderRecords[name][index][2] != 0
record = getBookPayloadRecord(name,index)
except:
print("Could not find record")
if compressed:
try:
record = zlib.decompress(record)
except:
raise CMBDTCFatal("Could not decompress record")
if filename != "":
try:
file = open(filename,"wb")
file.write(record)
file.close()
except:
raise CMBDTCFatal("Could not write to destination file")
else:
print(record)
#
# return next record [key,value] from the book metadata from the current book position
#
def readMetadataRecord():
return [bookReadString(),bookReadString()]
#
# Parse the metadata record from the book payload and return a list of [key,values]
#
def parseMetadata():
global bookHeaderRecords
global bookPayloadAddress
global bookMetadata
bookMetadata = {}
bookFile.seek(bookPayloadOffset + bookHeaderRecords["metadata"][0][0])
tag = bookReadString()
if tag != "metadata" :
raise CMBDTCFatal("Parse Error : Record Names Don't Match")
flags = ord(bookFile.read(1))
nbRecords = ord(bookFile.read(1))
for i in range (0,nbRecords) :
record =readMetadataRecord()
bookMetadata[record[0]] = record[1]
#
# Returns two bit at offset from a bit field
#
def getTwoBitsFromBitField(bitField,offset):
byteNumber = offset // 4
bitPosition = 6 - 2*(offset % 4)
return ord(bitField[byteNumber]) >> bitPosition & 3
#
# Returns the six bits at offset from a bit field
#
def getSixBitsFromBitField(bitField,offset):
offset *= 3
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
return value
#
# 8 bits to six bits encoding from hash to generate PID string
#
def encodePID(hash):
global charMap3
PID = ""
for position in range (0,8):
PID += charMap3[getSixBitsFromBitField(hash,position)]
return PID
#
# Context initialisation for the Topaz Crypto
#
def topazCryptoInit(key):
ctx1 = 0x0CAFFE19E
for keyChar in key:
keyByte = ord(keyChar)
ctx2 = ctx1
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
return [ctx1,ctx2]
#
# decrypt data with the context prepared by topazCryptoInit()
#
def topazCryptoDecrypt(data, ctx):
ctx1 = ctx[0]
ctx2 = ctx[1]
plainText = ""
for dataChar in data:
dataByte = ord(dataChar)
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
ctx2 = ctx1
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
plainText += chr(m)
return plainText
#
# Decrypt a payload record with the PID
#
def decryptRecord(data,PID):
ctx = topazCryptoInit(PID)
return topazCryptoDecrypt(data, ctx)
#
# Try to decrypt a dkey record (contains the book PID)
#
def decryptDkeyRecord(data,PID):
record = decryptRecord(data,PID)
fields = unpack("3sB8sB8s3s",record)
if fields[0] != "PID" or fields[5] != "pid" :
raise CMBDTCError("Didn't find PID magic numbers in record")
elif fields[1] != 8 or fields[3] != 8 :
raise CMBDTCError("Record didn't contain correct length fields")
elif fields[2] != PID :
raise CMBDTCError("Record didn't contain PID")
return fields[4]
#
# Decrypt all the book's dkey records (contain the book PID)
#
def decryptDkeyRecords(data,PID):
nbKeyRecords = ord(data[0])
records = []
data = data[1:]
for i in range (0,nbKeyRecords):
length = ord(data[0])
try:
key = decryptDkeyRecord(data[1:length+1],PID)
records.append(key)
except CMBDTCError:
pass
data = data[1+length:]
return records
#
# Encryption table used to generate the device PID
#
def generatePidEncryptionTable() :
table = []
for counter1 in range (0,0x100):
value = counter1
for counter2 in range (0,8):
if (value & 1 == 0) :
value = value >> 1
else :
value = value >> 1
value = value ^ 0xEDB88320
table.append(value)
return table
#
# Seed value used to generate the device PID
#
def generatePidSeed(table,dsn) :
value = 0
for counter in range (0,4) :
index = (ord(dsn[counter]) ^ value) &0xFF
value = (value >> 8) ^ table[index]
return value
#
# Generate the device PID
#
def generateDevicePID(table,dsn,nbRoll):
seed = generatePidSeed(table,dsn)
pidAscii = ""
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
index = 0
for counter in range (0,nbRoll):
pid[index] = pid[index] ^ ord(dsn[counter])
index = (index+1) %8
for counter in range (0,8):
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
pidAscii += charMap4[index]
return pidAscii
#
# Create decrypted book payload
#
def createDecryptedPayload(payload):
# store data to be able to create the header later
headerData= []
currentOffset = 0
# Add social DRM to decrypted files
try:
data = getKindleInfoValueForKey("kindle.name.info")+":"+ getKindleInfoValueForKey("login")
if payload!= None:
payload.write(lengthPrefixString("sdrm"))
payload.write(encodeNumber(0))
payload.write(data)
else:
currentOffset += len(lengthPrefixString("sdrm"))
currentOffset += len(encodeNumber(0))
currentOffset += len(data)
except:
pass
for headerRecord in bookHeaderRecords:
name = headerRecord
newRecord = []
if name != "dkey" :
for index in range (0,len(bookHeaderRecords[name])) :
offset = currentOffset
if payload != None:
# write tag
payload.write(lengthPrefixString(name))
# write data
payload.write(encodeNumber(index))
payload.write(getBookPayloadRecord(name, index))
else :
currentOffset += len(lengthPrefixString(name))
currentOffset += len(encodeNumber(index))
currentOffset += len(getBookPayloadRecord(name, index))
newRecord.append([offset,bookHeaderRecords[name][index][1],bookHeaderRecords[name][index][2]])
headerData.append([name,newRecord])
return headerData
#
# Create decrypted book
#
def createDecryptedBook(outputFile):
outputFile = open(outputFile,"wb")
# Write the payload in a temporary file
headerData = createDecryptedPayload(None)
outputFile.write("TPZ0")
outputFile.write(encodeNumber(len(headerData)))
for header in headerData :
outputFile.write(chr(0x63))
outputFile.write(lengthPrefixString(header[0]))
outputFile.write(encodeNumber(len(header[1])))
for numbers in header[1] :
outputFile.write(encodeNumber(numbers[0]))
outputFile.write(encodeNumber(numbers[1]))
outputFile.write(encodeNumber(numbers[2]))
outputFile.write(chr(0x64))
createDecryptedPayload(outputFile)
outputFile.close()
#
# Set the command to execute by the programm according to cmdLine parameters
#
def setCommand(name) :
global command
if command != "" :
raise CMBDTCFatal("Invalid command line parameters")
else :
command = name
#
# Program usage
#
def usage():
print("\nUsage:")
print("\nCMBDTC.py [options] bookFileName\n")
print("-p Adds a PID to the list of PIDs that are tried to decrypt the book key (can be used several times)")
print("-d Saves a decrypted copy of the book")
print("-r Prints or writes to disk a record indicated in the form name:index (e.g \"img:0\")")
print("-o Output file name to write records and decrypted books")
print("-v Verbose (can be used several times)")
print("-i Prints kindle.info database")
#
# Main
#
def main(argv=sys.argv):
global kindleDatabase
global bookMetadata
global bookKey
global bookFile
global command
progname = os.path.basename(argv[0])
verbose = 0
recordName = ""
recordIndex = 0
outputFile = ""
PIDs = []
kindleDatabase = None
command = ""
try:
opts, args = getopt.getopt(sys.argv[1:], "vdir:o:p:")
except getopt.GetoptError, err:
# print help information and exit:
print str(err) # will print something like "option -a not recognized"
usage()
sys.exit(2)
if len(opts) == 0 and len(args) == 0 :
usage()
sys.exit(2)
for o, a in opts:
if o == "-v":
verbose+=1
if o == "-i":
setCommand("printInfo")
if o =="-o":
if a == None :
raise CMBDTCFatal("Invalid parameter for -o")
outputFile = a
if o =="-r":
setCommand("printRecord")
try:
recordName,recordIndex = a.split(':')
except:
raise CMBDTCFatal("Invalid parameter for -r")
if o =="-p":
PIDs.append(a)
if o =="-d":
setCommand("doit")
if command == "" :
raise CMBDTCFatal("No action supplied on command line")
#
# Read the encrypted database
#
try:
kindleDatabase = parseKindleInfo()
except Exception, message:
if verbose>0:
print(message)
if kindleDatabase != None :
if command == "printInfo" :
printKindleInfo()
#
# Compute the DSN
#
# Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(str(GetVolumeSerialNumber(GetSystemDirectory().split('\\')[0] + '\\')),charMap1)
# Get the current user name
encodedUsername = encodeHash(GetUserName(),charMap1)
# concat, hash and encode
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
if verbose >1:
print("DSN: " + DSN)
#
# Compute the device PID
#
table = generatePidEncryptionTable()
devicePID = generateDevicePID(table,DSN,4)
PIDs.append(devicePID)
if verbose > 0:
print("Device PID: " + devicePID)
#
# Open book and parse metadata
#
if len(args) == 1:
bookFile = openBook(args[0])
parseTopazHeader()
parseMetadata()
#
# Compute book PID
#
# Get the account token
if kindleDatabase != None:
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
if verbose >1:
print("Account Token: " + kindleAccountToken)
keysRecord = bookMetadata["keys"]
keysRecordRecord = bookMetadata[keysRecord]
pidHash = SHA1(DSN+kindleAccountToken+keysRecord+keysRecordRecord)
bookPID = encodePID(pidHash)
PIDs.append(bookPID)
if verbose > 0:
print ("Book PID: " + bookPID )
#
# Decrypt book key
#
dkey = getBookPayloadRecord('dkey', 0)
bookKeys = []
for PID in PIDs :
bookKeys+=decryptDkeyRecords(dkey,PID)
if len(bookKeys) == 0 :
if verbose > 0 :
print ("Book key could not be found. Maybe this book is not registered with this device.")
else :
bookKey = bookKeys[0]
if verbose > 0:
print("Book key: " + bookKey.encode('hex'))
if command == "printRecord" :
extractBookPayloadRecord(recordName,int(recordIndex),outputFile)
if outputFile != "" and verbose>0 :
print("Wrote record to file: "+outputFile)
elif command == "doit" :
if outputFile!="" :
createDecryptedBook(outputFile)
if verbose >0 :
print ("Decrypted book saved. Don't pirate!")
elif verbose > 0:
print("Output file name was not supplied.")
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,59 @@
from PyQt4.Qt import QWidget, QVBoxLayout, QLabel, QLineEdit
from calibre.utils.config import JSONConfig
# This is where all preferences for this plugin will be stored
# You should always prefix your config file name with plugins/,
# so as to ensure you dont accidentally clobber a calibre config file
prefs = JSONConfig('plugins/K4MobiDeDRM')
# Set defaults
prefs.defaults['pids'] = ""
prefs.defaults['serials'] = ""
prefs.defaults['WINEPREFIX'] = None
class ConfigWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
self.l = QVBoxLayout()
self.setLayout(self.l)
self.serialLabel = QLabel('Kindle Serial numbers (separate with commas, no spaces)')
self.l.addWidget(self.serialLabel)
self.serials = QLineEdit(self)
self.serials.setText(prefs['serials'])
self.l.addWidget(self.serials)
self.serialLabel.setBuddy(self.serials)
self.pidLabel = QLabel('Mobipocket PIDs (separate with commas, no spaces)')
self.l.addWidget(self.pidLabel)
self.pids = QLineEdit(self)
self.pids.setText(prefs['pids'])
self.l.addWidget(self.pids)
self.pidLabel.setBuddy(self.serials)
self.wpLabel = QLabel('For Linux only: WINEPREFIX (enter absolute path)')
self.l.addWidget(self.wpLabel)
self.wineprefix = QLineEdit(self)
wineprefix = prefs['WINEPREFIX']
if wineprefix is not None:
self.wineprefix.setText(wineprefix)
else:
self.wineprefix.setText('')
self.l.addWidget(self.wineprefix)
self.wpLabel.setBuddy(self.wineprefix)
def save_settings(self):
prefs['pids'] = str(self.pids.text())
prefs['serials'] = str(self.serials.text())
winepref=str(self.wineprefix.text())
if winepref.strip() != '':
prefs['WINEPREFIX'] = winepref
else:
prefs['WINEPREFIX'] = None

View File

@@ -34,11 +34,11 @@ def readEncodedNumber(file):
data = ord(c) data = ord(c)
if data == 0xFF: if data == 0xFF:
flag = True flag = True
c = file.read(1) c = file.read(1)
if (len(c) == 0): if (len(c) == 0):
return None return None
data = ord(c) data = ord(c)
if data >= 0x80: if data >= 0x80:
datax = (data & 0x7F) datax = (data & 0x7F)
@@ -51,7 +51,7 @@ def readEncodedNumber(file):
data = datax data = datax
if flag: if flag:
data = -data data = -data
return data return data
@@ -59,29 +59,29 @@ def readEncodedNumber(file):
# most significant byte first which has the high bit set # most significant byte first which has the high bit set
def encodeNumber(number): def encodeNumber(number):
result = "" result = ""
negative = False negative = False
flag = 0 flag = 0
if number < 0 : if number < 0 :
number = -number + 1 number = -number + 1
negative = True negative = True
while True: while True:
byte = number & 0x7F byte = number & 0x7F
number = number >> 7 number = number >> 7
byte += flag byte += flag
result += chr(byte) result += chr(byte)
flag = 0x80 flag = 0x80
if number == 0 : if number == 0 :
if (byte == 0xFF and negative == False) : if (byte == 0xFF and negative == False) :
result += chr(0x80) result += chr(0x80)
break break
if negative: if negative:
result += chr(0xFF) result += chr(0xFF)
return result[::-1] return result[::-1]
@@ -214,6 +214,7 @@ class PageParser(object):
'links.title' : (1, 'text', 0, 0), 'links.title' : (1, 'text', 0, 0),
'links.href' : (1, 'text', 0, 0), 'links.href' : (1, 'text', 0, 0),
'links.type' : (1, 'text', 0, 0), 'links.type' : (1, 'text', 0, 0),
'links.id' : (1, 'number', 0, 0),
'paraCont' : (0, 'number', 1, 1), 'paraCont' : (0, 'number', 1, 1),
'paraCont.rootID' : (1, 'number', 0, 0), 'paraCont.rootID' : (1, 'number', 0, 0),
@@ -239,6 +240,7 @@ class PageParser(object):
'group' : (1, 'snippets', 1, 0), 'group' : (1, 'snippets', 1, 0),
'group.type' : (1, 'scalar_text', 0, 0), 'group.type' : (1, 'scalar_text', 0, 0),
'group._tag' : (1, 'scalar_text', 0, 0), 'group._tag' : (1, 'scalar_text', 0, 0),
'group.orientation': (1, 'scalar_text', 0, 0),
'region' : (1, 'snippets', 1, 0), 'region' : (1, 'snippets', 1, 0),
'region.type' : (1, 'scalar_text', 0, 0), 'region.type' : (1, 'scalar_text', 0, 0),
@@ -246,6 +248,7 @@ class PageParser(object):
'region.y' : (1, 'scalar_number', 0, 0), 'region.y' : (1, 'scalar_number', 0, 0),
'region.h' : (1, 'scalar_number', 0, 0), 'region.h' : (1, 'scalar_number', 0, 0),
'region.w' : (1, 'scalar_number', 0, 0), 'region.w' : (1, 'scalar_number', 0, 0),
'region.orientation' : (1, 'scalar_text', 0, 0),
'empty_text_region' : (1, 'snippets', 1, 0), 'empty_text_region' : (1, 'snippets', 1, 0),
@@ -265,6 +268,8 @@ class PageParser(object):
'paragraph.gridSize' : (1, 'scalar_number', 0, 0), 'paragraph.gridSize' : (1, 'scalar_number', 0, 0),
'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0), 'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0),
'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0), 'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0),
'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0),
'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0),
'word_semantic' : (1, 'snippets', 1, 1), 'word_semantic' : (1, 'snippets', 1, 1),
@@ -284,6 +289,8 @@ class PageParser(object):
'_span.gridSize' : (1, 'scalar_number', 0, 0), '_span.gridSize' : (1, 'scalar_number', 0, 0),
'_span.gridBottomCenter' : (1, 'scalar_number', 0, 0), '_span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
'_span.gridTopCenter' : (1, 'scalar_number', 0, 0), '_span.gridTopCenter' : (1, 'scalar_number', 0, 0),
'_span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
'_span.gridEndCenter' : (1, 'scalar_number', 0, 0),
'span' : (1, 'snippets', 1, 0), 'span' : (1, 'snippets', 1, 0),
'span.firstWord' : (1, 'scalar_number', 0, 0), 'span.firstWord' : (1, 'scalar_number', 0, 0),
@@ -291,6 +298,8 @@ class PageParser(object):
'span.gridSize' : (1, 'scalar_number', 0, 0), 'span.gridSize' : (1, 'scalar_number', 0, 0),
'span.gridBottomCenter' : (1, 'scalar_number', 0, 0), 'span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
'span.gridTopCenter' : (1, 'scalar_number', 0, 0), 'span.gridTopCenter' : (1, 'scalar_number', 0, 0),
'span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
'extratokens' : (1, 'snippets', 1, 0), 'extratokens' : (1, 'snippets', 1, 0),
'extratokens.type' : (1, 'scalar_text', 0, 0), 'extratokens.type' : (1, 'scalar_text', 0, 0),
@@ -609,65 +618,70 @@ class PageParser(object):
nodename = fullpathname.pop() nodename = fullpathname.pop()
ilvl = len(fullpathname) ilvl = len(fullpathname)
indent = ' ' * (3 * ilvl) indent = ' ' * (3 * ilvl)
result = indent + '<' + nodename + '>' rlst = []
rlst.append(indent + '<' + nodename + '>')
if len(argList) > 0: if len(argList) > 0:
argres = '' alst = []
for j in argList: for j in argList:
if (argtype == 'text') or (argtype == 'scalar_text') : if (argtype == 'text') or (argtype == 'scalar_text') :
argres += j + '|' alst.append(j + '|')
else : else :
argres += str(j) + ',' alst.append(str(j) + ',')
argres = "".join(alst)
argres = argres[0:-1] argres = argres[0:-1]
if argtype == 'snippets' : if argtype == 'snippets' :
result += 'snippets:' + argres rlst.append('snippets:' + argres)
else : else :
result += argres rlst.append(argres)
if len(subtagList) > 0 : if len(subtagList) > 0 :
result += '\n' rlst.append('\n')
for j in subtagList: for j in subtagList:
if len(j) > 0 : if len(j) > 0 :
result += self.formatTag(j) rlst.append(self.formatTag(j))
result += indent + '</' + nodename + '>\n' rlst.append(indent + '</' + nodename + '>\n')
else: else:
result += '</' + nodename + '>\n' rlst.append('</' + nodename + '>\n')
return result return "".join(rlst)
# flatten tag # flatten tag
def flattenTag(self, node): def flattenTag(self, node):
name = node[0] name = node[0]
subtagList = node[1] subtagList = node[1]
argtype = node[2] argtype = node[2]
argList = node[3] argList = node[3]
result = name rlst = []
rlst.append(name)
if (len(argList) > 0): if (len(argList) > 0):
argres = '' alst = []
for j in argList: for j in argList:
if (argtype == 'text') or (argtype == 'scalar_text') : if (argtype == 'text') or (argtype == 'scalar_text') :
argres += j + '|' alst.append(j + '|')
else : else :
argres += str(j) + '|' alst.append(str(j) + '|')
argres = "".join(alst)
argres = argres[0:-1] argres = argres[0:-1]
if argtype == 'snippets' : if argtype == 'snippets' :
result += '.snippets=' + argres rlst.append('.snippets=' + argres)
else : else :
result += '=' + argres rlst.append('=' + argres)
result += '\n' rlst.append('\n')
for j in subtagList: for j in subtagList:
if len(j) > 0 : if len(j) > 0 :
result += self.flattenTag(j) rlst.append(self.flattenTag(j))
return result return "".join(rlst)
# reduce create xml output # reduce create xml output
def formatDoc(self, flat_xml): def formatDoc(self, flat_xml):
result = '' rlst = []
for j in self.doc : for j in self.doc :
if len(j) > 0: if len(j) > 0:
if flat_xml: if flat_xml:
result += self.flattenTag(j) rlst.append(self.flattenTag(j))
else: else:
result += self.formatTag(j) rlst.append(self.formatTag(j))
result = "".join(rlst)
if self.debug : print result if self.debug : print result
return result return result

View File

@@ -86,4 +86,3 @@ def main(argv=sys.argv):
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) sys.exit(main())

View File

@@ -43,4 +43,3 @@ def main(argv=sys.argv):
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) sys.exit(main())

View File

@@ -52,4 +52,3 @@ def main(argv=sys.argv):
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) sys.exit(main())

View File

@@ -178,7 +178,7 @@ def sanitizeFileName(s):
def fixKey(key): def fixKey(key):
def fixByte(b): def fixByte(b):
return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80) return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80)
return "".join([chr(fixByte(ord(a))) for a in key]) return "".join([chr(fixByte(ord(a))) for a in key])
def deXOR(text, sp, table): def deXOR(text, sp, table):
r='' r=''
@@ -212,7 +212,7 @@ class EreaderProcessor(object):
for i in xrange(len(data)): for i in xrange(len(data)):
j = (j + shuf) % len(data) j = (j + shuf) % len(data)
r[j] = data[i] r[j] = data[i]
assert len("".join(r)) == len(data) assert len("".join(r)) == len(data)
return "".join(r) return "".join(r)
r = unshuff(input[0:-8], cookie_shuf) r = unshuff(input[0:-8], cookie_shuf)
@@ -399,12 +399,12 @@ class EreaderProcessor(object):
return r return r
def cleanPML(pml): def cleanPML(pml):
# Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255) # Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
pml2 = pml pml2 = pml
for k in xrange(128,256): for k in xrange(128,256):
badChar = chr(k) badChar = chr(k)
pml2 = pml2.replace(badChar, '\\a%03d' % k) pml2 = pml2.replace(badChar, '\\a%03d' % k)
return pml2 return pml2
def convertEreaderToPml(infile, name, cc, outdir): def convertEreaderToPml(infile, name, cc, outdir):
if not os.path.exists(outdir): if not os.path.exists(outdir):
@@ -524,4 +524,3 @@ def main(argv=None):
if __name__ == "__main__": if __name__ == "__main__":
sys.stdout=Unbuffered(sys.stdout) sys.stdout=Unbuffered(sys.stdout)
sys.exit(main()) sys.exit(main())

View File

@@ -292,6 +292,11 @@ class DocParser(object):
if self.fixedimage : if self.fixedimage :
makeImage = makeImage or (pclass.find('cl-f-') >= 0) makeImage = makeImage or (pclass.find('cl-f-') >= 0)
# before creating an image make sure glyph info exists
gidList = self.getData('info.glyph.glyphID',0,-1)
makeImage = makeImage & (len(gidList) > 0)
if not makeImage : if not makeImage :
# standard all word paragraph # standard all word paragraph
for wordnum in xrange(first, last): for wordnum in xrange(first, last):
@@ -382,10 +387,10 @@ class DocParser(object):
ws_last = int(argres) ws_last = int(argres)
elif name.endswith('word.class'): elif name.endswith('word.class'):
(cname, space) = argres.split('-',1) (cname, space) = argres.split('-',1)
if space == '' : space = '0' if space == '' : space = '0'
if (cname == 'spaceafter') and (int(space) > 0) : if (cname == 'spaceafter') and (int(space) > 0) :
word_class = 'sa' word_class = 'sa'
elif name.endswith('word.img.src'): elif name.endswith('word.img.src'):
result.append(('img' + word_class, int(argres))) result.append(('img' + word_class, int(argres)))
@@ -590,8 +595,8 @@ class DocParser(object):
def process(self): def process(self):
htmlpage = ''
tocinfo = '' tocinfo = ''
hlst = []
# get the ocr text # get the ocr text
(pos, argres) = self.findinDoc('info.word.ocrText',0,-1) (pos, argres) = self.findinDoc('info.word.ocrText',0,-1)
@@ -648,8 +653,8 @@ class DocParser(object):
# set anchor for link target on this page # set anchor for link target on this page
if not anchorSet and not first_para_continued: if not anchorSet and not first_para_continued:
htmlpage += '<div style="visibility: hidden; height: 0; width: 0;" id="' hlst.append('<div style="visibility: hidden; height: 0; width: 0;" id="')
htmlpage += self.id + '" title="pagetype_' + pagetype + '"></div>\n' hlst.append(self.id + '" title="pagetype_' + pagetype + '"></div>\n')
anchorSet = True anchorSet = True
# handle groups of graphics with text captions # handle groups of graphics with text captions
@@ -658,12 +663,12 @@ class DocParser(object):
if grptype != None: if grptype != None:
if grptype == 'graphic': if grptype == 'graphic':
gcstr = ' class="' + grptype + '"' gcstr = ' class="' + grptype + '"'
htmlpage += '<div' + gcstr + '>' hlst.append('<div' + gcstr + '>')
inGroup = True inGroup = True
elif (etype == 'grpend'): elif (etype == 'grpend'):
if inGroup: if inGroup:
htmlpage += '</div>\n' hlst.append('</div>\n')
inGroup = False inGroup = False
else: else:
@@ -673,25 +678,25 @@ class DocParser(object):
(pos, simgsrc) = self.findinDoc('img.src',start,end) (pos, simgsrc) = self.findinDoc('img.src',start,end)
if simgsrc: if simgsrc:
if inGroup: if inGroup:
htmlpage += '<img src="img/img%04d.jpg" alt="" />' % int(simgsrc) hlst.append('<img src="img/img%04d.jpg" alt="" />' % int(simgsrc))
else: else:
htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc) hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
elif regtype == 'chapterheading' : elif regtype == 'chapterheading' :
(pclass, pdesc) = self.getParaDescription(start,end, regtype) (pclass, pdesc) = self.getParaDescription(start,end, regtype)
if not breakSet: if not breakSet:
htmlpage += '<div style="page-break-after: always;">&nbsp;</div>\n' hlst.append('<div style="page-break-after: always;">&nbsp;</div>\n')
breakSet = True breakSet = True
tag = 'h1' tag = 'h1'
if pclass and (len(pclass) >= 7): if pclass and (len(pclass) >= 7):
if pclass[3:7] == 'ch1-' : tag = 'h1' if pclass[3:7] == 'ch1-' : tag = 'h1'
if pclass[3:7] == 'ch2-' : tag = 'h2' if pclass[3:7] == 'ch2-' : tag = 'h2'
if pclass[3:7] == 'ch3-' : tag = 'h3' if pclass[3:7] == 'ch3-' : tag = 'h3'
htmlpage += '<' + tag + ' class="' + pclass + '">' hlst.append('<' + tag + ' class="' + pclass + '">')
else: else:
htmlpage += '<' + tag + '>' hlst.append('<' + tag + '>')
htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype) hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
htmlpage += '</' + tag + '>' hlst.append('</' + tag + '>')
elif (regtype == 'text') or (regtype == 'fixed') or (regtype == 'insert') or (regtype == 'listitem'): elif (regtype == 'text') or (regtype == 'fixed') or (regtype == 'insert') or (regtype == 'listitem'):
ptype = 'full' ptype = 'full'
@@ -705,11 +710,11 @@ class DocParser(object):
if pclass[3:6] == 'h1-' : tag = 'h4' if pclass[3:6] == 'h1-' : tag = 'h4'
if pclass[3:6] == 'h2-' : tag = 'h5' if pclass[3:6] == 'h2-' : tag = 'h5'
if pclass[3:6] == 'h3-' : tag = 'h6' if pclass[3:6] == 'h3-' : tag = 'h6'
htmlpage += '<' + tag + ' class="' + pclass + '">' hlst.append('<' + tag + ' class="' + pclass + '">')
htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype) hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
htmlpage += '</' + tag + '>' hlst.append('</' + tag + '>')
else : else :
htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype) hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
elif (regtype == 'tocentry') : elif (regtype == 'tocentry') :
ptype = 'full' ptype = 'full'
@@ -718,7 +723,7 @@ class DocParser(object):
first_para_continued = False first_para_continued = False
(pclass, pdesc) = self.getParaDescription(start,end, regtype) (pclass, pdesc) = self.getParaDescription(start,end, regtype)
tocinfo += self.buildTOCEntry(pdesc) tocinfo += self.buildTOCEntry(pdesc)
htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype) hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
elif (regtype == 'vertical') or (regtype == 'table') : elif (regtype == 'vertical') or (regtype == 'table') :
ptype = 'full' ptype = 'full'
@@ -728,13 +733,13 @@ class DocParser(object):
ptype = 'end' ptype = 'end'
first_para_continued = False first_para_continued = False
(pclass, pdesc) = self.getParaDescription(start, end, regtype) (pclass, pdesc) = self.getParaDescription(start, end, regtype)
htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype) hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
elif (regtype == 'synth_fcvr.center'): elif (regtype == 'synth_fcvr.center'):
(pos, simgsrc) = self.findinDoc('img.src',start,end) (pos, simgsrc) = self.findinDoc('img.src',start,end)
if simgsrc: if simgsrc:
htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc) hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
else : else :
print ' Making region type', regtype, print ' Making region type', regtype,
@@ -760,18 +765,19 @@ class DocParser(object):
if pclass[3:6] == 'h1-' : tag = 'h4' if pclass[3:6] == 'h1-' : tag = 'h4'
if pclass[3:6] == 'h2-' : tag = 'h5' if pclass[3:6] == 'h2-' : tag = 'h5'
if pclass[3:6] == 'h3-' : tag = 'h6' if pclass[3:6] == 'h3-' : tag = 'h6'
htmlpage += '<' + tag + ' class="' + pclass + '">' hlst.append('<' + tag + ' class="' + pclass + '">')
htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype) hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
htmlpage += '</' + tag + '>' hlst.append('</' + tag + '>')
else : else :
htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype) hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
else : else :
print ' a "graphic" region' print ' a "graphic" region'
(pos, simgsrc) = self.findinDoc('img.src',start,end) (pos, simgsrc) = self.findinDoc('img.src',start,end)
if simgsrc: if simgsrc:
htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc) hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
htmlpage = "".join(hlst)
if last_para_continued : if last_para_continued :
if htmlpage[-4:] == '</p>': if htmlpage[-4:] == '</p>':
htmlpage = htmlpage[0:-4] htmlpage = htmlpage[0:-4]

Some files were not shown because too many files have changed in this diff Show More