Compare commits

...

8 Commits
v5.2 ... v5.5.1

Author SHA1 Message Date
Apprentice Alf
0c03820db7 tools v5.5.1 2015-03-07 13:55:45 +00:00
Apprentice Alf
9fda194391 tools v5.5
Plugins now include unaltered stand-alone scripts, so no longer need to keep separate copies.
2015-03-07 13:48:25 +00:00
Apprentice Alf
b661a6cdc5 tools v5.4.1 2015-03-07 13:28:34 +00:00
Apprentice Alf
0dcd18d524 tools v5.4 2015-03-07 13:14:37 +00:00
Apprentice Alf
0028027f71 Changed from DeDRM_WinApp to DeDRM_App 2015-03-07 13:05:48 +00:00
TetraChroma
4b3618246b ineptpdf 8.4.51 2015-03-06 18:02:17 +00:00
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
208 changed files with 15246 additions and 29050 deletions

View File

@@ -1,38 +1,70 @@
Ignoble Epub DeDRM - ignobleepub_v01.6_plugin.zip
Ignoble Epub DeDRM - ignobleepub_v02.5_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.
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:
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.
Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page. Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ignobleepub_v02.5_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
Configuration:
Customization
-------------
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
Upon first installing the plugin (or upgrading from a version earlier than 0.2.0), the plugin will be unconfigured. Until you create at least one B&N key—or migrate your existing key(s)/data from an earlier version of the plugin—the plugin will not function. When unconfigured (no saved keys)... an error message will occur whenever ePubs are imported to calibre. To eliminate the error message, open the plugin's customization dialog and create/import/migrate a key (or disable/uninstall the plugin). You can get to the plugin's customization dialog by opening calibre's Preferences dialog, and clicking Plugins (under the Advanced section). Once in the Plugin Preferences, expand the "File type plugins" section and look for the "Ignoble Epub DeDRM" plugin. Highlight that plugin and click the "Customize plugin" button.
If you've purchased books with more than one credit card, separate that other info with a colon: Your Name,1234123412341234:Other Name,2345234523452345
Upgrading from old keys
** 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.
If you are upgrading from an earlier version of this plugin and have provided your name(s) and credit card number(s) as part of the old plugin's customization string, you will be prompted to migrate this data to the plugin's new, more secure, key storage method when you open the customization dialog for the first time. If you choose NOT to migrate that data, you will be prompted to save that data as a text file in a location of your choosing. Either way, this plugin will no longer be storing names and credit card numbers in plain sight (or anywhere for that matter) on your computer or in calibre. If you don't choose to migrate OR save the data, that data will be lost. You have been warned!!
Upon configuring for the first time, you may also be asked if you wish to import your existing *.b64 keyfiles (if you use them) to the plugin's new key storage method. The new plugin no longer looks for keyfiles in calibre's configuration directory, so it's highly recommended that you import any existing keyfiles when prompted ... but you always have the ability to import existing keyfiles anytime you might need/want to.
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.
If you have upgraded from an earlier version of the plugin, the above instructions may be all you need to do to get the new plugin up and running. Continue reading for new-key generation and existing-key management instructions.
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.
Creating New Keys:
On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key.
Troubleshooting:
* Unique Key Name: this is a unique name you choose to help you identify the key after it's created. This name will show in the list of configured keys. Choose something that will help you remember the data (name, cc#) it was created with.
* Your Name: Your name as set in your Barnes & Noble account, My Account page, directly under PERSONAL INFORMATION. It is usually just your first name and last name separated by a space. This name will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that's stored in the preferences.
* Credit Card number: this is the credit card number that was set as default with Barnes & Noble at the time of download. Nothing fancy here; no dashes or spaces ... just the 16 (15?) digits. Again... this number will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that's stored in the preferences.
Click the 'OK" button to create and store the generated key. Or Cancel if you didn't want to create a key.
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. ;)
Deleting Keys:
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.
On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted key in the list. You will be prompted once to be sure that's what you truly mean to do. Once gone, it's permanently gone.
** 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.
Exporting Keys:
On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a computer's hard-drive. Use this button to export the highlighted key to a file (*.b64). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.
Importing Existing Keyfiles:
At the bottom-left of the plugin's customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing *.b64 keyfiles. Used for migrating keyfiles from older versions of the plugin (or keys generated with the original I <3 Cabbages script), or moving keyfiles from computer to computer, or restoring a backup. Some very basic validation is done to try to avoid overwriting already configured keys with incoming, imported keyfiles with the same base file name, but I'm sure that could be broken if someone tried hard. Just take care when importing.
Once done creating/importing/exporting/deleting decryption keys; click "OK" to exit the customization dialogue (the cancel button will actually work the same way here ... at this point all data/changes are committed already, so take your pick).
Troubleshooting
---------------
If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the 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. ;)
On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.)
On Windows, open a terminal/command window. (Start/Run… and then type 'cmd' (without the 's) as the program to run).
On Macintosh, open the Terminal application (in your Utilities folder).
On Linux open a command window. Hopefully all Linux users know how to do this, as I do not.
You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isnt already in calibre, and that calibre isnt running.
Now type in "calibredb add " (without the " but dont miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information.
Now copy the output from the terminal window.
On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it.
On Macintosh and Linux, just use the normal text select and copy commands.
Paste the information into a comment at my blog, http://apprenticealf.wordpress.com/ describing your problem.

View File

@@ -1,25 +1,26 @@
Inept Epub DeDRM - ineptepub_v01.7_plugin.zip
Inept Epub DeDRM - ineptepub_v02.0_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.
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 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.
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.
Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page. Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptepub_v02.0_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
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 find the Adobe Digital Editions installation installation.
Customization
-------------
When first run, the plugin will attempt to find your Adobe Digital Editions installation (on Windows and Mac OS). If successful, it will create 'calibre-adeptkey[number].der' file(s) and save them in Calibre's configuration directory. It will use those files and any other '*.der' files in any decryption attempts. If there is already at least one 'calibre-adept*.der' file in the directory, the plugin won't attempt to find the Adobe Digital Editions installation keys again.
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♥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 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.
If you already have keyfiles generated with i♥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 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.
@@ -29,11 +30,86 @@ All keyfiles with a '.der' extension found in Calibre's configuration directory
** 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 as well get used to it. ;)
If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the 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.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.
On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.)
** 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.
On Windows, open a terminal/command window. (Start/Run… and then type cmd (without the s) as the program to run).
On Macintosh, open the Terminal application (in your Utilities folder).
On Linux open a command window. Hopefully all Linux users know how to do this, as I do not.
You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isnt already in calibre, and that calibre isnt running.
Now type in "calibredb add " (without the " but dont miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information.
Now copy the output from the terminal window.
On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it.
On Macintosh and Linux, just use the normal text select and copy commands.
Paste the information into a comment at my blog, http://apprenticealf.wordpress.com/ describing your problem.
Linux and Adobe Digital Editions ePubs
--------------------------------------
Here are the instructions for using the tools with ePub books and Adobe Digital Editions on Linux under Wine. (Thank you mclien!)
1. download the most recent version of wine from winehq.org (1.3.29 in my case)
For debian users:
to get a recent version of wine I decited to use aptosid (2011-02, xfce)
(because Im used to debian)
install aptosid and upgrade it (see aptosid site for detaild instructions)
2. properly install Wine (see the Wine site for details)
For debian users:
cd to this dir and install the packages as root:
dpkg -i *.deb
you will get some error messages, which can be ignored.
again as root use
apt-get -f install to correct this errors
3. python 2.7 should already be installed on your system but you may need the following additional python package
'apt-get install python-tk
4. all programms need to be installed as normal user. All these programm are installed the same way:
wine
we need:
a) Adobe Digital Edition 1.7.2(from: http://kb2.adobe.com/cps/403/kb403051.html)
(there is a “cant install ADE” site, where the setup.exe hides)
b) ActivePython-2.7.2.5-win32-x86.msi (from: http://www.activestate.com/activepython/downloads)
c) Win32OpenSSL_Light-0_9_8r.exe (from: http://www.slproweb.com/)
d) pycrypto-2.3.win32-py2.7.msi (from: http://www.voidspace.org.uk/python/modules.shtml)
5. now get and unpack the very latest tools_vX.X (from Apprentice Alf) in the users drive_c of wine
(~/.wine/drive_c/)
6. start ADE with:
wine digitaleditions.exe or from the start menue wine-adobe-digital..
7. register this instance of ADE with your adobeID and close it
change to the tools_vX.X dir:
cd ~/.wine/drive_c/tools_vX.X/Other_Tools/
8. create the adeptkey.der with:
wine python ineptkey.py (only need once!)
(key will be here: ~/.wine/drive_c/tools_vX.X/Other_Tools/adeptkey.der)
9. Use ADE running under Wine to dowload all of your purchased ePub ebooks
10. install the ineptepub and ineptpdf plugins from the tools as discribed in the readmes.
11. copy the adeptkey.der into the config dir of calibre (~/.config/calibre in debian). Your ADE books imported to calibre will automatically be freed from DRM.

View File

@@ -1,25 +1,25 @@
Inept PDF Plugin - ineptpdf_v01.5_plugin.zip
Inept PDF Plugin - ineptpdf_v01.9_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.
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 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.
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.
Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page. Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptpdf_v01.9_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
Configuration:
Customization
-------------
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.
When first run, the plugin will attempt to find your Adobe Digital Editions installation (on Windows and Mac OS). If successful, it will create 'calibre-adeptkey[number].der' file(s) and save them in Calibre's configuration directory. It will use those files and any other '*.der' files in any decryption attempts. If there is already at least one 'calibre-adept*.der' file in the directory, the plugin won't attempt to find the Adobe Digital Editions installation keys again.
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 i♥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
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.
@@ -29,11 +29,86 @@ All keyfiles with a '.der' extension found in Calibre's configuration directory
** 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 as well get used to it. ;)
If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the 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.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.
On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.)
** 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.
On Windows, open a terminal/command window. (Start/Run… and then type cmd (without the s) as the program to run).
On Macintosh, open the Terminal application (in your Utilities folder).
On Linux open a command window. Hopefully all Linux users know how to do this, as I do not.
You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isnt already in calibre, and that calibre isnt running.
Now type in "calibredb add " (without the " but dont miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information.
Now copy the output from the terminal window.
On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it.
On Macintosh and Linux, just use the normal text select and copy commands.
Paste the information into a comment at my blog, http://apprenticealf.wordpress.com/ describing your problem.
Linux and Adobe Digital Editions PDFs
--------------------------------------
Here are the instructions for using the tools with ePub books and Adobe Digital Editions on Linux under Wine. (Thank you mclien!)
1. download the most recent version of wine from winehq.org (1.3.29 in my case)
For debian users:
to get a recent version of wine I decited to use aptosid (2011-02, xfce)
(because Im used to debian)
install aptosid and upgrade it (see aptosid site for detaild instructions)
2. properly install Wine (see the Wine site for details)
For debian users:
cd to this dir and install the packages as root:
dpkg -i *.deb
you will get some error messages, which can be ignored.
again as root use
apt-get -f install to correct this errors
3. python 2.7 should already be installed on your system but you may need the following additional python package
'apt-get install python-tk
4. all programms need to be installed as normal user. All these programm are installed the same way:
wine
we need:
a) Adobe Digital Edition 1.7.2(from: http://kb2.adobe.com/cps/403/kb403051.html)
(there is a “cant install ADE” site, where the setup.exe hides)
b) ActivePython-2.7.2.5-win32-x86.msi (from: http://www.activestate.com/activepython/downloads)
c) Win32OpenSSL_Light-0_9_8r.exe (from: http://www.slproweb.com/)
d) pycrypto-2.3.win32-py2.7.msi (from: http://www.voidspace.org.uk/python/modules.shtml)
5. now get and unpack the very latest tools_vX.X (from Apprentice Alf) in the users drive_c of wine
(~/.wine/drive_c/)
6. start ADE with:
wine digitaleditions.exe or from the start menue wine-adobe-digital..
7. register this instance of ADE with your adobeID and close it
change to the tools_vX.X dir:
cd ~/.wine/drive_c/tools_vX.X/Other_Tools/
8. create the adeptkey.der with:
wine python ineptkey.py (only need once!)
(key will be here: ~/.wine/drive_c/tools_vX.X/Other_Tools/adeptkey.der)
9. Use ADE running under Wine to dowload all of your purchased ePub ebooks
10. install the ineptepub and ineptpdf plugins from the tools as discribed in the readmes.
11. copy the adeptkey.der into the config dir of calibre (~/.config/calibre in debian). Your ADE books imported to calibre will automatically be freed from DRM.

View File

@@ -1,37 +1,122 @@
K4MobiDeDRM_v04.4_plugin.zip
Kindle and Mobipocket Plugin - K4MobiDeDRM_v04.10_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 supersedes MobiDeDRM, K4DeDRM, and K4PCDeDRM and K4X plugins. If you install this plugin, those plugins should be removed, as should any earlier versions of this plugin.
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.
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 books from those programs.
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 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.
Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page. Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (K4MobiDeDRM_v04.10_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
Make sure that you delete any old versions of the plugin. They might interfere with the operation of the new one.
Configuration:
Customization
-------------
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).
Highlight the plugin (K4MobiDeDRM under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page.
This configuration step is not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books.
If you have an eInk Kindle enter the 16 character serial number (these all begin a "B" or a "9") in the serial numbers field. The easiest way to make sure that you have the serial number right is to copy it from your Amazon account pages (the "Manage Your Devices" page). If you have more than one eInk Kindle, you can enter multiple serial numbers separated by commas.
If you have Mobipocket books, enter your 8 or 10 digit PID in the Mobipocket PIDs field. If you have more than one PID, separate them with commas.
These configuration steps are not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books.
Troubleshooting
---------------
If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the 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. ;)
On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.)
On Windows, open a terminal/command window. (Start/Run… and then type 'cmd' (without the 's) as the program to run).
On Macintosh, open the Terminal application (in your Utilities folder).
On Linux open a command window. Hopefully all Linux users know how to do this, as I do not.
You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isnt already in calibre, and that calibre isnt running.
Now type in "calibredb add " (without the " but dont miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information.
Now copy the output from the terminal window.
On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it.
On Macintosh and Linux, just use the normal text select and copy commands.
Paste the information into a comment at my blog, http://apprenticealf.wordpress.com/ describing your problem.
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. You will need to install Python and PyCrypto under Wine as detailed below. In addition, some people who have successfully used the plugin in this way have commented as follows:
Here are the instructions for using Kindle for PC on Linux under Wine. (Thank you Eyeless and Pete)
1. upgrade to very recent versions of Wine; This has been tested with Wine 1.3.15 1.3.2X. It may work with earlier versions but no promises. It does not work with wine 1.2.X versions.
If you have not already installed Kindle for PC under wine, follow steps 2 and 3 otherwise jump to step 4
2. Some versions of winecfg have a bug in setting the volume serial number, so create a .windows-serial file at root of drive_c to set a proper windows volume serial number (8 digit hex value for unsigned integer).
cd ~
cd .wine
cd drive_c
echo deadbeef > .windows-serial
Replace "deadbeef" with whatever hex value you want but I would stay away from the default setting of "ffffffff" which does not seem to work. BTW: deadbeef is itself a valid possible hex value if you want to use it
3. Only ***after*** setting the volume serial number properly download and install under wine K4PC version for Windows. Register it and download from your Archive one of your Kindle ebooks. Versions known to work are K4PC 1.7.1 and earlier. Later version may work but no promises.
FIRST user
----------
Hi everyone, I struggled to get this working on Ubuntu 12.04. Here are the secrets for everyone:
1. Make sure your Wine installation is set up to be 32 bit. 64 bit is not going to work! To do this, remove your .wine directory (or use a different wineprefix). Then use WINEARCH=win32 winecfg
2. But wait, you cant install Kindle yet. It wont work. You need to do: winetricks -q vcrun2008 or else youll get an error: unimplemented function msvcp90.dll .
3. Now download and install Kindle for PC and download your content as normal.
4. Now download and install Python 2.7 32 bit for Windows from python.org, 32 bit, install it the usual way, and you can now run the Kindle DRM tools.
SECOND USER
-----------
It took a while to figure out that I needed wine 32 bit, plus Python 27 32 bit, plus the winetricks, to get all this working together but once its done, its great and I can read my Kindle content on my Nook Color running Cyanogenmod!!!
Linux Systems Only:
For all of the following wine installs, use WINEARCH=win32 if you are on x86_64. Also remember that in order to execute a *.msi file, you have to run WINEARCH=win32 wine msiexec /i xxxxx.msi.
1. Install Kindle for PC with wine.
2. Install ActivePython 2.7.x (Windows x86) with wine from here: http://www.activestate.com/activepython/downloads
3. Install the pycrypto (Windows 32 bit for Python 2.7) module with wine from here: http://www.voidspace.org.uk/python/modules.shtml#pycrypto
4. Install the K4MobiDeDRM plugin into your _Linux_ Calibre installation
Now all Kindle books downloaded from Kindle for PC in Wine will be automatically de-DRMd when they are added to your _Linux_ Calibre. As always, you can troubleshoot problems by adding a book from the terminal using calibredb add xxxx.
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.
Or something like that! Hope that helps someone out.
Troubleshooting:
Installing Python on Windows
----------------------------
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.
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. ;)
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.
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.
2. When it has finished downloading, run the installer. Accept the default options.
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.
1. Download PyCrypto 2.1 for 32bit Windows and Python 2.7 from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
2. When it has finished downloading, unzip it. This will produce a file “pycrypto-2.1.0.win32-py2.7.exe”.
3. Double-click “pycrypto-2.1.0.win32-py2.7.exe” to run it. Accept the default options.

View File

@@ -1,28 +1,70 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
from calibre.customize import FileTypePlugin
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
__license__ = 'GPL v3'
__docformat__ = 'restructuredtext en'
import sys
import os
import re
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
#
# Requires Calibre version 0.7.55 or higher.
#
# All credit given to The Dark Reverser for the original mobidedrm script.
# Thanks to all those who've worked on the scripts since 2008 to improve
# the support for formats and sources.
#
# Revision history:
# 0.4.8 - Major code change to use unaltered k4mobidedrm.py 4.8 and later
# 0.4.9 - typo fix
# 0.4.10 - Another Topaz Fix (class added to page and group and region)
# 0.4.11 - Fixed Linux support of K4PC
"""
Decrypt Amazon Kindle and Mobipocket encrypted ebooks.
"""
PLUGIN_NAME = u"Kindle and Mobipocket DeDRM"
PLUGIN_VERSION_TUPLE = (0, 4, 11)
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
import sys, os, re
import time
from zipfile import ZipFile
from calibre.customize import FileTypePlugin
from calibre.constants import iswindows, isosx
from calibre.gui2 import is_ok_to_use_qt
from calibre.utils.config import config_dir
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
class K4DeDRM(FileTypePlugin):
name = 'Kindle and Mobipocket DeDRM' # Name of the plugin
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.'
name = PLUGIN_NAME
description = u"Removes DRM from eInk Kindle, Kindle 4 Mac and Kindle 4 PC ebooks, and from Mobipocket ebooks. Provided by the work of many including The Dark Reverser, DiapDealer, SomeUpdates, i♥cabbages, CMBDTC, Skindle, mdlnx, ApprenticeAlf, and probably others."
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates, mdlnx, Apprentice Alf' # The author of this plugin
version = (0, 4, 4) # The version number of this plugin
author = u"DiapDealer, SomeUpdates, mdlnx, Apprentice Alf and The Dark Reverser"
version = PLUGIN_VERSION_TUPLE
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
priority = 520 # run this plugin before earlier versions
priority = 521 # run this plugin before earlier versions
minimum_calibre_version = (0, 7, 55)
def initialize(self):
@@ -35,36 +77,40 @@ class K4DeDRM(FileTypePlugin):
so the CDLL stuff will work in the alfcrypto.py script.
"""
if iswindows:
names = ['alfcrypto.dll','alfcrypto64.dll']
names = [u"alfcrypto.dll",u"alfcrypto64.dll"]
elif isosx:
names = ['libalfcrypto.dylib']
names = [u"libalfcrypto.dylib"]
else:
names = ['libalfcrypto32.so','libalfcrypto64.so','alfcrypto.py','alfcrypto.dll','alfcrypto64.dll','getk4pcpids.py','mobidedrm.py','kgenpids.py','k4pcutils.py','topazextract.py']
names = [u"libalfcrypto32.so",u"libalfcrypto64.so",u"alfcrypto.py",u"alfcrypto.dll",u"alfcrypto64.dll",u"getk4pcpids.py",u"k4mobidedrm.py",u"mobidedrm.py",u"kgenpids.py",u"k4pcutils.py",u"topazextract.py"]
lib_dict = self.load_resources(names)
self.alfdir = os.path.join(config_dir, 'alfcrypto')
self.alfdir = os.path.join(config_dir,u"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)
open(file_path,'wb').write(data)
def run(self, path_to_ebook):
# make sure any unicode output gets converted safely with 'replace'
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
starttime = time.time()
print u"{0} v{1}: Trying to decrypt {2}.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(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
from calibre_plugins.k4mobidedrm import k4mobidedrm
plug_ver = '.'.join(str(self.version).strip('()').replace(' ', '').split(','))
k4 = True
pids = []
serials = []
kInfoFiles = []
self.config()
# Get supplied list of PIDs to try from plugin customization.
@@ -75,7 +121,7 @@ class K4DeDRM(FileTypePlugin):
pids.append(pid)
else:
if len(pid) > 0:
print "'%s' is not a valid Mobipocket PID." % pid
print u"{0} v{1}: \'{2}\' is not a valid Mobipocket PID.".format(PLUGIN_NAME, PLUGIN_VERSION, pid)
# For linux, get PIDs by calling the right routines under WINE
if sys.platform.startswith('linux'):
@@ -85,16 +131,16 @@ class K4DeDRM(FileTypePlugin):
# 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':
serial = str(serial).replace(" ","")
if len(serial) == 16 and serial[0] in ['B','9']:
serials.append(serial)
else:
if len(serial) > 0:
print "'%s' is not a valid Kindle serial number." % serial
print u"{0} v{1}: \'{2}\' is not a valid eInk Kindle serial number.".format(PLUGIN_NAME, PLUGIN_VERSION, serial)
# Load any kindle info files (*.info) included Calibre's config directory.
try:
print 'K4MobiDeDRM v%s: Calibre configuration directory = %s' % (plug_ver, config_dir)
print u"{0} v{1}: Calibre configuration directory is {2}".format(PLUGIN_NAME, PLUGIN_VERSION, config_dir)
files = os.listdir(config_dir)
filefilter = re.compile("\.info$|\.kinf$", re.IGNORECASE)
files = filter(filefilter.search, files)
@@ -102,64 +148,29 @@ class K4DeDRM(FileTypePlugin):
for filename in files:
fpath = os.path.join(config_dir, filename)
kInfoFiles.append(fpath)
print 'K4MobiDeDRM v%s: Kindle info/kinf file %s found in config folder.' % (plug_ver, filename)
except IOError:
print 'K4MobiDeDRM v%s: Error reading kindle info/kinf files from config directory.' % plug_ver
print u"{0} v{1}: Kindle info/kinf file {2} found in config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, filename)
except IOError, e:
print u"{0} v{1}: Error \'{2}\' reading kindle info/kinf files from config directory.".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
pass
mobi = True
magic3 = file(path_to_ebook,'rb').read(3)
if magic3 == 'TPZ':
mobi = False
bookname = os.path.splitext(os.path.basename(path_to_ebook))[0]
if mobi:
mb = mobidedrm.MobiBook(path_to_ebook)
else:
mb = topazextract.TopazBook(path_to_ebook)
title = mb.getBookTitle()
md1, md2 = mb.getPIDMetaInfo()
pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles)
try:
mb.processBook(pidlst)
except mobidedrm.DrmException, e:
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kInfoFiles,serials,pids,starttime)
except Exception, e:
#if you reached here then no luck raise and exception
if is_ok_to_use_qt():
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, u"{0} v{1}".format(PLUGIN_NAME, PLUGIN_VERSION), u"Error after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime))
d.show()
d.raise_()
d.exec_()
raise Exception("K4MobiDeDRM plugin v%s Error: %s" % (plug_ver, str(e)))
except topazextract.TpzDRMError, e:
#if you reached here then no luck raise and exception
if is_ok_to_use_qt():
from PyQt4.Qt import QMessageBox
d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM v%s Plugin" % plug_ver, "Error: " + str(e) + "... %s\n" % path_to_ebook)
d.show()
d.raise_()
d.exec_()
raise Exception("K4MobiDeDRM plugin v%s Error: %s" % (plug_ver, str(e)))
raise Exception(u"{0} v{1}: Error after {3:.1f} seconds: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0],time.time()-starttime))
print "Success!"
if mobi:
if mb.getPrintReplica():
of = self.temporary_file(bookname+'.azw4')
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:
of = self.temporary_file(bookname+'.mobi')
mb.getMobiFile(of.name)
else:
of = self.temporary_file(bookname+'.htmlz')
mb.getHTMLZip(of.name)
mb.cleanup()
print u"{0} v{1}: Successfully decrypted book after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-starttime)
of = self.temporary_file(k4mobidedrm.cleanup_name(k4mobidedrm.unescape(book.getBookTitle()))+book.getBookExtension())
book.getFile(of.name)
book.cleanup()
return of.name
def WINEgetPIDs(self, infile):
@@ -170,42 +181,54 @@ class K4DeDRM(FileTypePlugin):
import subasyncio
from subasyncio import Process
print " Getting PIDs from WINE"
print u" Getting PIDs from Wine"
outfile = os.path.join(self.alfdir + 'winepids.txt')
cmdline = 'wine python.exe ' \
+ '"'+self.alfdir + '/getk4pcpids.py"' \
+ ' "' + infile + '"' \
+ ' "' + outfile + '"'
outfile = os.path.join(self.alfdir + u"winepids.txt")
# Remove any previous winepids.txt file.
if os.path.exists(outfile):
os.remove(outfile)
cmdline = u"wine python.exe \"{0}/getk4pcpids.py\" \"{1}\" \"{2}\"".format(self.alfdir,infile,outfile)
env = os.environ
print "My wine_prefix from tweaks is ", self.wine_prefix
print u"wine_prefix from tweaks is \'{0}\'".format(self.wine_prefix)
if ("WINEPREFIX" in env):
print "Using WINEPREFIX from the environment: ", env["WINEPREFIX"]
print u"Using WINEPREFIX from the environment instead: \'{0}\'".format(env["WINEPREFIX"])
elif (self.wine_prefix is not None):
env['WINEPREFIX'] = self.wine_prefix
print "Using WINEPREFIX from tweaks: ", self.wine_prefix
env["WINEPREFIX"] = self.wine_prefix
print u"Using WINEPREFIX from tweaks \'{0}\'".format(self.wine_prefix)
else:
print "No wine prefix used"
print u"No wine prefix used."
print cmdline
print u"Trying command: {0}".format(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 u"WINE subprocess error: {0}".format(e.args[0])
return []
print u"WINE subprocess returned {0}".format(result)
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")
print "Conversion returned ", result
WINEpids = []
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
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)
print u"Found PID '{0}'".format(customvalue)
else:
print u"'{0}' is not a valid PID.".format(customvalue)
except Exception, e:
print u"Error parsing winepids.txt: {0}".format(e.args[0])
return []
if len(WINEpids) == 0:
print u"No PIDs generated by Wine Python subprocess."
return WINEpids
def is_customizable(self):

View File

@@ -1,11 +1,18 @@
#! /usr/bin/env python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# crypto library mainly by some_updates
# pbkdf2.py pbkdf2 code taken from pbkdf2.py
# pbkdf2.py Copyright © 2004 Matt Johnston <matt @ ucc asn au>
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
# pbkdf2.py This code may be freely used and modified for any purpose.
import sys, os
import hmac
from struct import pack
import hashlib
# interface to needed routines libalfcrypto
def _load_libalfcrypto():
import ctypes
@@ -27,7 +34,7 @@ def _load_libalfcrypto():
else:
name_of_lib = 'libalfcrypto64.so'
libalfcrypto = sys.path[0] + os.sep + name_of_lib
libalfcrypto = os.path.join(sys.path[0],name_of_lib)
if not os.path.isfile(libalfcrypto):
raise Exception('libalfcrypto not found')
@@ -147,7 +154,7 @@ def _load_libalfcrypto():
topazCryptoDecrypt(ctx, data, out, len(data))
return out.raw
print "Using Library AlfCrypto DLL/DYLIB/SO"
print u"Using Library AlfCrypto DLL/DYLIB/SO"
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
@@ -164,8 +171,7 @@ def _load_python_alfcrypto():
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
print "Bad key length!"
return None
raise Exception('Pukall_Cipher: Bad key length.')
wkey = []
for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
@@ -234,6 +240,7 @@ def _load_python_alfcrypto():
cleartext = self.aes.decrypt(iv + data)
return cleartext
print u"Using Library AlfCrypto Python"
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)

View File

@@ -1,899 +0,0 @@
#! /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

@@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from PyQt4.Qt import QWidget, QVBoxLayout, QLabel, QLineEdit
from calibre.utils.config import JSONConfig
@@ -20,7 +23,7 @@ class ConfigWidget(QWidget):
self.l = QVBoxLayout()
self.setLayout(self.l)
self.serialLabel = QLabel('Kindle Serial numbers (separate with commas, no spaces)')
self.serialLabel = QLabel('eInk Kindle Serial numbers (First character B, 16 characters, use commas if more than one)')
self.l.addWidget(self.serialLabel)
self.serials = QLineEdit(self)
@@ -28,7 +31,7 @@ class ConfigWidget(QWidget):
self.l.addWidget(self.serials)
self.serialLabel.setBuddy(self.serials)
self.pidLabel = QLabel('Mobipocket PIDs (separate with commas, no spaces)')
self.pidLabel = QLabel('Mobipocket PIDs (8 or 10 characters, use commas if more than one)')
self.l.addWidget(self.pidLabel)
self.pids = QLineEdit(self)
@@ -50,8 +53,8 @@ class ConfigWidget(QWidget):
self.wpLabel.setBuddy(self.wineprefix)
def save_settings(self):
prefs['pids'] = str(self.pids.text())
prefs['serials'] = str(self.serials.text())
prefs['pids'] = str(self.pids.text()).replace(" ","")
prefs['serials'] = str(self.serials.text()).replace(" ","")
winepref=str(self.wineprefix.text())
if winepref.strip() != '':
prefs['WINEPREFIX'] = winepref

View File

@@ -230,6 +230,7 @@ class PageParser(object):
'empty' : (1, 'snippets', 1, 0),
'page' : (1, 'snippets', 1, 0),
'page.class' : (1, 'scalar_text', 0, 0),
'page.pageid' : (1, 'scalar_text', 0, 0),
'page.pagelabel' : (1, 'scalar_text', 0, 0),
'page.type' : (1, 'scalar_text', 0, 0),
@@ -238,11 +239,13 @@ class PageParser(object):
'page.startID' : (1, 'scalar_number', 0, 0),
'group' : (1, 'snippets', 1, 0),
'group.class' : (1, 'scalar_text', 0, 0),
'group.type' : (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.class' : (1, 'scalar_text', 0, 0),
'region.type' : (1, 'scalar_text', 0, 0),
'region.x' : (1, 'scalar_number', 0, 0),
'region.y' : (1, 'scalar_number', 0, 0),

View File

@@ -5,8 +5,9 @@
#
# Changelog
# 1.00 - Initial version
# 1.01 - getPidList interface change
__version__ = '1.00'
__version__ = '1.01'
import sys
@@ -40,18 +41,18 @@ def getK4PCpids(path_to_ebook):
mobi = False
if mobi:
mb = mobidedrm.MobiBook(path_to_ebook,False)
mb = mobidedrm.MobiBook(path_to_ebook)
else:
mb = topazextract.TopazBook(path_to_ebook)
md1, md2 = mb.getPIDMetaInfo()
return kgenpids.getPidList(md1, md2, True, [], [], [])
return kgenpids.getPidList(md1, md2)
def main(argv=sys.argv):
print ('getk4pcpids.py v%(__version__)s. '
'Copyright 2012 Apprentic Alf' % globals())
'Copyright 2012 Apprentice Alf' % globals())
if len(argv)<2 or len(argv)>3:
print "Gets the possible book-specific PIDs from K4PC for a particular book"

View File

@@ -0,0 +1,302 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
# ignobleepub.pyw, version 3.6
# Copyright © 2009-2012 by DiapDealer et al.
# engine to remove drm from Kindle for Mac and Kindle for PC books
# for personal use for archiving and converting your ebooks
# PLEASE DO NOT PIRATE EBOOKS!
# We want all authors and publishers, and eBook stores to live
# long and prosperous lives but at the same time we just want to
# be able to read OUR books on whatever device we want and to keep
# readable for a long, long time
# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
# and many many others
# Special thanks to The Dark Reverser for MobiDeDrm and CMBDTC for cmbdtc_dump
# from which this script borrows most unashamedly.
# Changelog
# 1.0 - Name change to k4mobidedrm. Adds Mac support, Adds plugin code
# 1.1 - Adds support for additional kindle.info files
# 1.2 - Better error handling for older Mobipocket
# 1.3 - Don't try to decrypt Topaz books
# 1.7 - Add support for Topaz books and Kindle serial numbers. Split code.
# 1.9 - Tidy up after Topaz, minor exception changes
# 2.1 - Topaz fix and filename sanitizing
# 2.2 - Topaz Fix and minor Mac code fix
# 2.3 - More Topaz fixes
# 2.4 - K4PC/Mac key generation fix
# 2.6 - Better handling of non-K4PC/Mac ebooks
# 2.7 - Better trailing bytes handling in mobidedrm
# 2.8 - Moved parsing of kindle.info files to mac & pc util files.
# 3.1 - Updated for new calibre interface. Now __init__ in plugin.
# 3.5 - Now support Kindle for PC/Mac 1.6
# 3.6 - Even better trailing bytes handling in mobidedrm
# 3.7 - Add support for Amazon Print Replica ebooks.
# 3.8 - Improved Topaz support
# 4.1 - Improved Topaz support and faster decryption with alfcrypto
# 4.2 - Added support for Amazon's KF8 format ebooks
# 4.4 - Linux calls to Wine added, and improved configuration dialog
# 4.5 - Linux works again without Wine. Some Mac key file search changes
# 4.6 - First attempt to handle unicode properly
# 4.7 - Added timing reports, and changed search for Mac key files
# 4.8 - Much better unicode handling, matching the updated inept and ignoble scripts
# - Moved back into plugin, __init__ in plugin now only contains plugin code.
__version__ = '4.8'
import sys, os, re
import csv
import getopt
import re
import traceback
import time
import htmlentitydefs
class DrmException(Exception):
pass
if 'calibre' in sys.modules:
inCalibre = True
else:
inCalibre = False
if inCalibre:
from calibre_plugins.k4mobidedrm import mobidedrm
from calibre_plugins.k4mobidedrm import topazextract
from calibre_plugins.k4mobidedrm import kgenpids
else:
import mobidedrm
import topazextract
import kgenpids
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
# cleanup unicode filenames
# borrowed from calibre from calibre/src/calibre/__init__.py
# added in removal of control (<32) chars
# and removal of . at start and end
# and with some (heavily edited) code from Paul Durrant's kindlenamer.py
def cleanup_name(name):
# substitute filename unfriendly characters
name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" ").replace(u": ",u" ").replace(u":",u"").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'")
# delete control characters
name = u"".join(char for char in name if ord(char)>=32)
# white space to single space, delete leading and trailing while space
name = re.sub(ur"\s", u" ", name).strip()
# remove leading dots
while len(name)>0 and name[0] == u".":
name = name[1:]
# remove trailing dots (Windows doesn't like them)
if name.endswith(u'.'):
name = name[:-1]
return name
# must be passed unicode
def unescape(text):
def fixup(m):
text = m.group(0)
if text[:2] == u"&#":
# character reference
try:
if text[:3] == u"&#x":
return unichr(int(text[3:-1], 16))
else:
return unichr(int(text[2:-1]))
except ValueError:
pass
else:
# named entity
try:
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
except KeyError:
pass
return text # leave as is
return re.sub(u"&#?\w+;", fixup, text)
def GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime = time.time()):
# handle the obvious cases at the beginning
if not os.path.isfile(infile):
raise DRMException (u"Input file does not exist.")
mobi = True
magic3 = file(infile,'rb').read(3)
if magic3 == 'TPZ':
mobi = False
if mobi:
mb = mobidedrm.MobiBook(infile)
else:
mb = topazextract.TopazBook(infile)
bookname = unescape(mb.getBookTitle())
print u"Decrypting {1} ebook: {0}".format(bookname, mb.getBookType())
# extend PID list with book-specific PIDs
md1, md2 = mb.getPIDMetaInfo()
pids.extend(kgenpids.getPidList(md1, md2, serials, kInfoFiles))
print u"Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(pids))
try:
mb.processBook(pids)
except:
mb.cleanup
raise
print u"Decryption succeeded after {0:.1f} seconds".format(time.time()-starttime)
return mb
# infile, outdir and kInfoFiles should be unicode strings
def decryptBook(infile, outdir, kInfoFiles, serials, pids):
starttime = time.time()
print "Starting decryptBook routine."
try:
book = GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime)
except Exception, e:
print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
return 1
# if we're saving to the same folder as the original, use file name_
# if to a different folder, use book name
if os.path.normcase(os.path.normpath(outdir)) == os.path.normcase(os.path.normpath(os.path.dirname(infile))):
outfilename = os.path.splitext(os.path.basename(infile))[0]
else:
outfilename = cleanup_name(book.getBookTitle())
# avoid excessively long file names
if len(outfilename)>150:
outfilename = outfilename[:150]
outfilename = outfilename+u"_nodrm"
outfile = os.path.join(outdir, outfilename + book.getBookExtension())
book.getFile(outfile)
print u"Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename)
if book.getBookType()==u"Topaz":
zipname = os.path.join(outdir, outfilename + u"_SVG.zip")
book.getSVGZip(zipname)
print u"Saved SVG ZIP Archive for {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename)
# remove internal temporary directory of Topaz pieces
book.cleanup()
def usage(progname):
print u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks"
print u"Usage:"
print u" {0} [-k <kindle.info>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname)
#
# Main
#
def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
try:
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
except getopt.GetoptError, err:
print u"Error in options or arguments: {0}".format(err.args[0])
usage(progname)
sys.exit(2)
if len(args)<2:
usage(progname)
sys.exit(2)
infile = args[0]
outdir = args[1]
kInfoFiles = []
serials = []
pids = []
for o, a in opts:
if o == "-k":
if a == None :
raise DrmException("Invalid parameter for -k")
kInfoFiles.append(a)
if o == "-p":
if a == None :
raise DrmException("Invalid parameter for -p")
pids = a.split(',')
if o == "-s":
if a == None :
raise DrmException("Invalid parameter for -s")
serials = a.split(',')
# try with built in Kindle Info files if not on Linux
k4 = not sys.platform.startswith('linux')
return decryptBook(infile, outdir, kInfoFiles, serials, pids)
if __name__ == '__main__':
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())

View File

@@ -1,223 +0,0 @@
#!/usr/bin/env python
from __future__ import with_statement
# engine to remove drm from Kindle for Mac and Kindle for PC books
# for personal use for archiving and converting your ebooks
# PLEASE DO NOT PIRATE EBOOKS!
# We want all authors and publishers, and eBook stores to live
# long and prosperous lives but at the same time we just want to
# be able to read OUR books on whatever device we want and to keep
# readable for a long, long time
# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
# and many many others
__version__ = '4.3'
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
import os, csv, getopt
import string
import re
import traceback
buildXML = False
class DrmException(Exception):
pass
if 'calibre' in sys.modules:
inCalibre = True
else:
inCalibre = False
if inCalibre:
from calibre_plugins.k4mobidedrm import mobidedrm
from calibre_plugins.k4mobidedrm import topazextract
from calibre_plugins.k4mobidedrm import kgenpids
else:
import mobidedrm
import topazextract
import kgenpids
# cleanup bytestring filenames
# borrowed from calibre from calibre/src/calibre/__init__.py
# added in removal of non-printing chars
# and removal of . at start
# convert underscores to spaces (we're OK with spaces in file names)
def cleanup_name(name):
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
substitute='_'
one = ''.join(char for char in name if char in string.printable)
one = _filename_sanitize.sub(substitute, one)
one = re.sub(r'\s', ' ', one).strip()
one = re.sub(r'^\.+$', '_', one)
one = one.replace('..', substitute)
# Windows doesn't like path components that end with a period
if one.endswith('.'):
one = one[:-1]+substitute
# Mac and Unix don't like file names that begin with a full stop
if len(one) > 0 and one[0] == '.':
one = substitute+one[1:]
one = one.replace('_',' ')
return one
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
global buildXML
# handle the obvious cases at the beginning
if not os.path.isfile(infile):
print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: Input file does not exist"
return 1
mobi = True
magic3 = file(infile,'rb').read(3)
if magic3 == 'TPZ':
mobi = False
bookname = os.path.splitext(os.path.basename(infile))[0]
if mobi:
mb = mobidedrm.MobiBook(infile)
else:
mb = topazextract.TopazBook(infile)
title = mb.getBookTitle()
print "Processing Book: ", title
filenametitle = cleanup_name(title)
outfilename = cleanup_name(bookname)
# generate 'sensible' filename, that will sort with the original name,
# but is close to the name from the file.
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
if len(outfilename)>150:
outfilename = outfilename[:150]
# build pid list
md1, md2 = mb.getPIDMetaInfo()
pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles)
try:
mb.processBook(pidlst)
except mobidedrm.DrmException, e:
print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
return 1
except topazextract.TpzDRMError, e:
print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
return 1
except Exception, e:
print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
return 1
if mobi:
if mb.getPrintReplica():
outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw4')
elif mb.getMobiVersion() >= 8:
outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw3')
else:
outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi')
mb.getMobiFile(outfile)
return 0
# topaz:
print " Creating NoDRM HTMLZ Archive"
zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz')
mb.getHTMLZip(zipname)
print " Creating SVG ZIP Archive"
zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip')
mb.getSVGZip(zipname)
if buildXML:
print " Creating XML ZIP Archive"
zipname = os.path.join(outdir, outfilename + '_XML' + '.zip')
mb.getXMLZip(zipname)
# remove internal temporary directory of Topaz pieces
mb.cleanup()
return 0
def usage(progname):
print "Removes DRM protection from K4PC/M, Kindle, Mobi and Topaz ebooks"
print "Usage:"
print " %s [-k <kindle.info>] [-p <pidnums>] [-s <kindleSerialNumbers>] <infile> <outdir> " % progname
#
# Main
#
def main(argv=sys.argv):
progname = os.path.basename(argv[0])
k4 = False
kInfoFiles = []
serials = []
pids = []
print ('K4MobiDeDrm v%(__version__)s '
'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
try:
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
except getopt.GetoptError, err:
print str(err)
usage(progname)
sys.exit(2)
if len(args)<2:
usage(progname)
sys.exit(2)
for o, a in opts:
if o == "-k":
if a == None :
raise DrmException("Invalid parameter for -k")
kInfoFiles.append(a)
if o == "-p":
if a == None :
raise DrmException("Invalid parameter for -p")
pids = a.split(',')
if o == "-s":
if a == None :
raise DrmException("Invalid parameter for -s")
serials = a.split(',')
# try with built in Kindle Info files
k4 = True
if sys.platform.startswith('linux'):
k4 = False
kInfoFiles = None
infile = args[0]
outdir = args[1]
return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids)
if __name__ == '__main__':
sys.stdout=Unbuffered(sys.stdout)
sys.exit(main())

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
import sys
@@ -17,26 +18,24 @@ global charMap4
if 'calibre' in sys.modules:
inCalibre = True
else:
inCalibre = False
if inCalibre:
if sys.platform.startswith('win'):
from calibre.constants import iswindows, isosx
if iswindows:
from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
if sys.platform.startswith('darwin'):
if isosx:
from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
else:
if sys.platform.startswith('win'):
inCalibre = False
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
if iswindows:
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
if sys.platform.startswith('darwin'):
if isosx:
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
charMap3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
charMap4 = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
# crypto digestroutines
import hashlib
@@ -54,7 +53,7 @@ def SHA1(message):
# Encode the bytes in data with the characters in map
def encode(data, map):
result = ""
result = ''
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
@@ -69,14 +68,14 @@ def encodeHash(data,map):
# Decode the string in data with the characters in map. Returns the decoded bytes
def decode(data,map):
result = ""
result = ''
for i in range (0,len(data)-1,2):
high = map.find(data[i])
low = map.find(data[i+1])
if (high == -1) or (low == -1) :
break
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
result += pack("B",value)
result += pack('B',value)
return result
#
@@ -98,7 +97,7 @@ def getSixBitsFromBitField(bitField,offset):
# 8 bits to six bits encoding from hash to generate PID string
def encodePID(hash):
global charMap3
PID = ""
PID = ''
for position in range (0,8):
PID += charMap3[getSixBitsFromBitField(hash,position)]
return PID
@@ -129,7 +128,7 @@ def generatePidSeed(table,dsn) :
def generateDevicePID(table,dsn,nbRoll):
global charMap4
seed = generatePidSeed(table,dsn)
pidAscii = ""
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):
@@ -176,28 +175,31 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum):
def getKindlePids(rec209, token, serialnum):
pids=[]
# Compute book PID
pidHash = SHA1(serialnum+rec209+token)
bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID)
pidlst.append(bookPID)
pids.append(bookPID)
# compute fixed pid for old pre 2.5 firmware update pid as well
bookPID = pidFromSerial(serialnum, 7) + "*"
bookPID = checksumPid(bookPID)
pidlst.append(bookPID)
kindlePID = pidFromSerial(serialnum, 7) + "*"
kindlePID = checksumPid(kindlePID)
pids.append(kindlePID)
return pidlst
return pids
# parse the Kindleinfo file to calculate the book pid.
keynames = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
keynames = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber']
def getK4Pids(pidlst, rec209, token, kInfoFile):
def getK4Pids(rec209, token, kInfoFile):
global charMap1
kindleDatabase = None
pids = []
try:
kindleDatabase = getDBfromFile(kInfoFile)
except Exception, message:
@@ -206,17 +208,17 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
pass
if kindleDatabase == None :
return pidlst
return pids
try:
# Get the Mazama Random number
MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"]
MazamaRandomNumber = kindleDatabase['MazamaRandomNumber']
# Get the kindle account token
kindleAccountToken = kindleDatabase["kindle.account.tokens"]
kindleAccountToken = kindleDatabase['kindle.account.tokens']
except KeyError:
print "Keys not found in " + kInfoFile
return pidlst
print u"Keys not found in {0}".format(os.path.basename(kInfoFile))
return pids
# Get the ID string used
encodedIDString = encodeHash(GetIDString(),charMap1)
@@ -231,7 +233,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
table = generatePidEncryptionTable()
devicePID = generateDevicePID(table,DSN,4)
devicePID = checksumPid(devicePID)
pidlst.append(devicePID)
pids.append(devicePID)
# Compute book PIDs
@@ -239,38 +241,38 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID)
pidlst.append(bookPID)
pids.append(bookPID)
# variant 1
pidHash = SHA1(kindleAccountToken+rec209+token)
bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID)
pidlst.append(bookPID)
pids.append(bookPID)
# variant 2
pidHash = SHA1(DSN+rec209+token)
bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID)
pidlst.append(bookPID)
pids.append(bookPID)
return pidlst
return pids
def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
def getPidList(md1, md2, serials=[], kInfoFiles=[]):
pidlst = []
if kInfoFiles is None:
kInfoFiles = []
if k4:
kInfoFiles = getKindleInfoFiles(kInfoFiles)
if serials is None:
serials = []
if iswindows or isosx:
kInfoFiles.extend(getKindleInfoFiles())
for infoFile in kInfoFiles:
try:
pidlst = getK4Pids(pidlst, md1, md2, infoFile)
except Exception, message:
print("Error getting PIDs from " + infoFile + ": " + message)
pidlst.extend(getK4Pids(md1, md2, infoFile))
except Exception, e:
print u"Error getting PIDs from {0}: {1}".format(os.path.basename(infoFile),e.args[0])
for serialnum in serials:
try:
pidlst = getKindlePid(pidlst, md1, md2, serialnum)
pidlst.extend(getKindlePids(md1, md2, serialnum))
except Exception, message:
print("Error getting PIDs from " + serialnum + ": " + message)
for pid in pids:
pidlst.append(pid)
print u"Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0])
return pidlst

View File

@@ -0,0 +1,142 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Mobipocket PID calculator v0.4 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
# 0.3 updated for unicode
import sys
import binascii
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
if sys.hexversion >= 0x3000000:
print 'This script is incompatible with Python 3.x. Please install Python 2.7.x.'
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 cli_main(argv=unicode_argv()):
print u"Mobipocket PID calculator for Amazon Kindle. Copyright © 2007, 2009 Igor Skochinsky"
if len(sys.argv)==2:
serial = sys.argv[1]
else:
print u"Usage: kindlepid.py <Kindle Serial Number>/<iPhone/iPod Touch UDID>"
return 1
if len(serial)==16:
if serial.startswith("B"):
print u"Kindle serial number detected"
else:
print u"Warning: unrecognized serial number. Please recheck input."
return 1
pid = pidFromSerial(serial.encode("utf-8"),7)+'*'
print u"Mobipocket PID for Kindle serial#{0} is {1} ".format(serial,checksumPid(pid))
return 0
elif len(serial)==40:
print u"iPhone serial number (UDID) detected"
pid = pidFromSerial(serial.encode("utf-8"),8)
print u"Mobipocket PID for iPhone serial#{0} is {1} ".format(serial,checksumPid(pid))
return 0
print u"Warning: unrecognized serial number. Please recheck input."
return 1
if __name__ == "__main__":
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())

View File

@@ -1,68 +0,0 @@
# 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

@@ -1,43 +1,90 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
class Unbuffered:
# topazextract.py, version ?
# Mostly written by some_updates based on code from many others
__version__ = '4.8'
import sys
import os, csv, getopt
import zlib, zipfile, tempfile, shutil
import traceback
from struct import pack
from struct import unpack
from alfcrypto import Topaz_Cipher
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
import sys
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = 'utf-8'
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
if 'calibre' in sys.modules:
inCalibre = True
else:
inCalibre = False
buildXML = False
import os, csv, getopt
import zlib, zipfile, tempfile, shutil
from struct import pack
from struct import unpack
from alfcrypto import Topaz_Cipher
class TpzDRMError(Exception):
pass
# local support routines
if inCalibre:
from calibre_plugins.k4mobidedrm import kgenpids
else:
inCalibre = False
import kgenpids
class DrmException(Exception):
pass
# recursive zip creation support routine
def zipUpDir(myzip, tdir, localname):
currentdir = tdir
if localname != "":
if localname != u"":
currentdir = os.path.join(currentdir,localname)
list = os.listdir(currentdir)
for file in list:
@@ -73,7 +120,7 @@ def bookReadEncodedNumber(fo):
# Get a length prefixed string from file
def bookReadString(fo):
stringLength = bookReadEncodedNumber(fo)
return unpack(str(stringLength)+"s",fo.read(stringLength))[0]
return unpack(str(stringLength)+'s',fo.read(stringLength))[0]
#
# crypto routines
@@ -112,13 +159,13 @@ def decryptRecord(data,PID):
# Try to decrypt a dkey record (contains the bookPID)
def decryptDkeyRecord(data,PID):
record = decryptRecord(data,PID)
fields = unpack("3sB8sB8s3s",record)
if fields[0] != "PID" or fields[5] != "pid" :
raise TpzDRMError("Didn't find PID magic numbers in record")
fields = unpack('3sB8sB8s3s',record)
if fields[0] != 'PID' or fields[5] != 'pid' :
raise DrmException(u"Didn't find PID magic numbers in record")
elif fields[1] != 8 or fields[3] != 8 :
raise TpzDRMError("Record didn't contain correct length fields")
raise DrmException(u"Record didn't contain correct length fields")
elif fields[2] != PID :
raise TpzDRMError("Record didn't contain PID")
raise DrmException(u"Record didn't contain PID")
return fields[4]
# Decrypt all dkey records (contain the book PID)
@@ -131,11 +178,11 @@ def decryptDkeyRecords(data,PID):
try:
key = decryptDkeyRecord(data[1:length+1],PID)
records.append(key)
except TpzDRMError:
except DrmException:
pass
data = data[1+length:]
if len(records) == 0:
raise TpzDRMError("BookKey Not Found")
raise DrmException(u"BookKey Not Found")
return records
@@ -148,9 +195,9 @@ class TopazBook:
self.bookHeaderRecords = {}
self.bookMetadata = {}
self.bookKey = None
magic = unpack("4s",self.fo.read(4))[0]
magic = unpack('4s',self.fo.read(4))[0]
if magic != 'TPZ0':
raise TpzDRMError("Parse Error : Invalid Header, not a Topaz file")
raise DrmException(u"Parse Error : Invalid Header, not a Topaz file")
self.parseTopazHeaders()
self.parseMetadata()
@@ -167,7 +214,7 @@ class TopazBook:
# Read and parse one header record at the current book file position and return the associated data
# [[offset,decompressedLength,compressedLength],...]
if ord(self.fo.read(1)) != 0x63:
raise TpzDRMError("Parse Error : Invalid Header")
raise DrmException(u"Parse Error : Invalid Header")
tag = bookReadString(self.fo)
record = bookReadHeaderRecordData()
return [tag,record]
@@ -177,15 +224,15 @@ class TopazBook:
# print result[0], result[1]
self.bookHeaderRecords[result[0]] = result[1]
if ord(self.fo.read(1)) != 0x64 :
raise TpzDRMError("Parse Error : Invalid Header")
raise DrmException(u"Parse Error : Invalid Header")
self.bookPayloadOffset = self.fo.tell()
def parseMetadata(self):
# Parse the metadata record from the book payload and return a list of [key,values]
self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords["metadata"][0][0])
self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords['metadata'][0][0])
tag = bookReadString(self.fo)
if tag != "metadata" :
raise TpzDRMError("Parse Error : Record Names Don't Match")
if tag != 'metadata' :
raise DrmException(u"Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1))
# print nbRecords
@@ -210,7 +257,7 @@ class TopazBook:
title = ''
if 'Title' in self.bookMetadata:
title = self.bookMetadata['Title']
return title
return title.decode('utf-8')
def setBookKey(self, key):
self.bookKey = key
@@ -223,13 +270,13 @@ class TopazBook:
try:
recordOffset = self.bookHeaderRecords[name][index][0]
except:
raise TpzDRMError("Parse Error : Invalid Record, record not found")
raise DrmException("Parse Error : Invalid Record, record not found")
self.fo.seek(self.bookPayloadOffset + recordOffset)
tag = bookReadString(self.fo)
if tag != name :
raise TpzDRMError("Parse Error : Invalid Record, record name doesn't match")
raise DrmException("Parse Error : Invalid Record, record name doesn't match")
recordIndex = bookReadEncodedNumber(self.fo)
if recordIndex < 0 :
@@ -237,7 +284,7 @@ class TopazBook:
recordIndex = -recordIndex -1
if recordIndex != index :
raise TpzDRMError("Parse Error : Invalid Record, index doesn't match")
raise DrmException("Parse Error : Invalid Record, index doesn't match")
if (self.bookHeaderRecords[name][index][2] > 0):
compressed = True
@@ -250,7 +297,7 @@ class TopazBook:
ctx = topazCryptoInit(self.bookKey)
record = topazCryptoDecrypt(record,ctx)
else :
raise TpzDRMError("Error: Attempt to decrypt without bookKey")
raise DrmException("Error: Attempt to decrypt without bookKey")
if compressed:
record = zlib.decompress(record)
@@ -262,12 +309,12 @@ class TopazBook:
fixedimage=True
try:
keydata = self.getBookPayloadRecord('dkey', 0)
except TpzDRMError, e:
print "no dkey record found, book may not be encrypted"
print "attempting to extrct files without a book key"
except DrmException, e:
print u"no dkey record found, book may not be encrypted"
print u"attempting to extrct files without a book key"
self.createBookDirectory()
self.extractFiles()
print "Successfully Extracted Topaz contents"
print u"Successfully Extracted Topaz contents"
if inCalibre:
from calibre_plugins.k4mobidedrm import genbook
else:
@@ -275,7 +322,7 @@ class TopazBook:
rv = genbook.generateBook(self.outdir, raw, fixedimage)
if rv == 0:
print "\nBook Successfully generated"
print u"Book Successfully generated."
return rv
# try each pid to decode the file
@@ -283,25 +330,25 @@ class TopazBook:
for pid in pidlst:
# use 8 digit pids here
pid = pid[0:8]
print "\nTrying: ", pid
print u"Trying: {0}".format(pid)
bookKeys = []
data = keydata
try:
bookKeys+=decryptDkeyRecords(data,pid)
except TpzDRMError, e:
except DrmException, e:
pass
else:
bookKey = bookKeys[0]
print "Book Key Found!"
print u"Book Key Found! ({0})".format(bookKey.encode('hex'))
break
if not bookKey:
raise TpzDRMError('Decryption Unsucessful; No valid pid found')
raise DrmException(u"No key found in {0:d} keys tried. Read the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(len(pidlst)))
self.setBookKey(bookKey)
self.createBookDirectory()
self.extractFiles()
print "Successfully Extracted Topaz contents"
print u"Successfully Extracted Topaz contents"
if inCalibre:
from calibre_plugins.k4mobidedrm import genbook
else:
@@ -309,7 +356,7 @@ class TopazBook:
rv = genbook.generateBook(self.outdir, raw, fixedimage)
if rv == 0:
print "\nBook Successfully generated"
print u"Book Successfully generated"
return rv
def createBookDirectory(self):
@@ -317,16 +364,16 @@ class TopazBook:
# create output directory structure
if not os.path.exists(outdir):
os.makedirs(outdir)
destdir = os.path.join(outdir,'img')
destdir = os.path.join(outdir,u"img")
if not os.path.exists(destdir):
os.makedirs(destdir)
destdir = os.path.join(outdir,'color_img')
destdir = os.path.join(outdir,u"color_img")
if not os.path.exists(destdir):
os.makedirs(destdir)
destdir = os.path.join(outdir,'page')
destdir = os.path.join(outdir,u"page")
if not os.path.exists(destdir):
os.makedirs(destdir)
destdir = os.path.join(outdir,'glyphs')
destdir = os.path.join(outdir,u"glyphs")
if not os.path.exists(destdir):
os.makedirs(destdir)
@@ -334,149 +381,148 @@ class TopazBook:
outdir = self.outdir
for headerRecord in self.bookHeaderRecords:
name = headerRecord
if name != "dkey" :
ext = '.dat'
if name == 'img' : ext = '.jpg'
if name == 'color' : ext = '.jpg'
print "\nProcessing Section: %s " % name
if name != 'dkey':
ext = u".dat"
if name == 'img': ext = u".jpg"
if name == 'color' : ext = u".jpg"
print u"Processing Section: {0}\n. . .".format(name),
for index in range (0,len(self.bookHeaderRecords[name])) :
fnum = "%04d" % index
fname = name + fnum + ext
fname = u"{0}{1:04d}{2}".format(name,index,ext)
destdir = outdir
if name == 'img':
destdir = os.path.join(outdir,'img')
destdir = os.path.join(outdir,u"img")
if name == 'color':
destdir = os.path.join(outdir,'color_img')
destdir = os.path.join(outdir,u"color_img")
if name == 'page':
destdir = os.path.join(outdir,'page')
destdir = os.path.join(outdir,u"page")
if name == 'glyphs':
destdir = os.path.join(outdir,'glyphs')
destdir = os.path.join(outdir,u"glyphs")
outputFile = os.path.join(destdir,fname)
print ".",
print u".",
record = self.getBookPayloadRecord(name,index)
if record != '':
file(outputFile, 'wb').write(record)
print " "
print u" "
def getHTMLZip(self, zipname):
def getFile(self, zipname):
htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
htmlzip.write(os.path.join(self.outdir,'book.html'),'book.html')
htmlzip.write(os.path.join(self.outdir,'book.opf'),'book.opf')
if os.path.isfile(os.path.join(self.outdir,'cover.jpg')):
htmlzip.write(os.path.join(self.outdir,'cover.jpg'),'cover.jpg')
htmlzip.write(os.path.join(self.outdir,'style.css'),'style.css')
zipUpDir(htmlzip, self.outdir, 'img')
htmlzip.write(os.path.join(self.outdir,u"book.html"),u"book.html")
htmlzip.write(os.path.join(self.outdir,u"book.opf"),u"book.opf")
if os.path.isfile(os.path.join(self.outdir,u"cover.jpg")):
htmlzip.write(os.path.join(self.outdir,u"cover.jpg"),u"cover.jpg")
htmlzip.write(os.path.join(self.outdir,u"style.css"),u"style.css")
zipUpDir(htmlzip, self.outdir, u"img")
htmlzip.close()
def getBookType(self):
return u"Topaz"
def getBookExtension(self):
return u".htmlz"
def getSVGZip(self, zipname):
svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
svgzip.write(os.path.join(self.outdir,'index_svg.xhtml'),'index_svg.xhtml')
zipUpDir(svgzip, self.outdir, 'svg')
zipUpDir(svgzip, self.outdir, 'img')
svgzip.write(os.path.join(self.outdir,u"index_svg.xhtml"),u"index_svg.xhtml")
zipUpDir(svgzip, self.outdir, u"svg")
zipUpDir(svgzip, self.outdir, u"img")
svgzip.close()
def getXMLZip(self, zipname):
xmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
targetdir = os.path.join(self.outdir,'xml')
zipUpDir(xmlzip, targetdir, '')
zipUpDir(xmlzip, self.outdir, 'img')
xmlzip.close()
def cleanup(self):
if os.path.isdir(self.outdir):
shutil.rmtree(self.outdir, True)
def usage(progname):
print "Removes DRM protection from Topaz ebooks and extract the contents"
print "Usage:"
print " %s [-k <kindle.info>] [-p <pidnums>] [-s <kindleSerialNumbers>] <infile> <outdir> " % progname
print u"Removes DRM protection from Topaz ebooks and extracts the contents"
print u"Usage:"
print u" {0} [-k <kindle.info>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname)
# Main
def main(argv=sys.argv):
global buildXML
def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
k4 = False
pids = []
serials = []
kInfoFiles = []
print u"TopazExtract v{0}.".format(__version__)
try:
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:x")
except getopt.GetoptError, err:
print str(err)
print u"Error in options or arguments: {0}".format(err.args[0])
usage(progname)
return 1
if len(args)<2:
usage(progname)
return 1
for o, a in opts:
if o == "-k":
if a == None :
print "Invalid parameter for -k"
return 1
kInfoFiles.append(a)
if o == "-p":
if a == None :
print "Invalid parameter for -p"
return 1
pids = a.split(',')
if o == "-s":
if a == None :
print "Invalid parameter for -s"
return 1
serials = a.split(',')
k4 = True
infile = args[0]
outdir = args[1]
if not os.path.isfile(infile):
print "Input File Does Not Exist"
print u"Input File {0} Does Not Exist.".format(infile)
return 1
if not os.path.exists(outdir):
print u"Output Directory {0} Does Not Exist.".format(outdir)
return 1
kInfoFiles = []
serials = []
pids = []
for o, a in opts:
if o == '-k':
if a == None :
raise DrmException("Invalid parameter for -k")
kInfoFiles.append(a)
if o == '-p':
if a == None :
raise DrmException("Invalid parameter for -p")
pids = a.split(',')
if o == '-s':
if a == None :
raise DrmException("Invalid parameter for -s")
serials = [serial.replace(" ","") for serial in a.split(',')]
bookname = os.path.splitext(os.path.basename(infile))[0]
tb = TopazBook(infile)
title = tb.getBookTitle()
print "Processing Book: ", title
keysRecord, keysRecordRecord = tb.getPIDMetaInfo()
pidlst = kgenpids.getPidList(keysRecord, keysRecordRecord, k4, pids, serials, kInfoFiles)
print u"Processing Book: {0}".format(title)
md1, md2 = tb.getPIDMetaInfo()
pids.extend(kgenpids.getPidList(md1, md2, serials, kInfoFiles))
try:
print "Decrypting Book"
tb.processBook(pidlst)
print u"Decrypting Book"
tb.processBook(pids)
print " Creating HTML ZIP Archive"
zipname = os.path.join(outdir, bookname + '_nodrm' + '.htmlz')
tb.getHTMLZip(zipname)
print u" Creating HTML ZIP Archive"
zipname = os.path.join(outdir, bookname + u"_nodrm.htmlz")
tb.getFile(zipname)
print " Creating SVG ZIP Archive"
zipname = os.path.join(outdir, bookname + '_SVG' + '.zip')
print u" Creating SVG ZIP Archive"
zipname = os.path.join(outdir, bookname + u"_SVG.zip")
tb.getSVGZip(zipname)
if buildXML:
print " Creating XML ZIP Archive"
zipname = os.path.join(outdir, bookname + '_XML' + '.zip')
tb.getXMLZip(zipname)
# removing internal temporary directory of pieces
tb.cleanup()
except TpzDRMError, e:
print str(e)
# tb.cleanup()
except DrmException, e:
print u"Decryption failed\n{0}".format(traceback.format_exc())
try:
tb.cleanup()
except:
pass
return 1
except Exception, e:
print str(e)
# tb.cleanup
print u"Decryption failed\m{0}".format(traceback.format_exc())
try:
tb.cleanup()
except:
pass
return 1
return 0
if __name__ == '__main__':
sys.stdout=Unbuffered(sys.stdout)
sys.exit(main())
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())

View File

@@ -1,31 +1,42 @@
eReader PDB2PML - eReaderPDB2PML_v06_plugin.zip
eReader PDB2PML - eReaderPDB2PML_v08_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.
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.
Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page. Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (eReaderPDB2PML_v08_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
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:
Customization
-------------
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
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 ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the 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.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.
On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.)
** 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.
On Windows, open a terminal/command window. (Start/Run… and then type cmd (without the s) as the program to run).
On Macintosh, open the Terminal application (in your Utilities folder).
On Linux open a command window. Hopefully all Linux users know how to do this, as I do not.
You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isnt already in calibre, and that calibre isnt running.
Now type in "calibredb add " (without the " but dont miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information.
Now copy the output from the terminal window.
On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it.
On Macintosh and Linux, just use the normal text select and copy commands.
Paste the information into a comment at my blog, http://apprenticealf.wordpress.com/ describing your problem.

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# eReaderPDB2PML_plugin.py
# Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/>
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
#
# All credit given to The Dark Reverser for the original standalone script.
# I had the much easier job of converting it to Calibre a plugin.
@@ -33,28 +34,56 @@
# 0.0.3 - removed added psyco code as it is not supported under Calibre's Python 2.7
# 0.0.4 - minor typos fixed
# 0.0.5 - updated to the new calibre plugin interface
# 0.0.6 - unknown changes
# 0.0.7 - improved config dialog processing and fix possible output/unicode problem
# 0.0.8 - Proper fix for unicode problems, separate out erdr2pml from plugin
PLUGIN_NAME = u"eReader PDB 2 PML"
PLUGIN_VERSION_TUPLE = (0, 0, 8)
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
import sys, os
from calibre.customize import FileTypePlugin
from calibre.ptempfile import PersistentTemporaryDirectory
from calibre.constants import iswindows, isosx
from calibre_plugins.erdrpdb2pml import erdr2pml
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
class eRdrDeDRM(FileTypePlugin):
name = 'eReader PDB 2 PML' # Name of the plugin
description = 'Removes DRM from secure pdb files. \
Credit given to The Dark Reverser for the original standalone script.'
name = PLUGIN_NAME
description = u"Removes DRM from secure pdb files. Credit given to The Dark Reverser for the original standalone script."
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'DiapDealer' # The author of this plugin
version = (0, 0, 6) # The version number of this plugin
author = u"DiapDealer, Apprentice Alf and The Dark Reverser"
version = PLUGIN_VERSION_TUPLE
file_types = set(['pdb']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import
minimum_calibre_version = (0, 7, 55)
priority = 100
def run(self, path_to_ebook):
global bookname, erdr2pml
# make sure any unicode output gets converted safely with 'replace'
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
print u"{0} v{1}: Trying to decrypt {2}.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
infile = path_to_ebook
bookname = os.path.splitext(os.path.basename(infile))[0]
@@ -62,79 +91,34 @@ class eRdrDeDRM(FileTypePlugin):
pmlzfile = self.temporary_file(bookname + '.pmlz')
if self.site_customization:
from calibre_plugins.erdrpdb2pml import erdr2pml
keydata = self.site_customization
ar = keydata.split(':')
for i in ar:
try:
name, cc = i.split(',')
user_key = erdr2pml.getuser_key(name,cc)
except ValueError:
print ' Error parsing user supplied data.'
print u"{0} v{1}: Error parsing user supplied data.".format(PLUGIN_NAME, PLUGIN_VERSION)
return path_to_ebook
try:
print "Processing..."
print u"{0} v{1}: Processing...".format(PLUGIN_NAME, PLUGIN_VERSION)
import time
start_time = time.time()
pmlfilepath = self.convertEreaderToPml(infile, name, cc, outdir)
if pmlfilepath and pmlfilepath != 1:
import zipfile
print " Creating PMLZ file"
myZipFile = zipfile.ZipFile(pmlzfile.name,'w',zipfile.ZIP_STORED, False)
list = os.listdir(outdir)
for file in list:
localname = file
filePath = os.path.join(outdir,file)
if os.path.isfile(filePath):
myZipFile.write(filePath, localname)
elif os.path.isdir(filePath):
imageList = os.listdir(filePath)
localimgdir = os.path.basename(filePath)
for image in imageList:
localname = os.path.join(localimgdir,image)
imagePath = os.path.join(filePath,image)
if os.path.isfile(imagePath):
myZipFile.write(imagePath, localname)
myZipFile.close()
end_time = time.time()
search_time = end_time - start_time
print 'elapsed time: %.2f seconds' % (search_time, )
print "done"
if erdr2pml.decryptBook(infile,pmlzfile.name,True,user_key) == 0:
print u"{0} v{1}: Elapsed time: {2:.2f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-start_time)
return pmlzfile.name
else:
raise ValueError('Error Creating PML file.')
raise ValueError(u"{0} v{1}: Error Creating PML file.".format(PLUGIN_NAME, PLUGIN_VERSION))
except ValueError, e:
print "Error: %s" % e
print u"{0} v{1}: Error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0])
pass
raise Exception('Couldn\'t decrypt pdb file.')
raise Exception(u"{0} v{1}: Couldn\'t decrypt pdb file. See Apprentice Alf's blog for help.".format(PLUGIN_NAME, PLUGIN_VERSION))
else:
raise Exception('No name and CC# provided.')
raise Exception(u"{0} v{1}: No name and CC# provided.".format(PLUGIN_NAME, PLUGIN_VERSION))
def convertEreaderToPml(self, infile, name, cc, outdir):
print " Decoding File"
sect = erdr2pml.Sectionizer(infile, 'PNRdPPrs')
er = erdr2pml.EreaderProcessor(sect, name, cc)
if er.getNumImages() > 0:
print " Extracting images"
#imagedir = bookname + '_img/'
imagedir = 'images/'
imagedirpath = os.path.join(outdir,imagedir)
if not os.path.exists(imagedirpath):
os.makedirs(imagedirpath)
for i in xrange(er.getNumImages()):
name, contents = er.getImage(i)
file(os.path.join(imagedirpath, name), 'wb').write(contents)
print " Extracting pml"
pml_string = er.getText()
pmlfilename = bookname + ".pml"
try:
file(os.path.join(outdir, pmlfilename),'wb').write(erdr2pml.cleanPML(pml_string))
return os.path.join(outdir, pmlfilename)
except:
return 1
def customization_help(self, gui=False):
return 'Enter Account Name & Last 8 digits of Credit Card number (separate with a comma)'
return u"Enter Account Name & Last 8 digits of Credit Card number (separate with a comma, multiple pairs with a colon)"

View File

@@ -1,8 +1,11 @@
#!/usr/bin/env python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
#
# -*- coding: utf-8 -*-
# erdr2pml.py
# Copyright © 2008 The Dark Reverser
#
# Modified 20082012 by some_updates, DiapDealer and Apprentice Alf
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
# Changelog
@@ -62,19 +65,11 @@
# 0.21 - Support eReader (drm) version 11.
# - Don't reject dictionary format.
# - Ignore sidebars for dictionaries (different format?)
# 0.22 - Unicode and plugin support, different image folders for PMLZ and source
__version__='0.21'
__version__='0.22'
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
import sys, re
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile
if 'calibre' in sys.modules:
@@ -82,8 +77,66 @@ if 'calibre' in sys.modules:
else:
inCalibre = False
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
Des = None
if sys.platform.startswith('win'):
if iswindows:
# first try with pycrypto
if inCalibre:
from calibre_plugins.erdrpdb2pml import pycrypto_des
@@ -168,17 +221,30 @@ class Sectionizer(object):
off = self.sections[section][0]
return self.contents[off:end_off]
def sanitizeFileName(s):
r = ''
for c in s:
if c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-":
r += c
return r
# cleanup unicode filenames
# borrowed from calibre from calibre/src/calibre/__init__.py
# added in removal of control (<32) chars
# and removal of . at start and end
# and with some (heavily edited) code from Paul Durrant's kindlenamer.py
def sanitizeFileName(name):
# substitute filename unfriendly characters
name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" ").replace(u": ",u" ").replace(u":",u"").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'")
# delete control characters
name = u"".join(char for char in name if ord(char)>=32)
# white space to single space, delete leading and trailing while space
name = re.sub(ur"\s", u" ", name).strip()
# remove leading dots
while len(name)>0 and name[0] == u".":
name = name[1:]
# remove trailing dots (Windows doesn't like them)
if name.endswith(u'.'):
name = name[:-1]
return name
def fixKey(key):
def fixByte(b):
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):
r=''
@@ -191,7 +257,7 @@ def deXOR(text, sp, table):
return r
class EreaderProcessor(object):
def __init__(self, sect, username, creditcard):
def __init__(self, sect, user_key):
self.section_reader = sect.loadSection
data = self.section_reader(0)
version, = struct.unpack('>H', data[0:2])
@@ -212,18 +278,10 @@ class EreaderProcessor(object):
for i in xrange(len(data)):
j = (j + shuf) % len(data)
r[j] = data[i]
assert len("".join(r)) == len(data)
assert len("".join(r)) == len(data)
return "".join(r)
r = unshuff(input[0:-8], cookie_shuf)
def fixUsername(s):
r = ''
for c in s.lower():
if (c >= 'a' and c <= 'z' or c >= '0' and c <= '9'):
r += c
return r
user_key = struct.pack('>LL', binascii.crc32(fixUsername(username)) & 0xffffffff, binascii.crc32(creditcard[-8:])& 0xffffffff)
drm_sub_version = struct.unpack('>H', r[0:2])[0]
self.num_text_pages = struct.unpack('>H', r[2:4])[0] - 1
self.num_image_pages = struct.unpack('>H', r[26:26+2])[0]
@@ -302,7 +360,7 @@ class EreaderProcessor(object):
sect = self.section_reader(self.first_image_page + i)
name = sect[4:4+32].strip('\0')
data = sect[62:]
return sanitizeFileName(name), data
return sanitizeFileName(unicode(name,'windows-1252')), data
# def getChapterNamePMLOffsetData(self):
@@ -399,60 +457,53 @@ class EreaderProcessor(object):
return r
def cleanPML(pml):
# Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
pml2 = pml
for k in xrange(128,256):
badChar = chr(k)
pml2 = pml2.replace(badChar, '\\a%03d' % k)
return pml2
# Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
pml2 = pml
for k in xrange(128,256):
badChar = chr(k)
pml2 = pml2.replace(badChar, '\\a%03d' % k)
return pml2
def convertEreaderToPml(infile, name, cc, outdir):
if not os.path.exists(outdir):
os.makedirs(outdir)
def decryptBook(infile, outpath, make_pmlz, user_key):
bookname = os.path.splitext(os.path.basename(infile))[0]
print " Decoding File"
sect = Sectionizer(infile, 'PNRdPPrs')
er = EreaderProcessor(sect, name, cc)
if er.getNumImages() > 0:
print " Extracting images"
imagedir = bookname + '_img/'
imagedirpath = os.path.join(outdir,imagedir)
if not os.path.exists(imagedirpath):
os.makedirs(imagedirpath)
for i in xrange(er.getNumImages()):
name, contents = er.getImage(i)
file(os.path.join(imagedirpath, name), 'wb').write(contents)
print " Extracting pml"
pml_string = er.getText()
pmlfilename = bookname + ".pml"
file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
# bkinfo = er.getBookInfo()
# if bkinfo != '':
# print " Extracting book meta information"
# file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo)
def decryptBook(infile, outdir, name, cc, make_pmlz):
if make_pmlz :
# ignore specified outdir, use tempdir instead
if make_pmlz:
# outpath is actually pmlz name
pmlzname = outpath
outdir = tempfile.mkdtemp()
imagedirpath = os.path.join(outdir,u"images")
else:
pmlzname = None
outdir = outpath
imagedirpath = os.path.join(outdir,bookname + u"_img")
try:
print "Processing..."
convertEreaderToPml(infile, name, cc, outdir)
if make_pmlz :
if not os.path.exists(outdir):
os.makedirs(outdir)
print u"Decoding File"
sect = Sectionizer(infile, 'PNRdPPrs')
er = EreaderProcessor(sect, user_key)
if er.getNumImages() > 0:
print u"Extracting images"
if not os.path.exists(imagedirpath):
os.makedirs(imagedirpath)
for i in xrange(er.getNumImages()):
name, contents = er.getImage(i)
file(os.path.join(imagedirpath, name), 'wb').write(contents)
print u"Extracting pml"
pml_string = er.getText()
pmlfilename = bookname + ".pml"
file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
if pmlzname is not None:
import zipfile
import shutil
print " Creating PMLZ file"
zipname = infile[:-4] + '.pmlz'
myZipFile = zipfile.ZipFile(zipname,'w',zipfile.ZIP_STORED, False)
print u"Creating PMLZ file {0}".format(os.path.basename(pmlzname))
myZipFile = zipfile.ZipFile(pmlzname,'w',zipfile.ZIP_STORED, False)
list = os.listdir(outdir)
for file in list:
localname = file
filePath = os.path.join(outdir,file)
for filename in list:
localname = filename
filePath = os.path.join(outdir,filename)
if os.path.isfile(filePath):
myZipFile.write(filePath, localname)
elif os.path.isdir(filePath):
@@ -466,36 +517,46 @@ def decryptBook(infile, outdir, name, cc, make_pmlz):
myZipFile.close()
# remove temporary directory
shutil.rmtree(outdir, True)
print 'output is %s' % zipname
print u"Output is {0}".format(pmlzname)
else :
print 'output in %s' % outdir
print u"Output is in {0}".format(outdir)
print "done"
except ValueError, e:
print "Error: %s" % e
print u"Error: {0}".format(e.args[0])
return 1
return 0
def usage():
print "Converts DRMed eReader books to PML Source"
print "Usage:"
print " erdr2pml [options] infile.pdb [outdir] \"your name\" credit_card_number "
print " "
print "Options: "
print " -h prints this message"
print " --make-pmlz create PMLZ instead of using output directory"
print " "
print "Note:"
print " if ommitted, outdir defaults based on 'infile.pdb'"
print " It's enough to enter the last 8 digits of the credit card number"
print u"Converts DRMed eReader books to PML Source"
print u"Usage:"
print u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number"
print u" "
print u"Options: "
print u" -h prints this message"
print u" -p create PMLZ instead of source folder"
print u" --make-pmlz create PMLZ instead of source folder"
print u" "
print u"Note:"
print u" if outpath is ommitted, creates source in 'infile_Source' folder"
print u" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'"
print u" if source folder created, images are in infile_img folder"
print u" if pmlz file created, images are in images folder"
print u" It's enough to enter the last 8 digits of the credit card number"
return
def getuser_key(name,cc):
newname = "".join(c for c in name.lower() if c >= 'a' and c <= 'z' or c >= '0' and c <= '9')
cc = cc.replace(" ","")
return struct.pack('>LL', binascii.crc32(newname) & 0xffffffff,binascii.crc32(cc[-8:])& 0xffffffff)
def cli_main(argv=unicode_argv()):
print u"eRdr2Pml v{0}. Copyright © 20092012 The Dark Reverser et al.".format(__version__)
def main(argv=None):
try:
opts, args = getopt.getopt(sys.argv[1:], "h", ["make-pmlz"])
opts, args = getopt.getopt(argv[1:], "hp", ["make-pmlz"])
except getopt.GetoptError, err:
print str(err)
print err.args[0]
usage()
return 1
make_pmlz = False
@@ -503,25 +564,31 @@ def main(argv=None):
if o == "-h":
usage()
return 0
elif o == "-p":
make_pmlz = True
elif o == "--make-pmlz":
make_pmlz = True
print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__
if len(args)!=3 and len(args)!=4:
usage()
return 1
if len(args)==3:
infile, name, cc = args[0], args[1], args[2]
outdir = infile[:-4] + '_Source'
infile, name, cc = args
if make_pmlz:
outpath = os.path.splitext(infile)[0] + u".pmlz"
else:
outpath = os.path.splitext(infile)[0] + u"_Source"
elif len(args)==4:
infile, outdir, name, cc = args[0], args[1], args[2], args[3]
infile, outpath, name, cc = args
return decryptBook(infile, outdir, name, cc, make_pmlz)
print getuser_key(name,cc).encode('hex')
return decryptBook(infile, outpath, make_pmlz, getuser_key(name,cc))
if __name__ == "__main__":
sys.stdout=Unbuffered(sys.stdout)
sys.exit(main())
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())

View File

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

View File

@@ -28,4 +28,3 @@ def load_pycrypto():
i += 8
return ''.join(result)
return DES

View File

@@ -2,8 +2,8 @@
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
import sys
ECB = 0
CBC = 1
ECB = 0
CBC = 1
class Des(object):
__pc1 = [56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17,
9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35,
@@ -11,13 +11,13 @@ class Des(object):
13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3]
__left_rotations = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
__pc2 = [13, 16, 10, 23, 0, 4,2, 27, 14, 5, 20, 9,
22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31]
__ip = [57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6]
22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31]
__ip = [57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6]
__expansion_table = [31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8,
7, 8, 9, 10, 11, 12,11, 12, 13, 14, 15, 16,
15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24,
@@ -61,8 +61,8 @@ class Des(object):
35, 3, 43, 11, 51, 19, 59, 27,34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25,32, 0, 40, 8, 48, 16, 56, 24]
# Type of crypting being done
ENCRYPT = 0x00
DECRYPT = 0x01
ENCRYPT = 0x00
DECRYPT = 0x01
def __init__(self, key, mode=ECB, IV=None):
if len(key) != 8:
raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
@@ -74,7 +74,7 @@ class Des(object):
self.setIV(IV)
self.L = []
self.R = []
self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
self.final = []
self.setKey(key)
def getKey(self):

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Ignoble Epub DeDRM Plugin Configuration</title>
</head>
<body>
<h1>Ignoble Epub DeDRM Plugin</h1>
<h3>(version 0.2.4)</h3>
<h3> For additional help read the <a href="http://apprenticealf.wordpress.com/2011/01/17/frequently-asked-questions-about-the-drm-removal-tools/" target="_blank">FAQ</a> on <a href="http://apprenticealf.wordpress.com" target="_blank">Apprentice Alf's Blog</a> and ask questions in the comments section of the <a href="http://apprenticealf.wordpress.com/2012/09/10/drm-removal-tools-for-ebooks/" target="_blank">first post</a>.</h3>
<p>All credit given to I ♥ Cabbages for the original standalone scripts (I had the much easier job of converting them to a calibre plugin).</p>
<p>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.</p>
<p>This help file is always available from within the plugin's customization dialog in calibre (when installed, of course). The "Plugin Help" link can be found in the upper-right portion of the customization dialog.</p>
<h3>Installation:</h3>
<p>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_v02.3_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog. <b><u>Now restart calibre</u></b>.</p>
<h3>Configuration:</h3>
<p>Upon first installing the plugin (or upgrading from a version earlier than 0.2.0), the plugin will be unconfigured. Until you create at least one B&amp;N key&mdash;or migrate your existing key(s)/data from an earlier version of the plugin&mdash;the plugin will not function. When unconfigured (no saved keys)... an error message will occur whenever ePubs are imported to calibre. To eliminate the error message, open the plugin's customization dialog and create/import/migrate a key (or disable/uninstall the plugin). You can get to the plugin's customization dialog by opening calibre's Preferences dialog, and clicking Plugins (under the Advanced section). Once in the Plugin Preferences, expand the "File type plugins" section and look for the "Ignoble Epub DeDRM" plugin. Highlight that plugin and click the "Customize plugin" button.</p>
<p>If you are upgrading from an earlier version of this plugin and have provided your name(s) and credit card number(s) as part of the old plugin's customization string, you will be prompted to migrate this data to the plugin's new, more secure, key storage method when you open the customization dialog for the first time. If you choose NOT to migrate that data, you will be prompted to save that data as a text file in a location of your choosing. Either way, this plugin will no longer be storing names and credit card numbers in plain sight (or anywhere for that matter) on your computer or in calibre. If you don't choose to migrate OR save the data, that data will be lost. You have been warned!!</p>
<p>Upon configuring for the first time, you may also be asked if you wish to import your existing *.b64 keyfiles (if you use them) to the plugin's new key storage method. The new plugin no longer looks for keyfiles in calibre's configuration directory, so it's highly recommended that you import any existing keyfiles when prompted ... but you <i>always</i> have the ability to import existing keyfiles anytime you might need/want to.</p>
<p>If you have upgraded from an earlier version of the plugin, the above instructions may be all you need to do to get the new plugin up and running. Continue reading for new-key generation and existing-key management instructions.</p>
<h4 style="margin-left: 1.0em;"><u>Creating New Keys:</u></h4>
<p style="margin-left: 1.0em">On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key.</p>
<ul style="margin-left: 2.0em;">
<li><b>Unique Key Name:</b> this is a unique name you choose to help you identify the key after it's created. This name will show in the list of configured keys. Choose something that will help you remember the data (name, cc#) it was created with.</i>
<li style="margin-top: 0.5em;"><b>Your Name:</b> Your name as set in your Barnes & Noble account, My Account page, directly under PERSONAL INFORMATION. It is usually just your first name and last name separated by a space. This name will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that's stored in the preferences.</i>
<li style="margin-top: 0.5em;"><b>Credit Card#:</b> this is the default credit card number that was on file with Barnes & Noble at the time of download of the ebook to be de-DRMed. Nothing fancy here; no dashes or spaces ... just the 16 (15 for American Express) digits. Again... this number will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that's stored in the preferences.</i>
</ul>
<p style="margin-left: 1.0em;">Click the 'OK" button to create and store the generated key. Or Cancel if you didn't want to create a key.</p>
<h4 style="margin-left: 1.0em;"><u>Deleting Keys:</u></h4>
<p style="margin-left: 1.0em;">On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted key in the list. You will be prompted once to be sure that's what you truly mean to do. Once gone, it's permanently gone.</p>
<h4 style="margin-left: 1.0em;"><u>Exporting Keys:</u></h4>
<p style="margin-left: 1.0em;">On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a computer's hard-drive. Use this button to export the highlighted key to a file (*.b64). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.</p>
<h4 style="margin-left: 1.0em;"><u>Importing Existing Keyfiles:</u></h4>
<p style="margin-left: 1.0em;">At the bottom-left of the plugin's customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing *.b64 keyfiles. Used for migrating keyfiles from older versions of the plugin (or keys generated with the original I &lt;3 Cabbages script), or moving keyfiles from computer to computer, or restoring a backup. Some very basic validation is done to try to avoid overwriting already configured keys with incoming, imported keyfiles with the same base file name, but I'm sure that could be broken if someone tried hard. Just take care when importing.</p>
<p>Once done creating/importing/exporting/deleting decryption keys; click "OK" to exit the customization dialogue (the cancel button will actually work the same way here ... at this point all data/changes are committed already, so take your pick).</p>
<h3>Troubleshooting:</h3>
<p style="margin-top: 0.5em;">If you find that it's not working for you (imported Barnes & Noble 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. ;)</p>
<p>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.</p>
<p>Another way to debug (perhaps easier if you're not all that comfortable with command-line stuff) is to launch calibre in debug mode. Open a command prompt (terminal) and type "calibre-debug -g" (again without the quotes). Calibre will launch, and you can can add the problem book(s) using the normal gui method. The debug info will be output to the original command prompt (terminal window). Copy the resulting output and paste it into any online help request you make.</p>
<p>&nbsp;</p>
<p>** 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.</p>
<p>&nbsp;</p>
<h4>Revision history:</h4>
<pre>
0.1.0 - Initial release
0.1.1 - Allow Windows users to make use of openssl if they have it installed.
- Incorporated SomeUpdates zipfix routine.
0.1.2 - bug fix for non-ascii file names in encryption.xml
0.1.3 - Try PyCrypto on Windows first
0.1.4 - update zipfix to deal with mimetype not in correct place
0.1.5 - update zipfix to deal with completely missing mimetype files
0.1.6 - update to the new calibre plugin interface
0.1.7 - Fix for potential problem with PyCrypto
0.1.8 - an updated/modified zipfix.py and included zipfilerugged.py
0.2.0 - Completely overhauled plugin configuration dialog and key management/storage
0.2.1 - an updated/modified zipfix.py and included zipfilerugged.py
0.2.2 - added in potential fixes from 0.1.7 that had been missed.
0.2.3 - fixed possible output/unicode problem
0.2.4 - ditched nearly hopeless caselessStrCmp method in favor of uStrCmp.
- added ability to rename existing keys.
</pre>
</body>
</html>

View File

@@ -0,0 +1,199 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
__license__ = 'GPL v3'
__docformat__ = 'restructuredtext en'
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
#
# Requires Calibre version 0.7.55 or higher.
#
# 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 a version of 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.
#
# Configuration:
# Check out the plugin's configuration settings by clicking the "Customize plugin"
# button when you have the "BnN ePub DeDRM" plugin highlighted (under Preferences->
# Plugins->File type plugins). Once you have the configuration dialog open, you'll
# see a Help link on the top right-hand side.
#
# Revision history:
# 0.1.0 - Initial release
# 0.1.1 - Allow Windows users to make use of openssl if they have it installed.
# - Incorporated SomeUpdates zipfix routine.
# 0.1.2 - bug fix for non-ascii file names in encryption.xml
# 0.1.3 - Try PyCrypto on Windows first
# 0.1.4 - update zipfix to deal with mimetype not in correct place
# 0.1.5 - update zipfix to deal with completely missing mimetype files
# 0.1.6 - update for the new calibre plugin interface
# 0.1.7 - Fix for potential problem with PyCrypto
# 0.1.8 - an updated/modified zipfix.py and included zipfilerugged.py
# 0.2.0 - Completely overhauled plugin configuration dialog and key management/storage
# 0.2.1 - added zipfix.py and included zipfilerugged.py from 0.1.8
# 0.2.2 - added in potential fixes from 0.1.7 that had been missed.
# 0.2.3 - fixed possible output/unicode problem
# 0.2.4 - ditched nearly hopeless caselessStrCmp method in favor of uStrCmp.
# - added ability to rename existing keys.
# 0.2.5 - Major code change to use unaltered ignobleepub.py 3.6 and
# - ignoblekeygen 2.4 and later.
"""
Decrypt Barnes & Noble ADEPT encrypted EPUB books.
"""
PLUGIN_NAME = u"Ignoble Epub DeDRM"
PLUGIN_VERSION_TUPLE = (0, 2, 5)
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
# Include an html helpfile in the plugin's zipfile with the following name.
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
import sys, os, re
import zipfile
from zipfile import ZipFile
class IGNOBLEError(Exception):
pass
from calibre.customize import FileTypePlugin
from calibre.constants import iswindows, isosx
from calibre.gui2 import is_ok_to_use_qt
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
class IgnobleDeDRM(FileTypePlugin):
name = PLUGIN_NAME
description = u"Removes DRM from secure Barnes & Noble epub files. Credit given to i♥cabbages for the original stand-alone scripts."
supported_platforms = ['linux', 'osx', 'windows']
author = u"DiapDealer, Apprentice Alf and i♥cabbages"
version = PLUGIN_VERSION_TUPLE
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
file_types = set(['epub'])
on_import = True
priority = 101
def run(self, path_to_ebook):
# make sure any unicode output gets converted safely with 'replace'
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
print u"{0} v{1}: Trying to decrypt {2}.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
# First time use or first time after upgrade to new key-handling/storage method
# or no keys configured. Give a visual prompt to configure.
import calibre_plugins.ignobleepub.config as cfg
if not cfg.prefs['configured']:
titlemsg = '%s v%s' % (PLUGIN_NAME, PLUGIN_VERSION)
errmsg = titlemsg + ' not (properly) configured!\n' + \
'\nThis may be the first time you\'ve used this plugin' + \
' (or the first time since upgrading this plugin).' + \
' You\'ll need to open the customization dialog (Preferences->Plugins->File type plugins)' + \
' and follow the instructions there.\n' + \
'\nIf you don\'t use the ' + PLUGIN_NAME + ' plugin, you should disable or uninstall it.'
if is_ok_to_use_qt():
from PyQt4.Qt import QMessageBox
d = QMessageBox(QMessageBox.Warning, titlemsg, errmsg )
d.show()
d.raise_()
d.exec_()
raise Exception('%s Plugin v%s: Plugin not configured.' % (PLUGIN_NAME, PLUGIN_VERSION))
# Create a TemporaryPersistent file to work with.
# Check original epub archive for zip errors.
from calibre_plugins.ignobleepub import zipfix
inf = self.temporary_file(u".epub")
try:
print u"{0} v{1}: Verifying zip archive integrity.".format(PLUGIN_NAME, PLUGIN_VERSION)
fr = zipfix.fixZip(path_to_ebook, inf.name)
fr.fix()
except Exception, e:
print u"{0} v{1}: Error \'{2}\' when checking zip archive.".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
raise Exception(e)
return
#check the book
from calibre_plugins.ignobleepub import ignobleepub
if not ignobleepub.ignobleBook(inf.name):
print u"{0} v{1}: {2} is not a secure Barnes & Noble ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
# return the original file, so that no error message is generated in the GUI
return path_to_ebook
# Attempt to decrypt epub with each encryption key (generated or provided).
for keyname, userkey in cfg.prefs['keys'].items():
keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname)
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked)
of = self.temporary_file(u".epub")
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
result = ignobleepub.decryptBook(userkey, inf.name, of.name)
# Ebook is not a B&N epub... do nothing and pass it on.
# This allows a non-encrypted epub to be imported without error messages.
if result[0] == 1:
print u"{0} v{1}: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, result[1])
of.close()
return path_to_ebook
break
# Decryption was successful return the modified PersistentTemporary
# file to Calibre's import process.
if result[0] == 0:
print u"{0} v{1}: Encryption successfully removed.".format(PLUGIN_NAME, PLUGIN_VERSION)
of.close()
return of.name
break
print u"{0} v{1}: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, result[1])
of.close()
# Something went wrong with decryption.
# Import the original unmolested epub.
print(u"{0} v{1}: Ultimately failed to decrypt".format(PLUGIN_NAME, PLUGIN_VERSION))
return path_to_ebook
def is_customizable(self):
# return true to allow customization via the Plugin->Preferences.
return True
def config_widget(self):
from calibre_plugins.ignobleepub.config import ConfigWidget
# Extract the helpfile contents from in the plugin's zipfile.
# The helpfile must be named <plugin name variable> + '_Help.htm'
return ConfigWidget(self.load_resources(RESOURCE_NAME)[RESOURCE_NAME])
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
def save_settings(self, config_widget):
config_widget.save_settings()

View File

@@ -0,0 +1,305 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
__license__ = 'GPL v3'
# Standard Python modules.
import os, sys, re, hashlib
# PyQT4 modules (part of calibre).
from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
QGroupBox, QPushButton, QListWidget, QListWidgetItem,
QAbstractItemView, QIcon, QDialog, QUrl, QString)
from PyQt4 import QtGui
# calibre modules and constants.
from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url,
choose_dir, choose_files)
from calibre.utils.config import dynamic, config_dir, JSONConfig
# modules from this plugin's zipfile.
from calibre_plugins.ignobleepub.__init__ import PLUGIN_NAME, PLUGIN_VERSION
from calibre_plugins.ignobleepub.__init__ import RESOURCE_NAME as help_file_name
from calibre_plugins.ignobleepub.utilities import (uStrCmp, DETAILED_MESSAGE, parseCustString)
from calibre_plugins.ignobleepub.dialogs import AddKeyDialog, RenameKeyDialog
from calibre_plugins.ignobleepub.ignoblekeygen import generate_key
JSON_NAME = PLUGIN_NAME.strip().lower().replace(' ', '_')
JSON_PATH = 'plugins/' + JSON_NAME + '.json'
# 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(JSON_PATH)
# Set defaults
prefs.defaults['keys'] = {}
prefs.defaults['configured'] = False
class ConfigWidget(QWidget):
def __init__(self, help_file_data):
QWidget.__init__(self)
self.help_file_data = help_file_data
self.plugin_keys = prefs['keys']
# Handle the old plugin's customization string by either converting the
# old string to stored keys or by saving the string to a text file of the
# user's choice. Either way... get that personal data out of plain sight.
from calibre.customize.ui import config
sc = config['plugin_customization']
val = sc.get(PLUGIN_NAME, None)
if val is not None:
title = 'Convert existing customization data?'
msg = '<p>Convert your existing insecure customization data? (Please '+ \
'read the detailed message)'
det_msg = DETAILED_MESSAGE
# Offer to convert the old string to the new format
if question_dialog(self, _(title), _(msg), det_msg, True, True):
userkeys = parseCustString(str(val))
if userkeys:
counter = 0
# Yay! We found valid customization data... add it to the new plugin
for k in userkeys:
counter += 1
self.plugin_keys['Converted Old Plugin Key - ' + str(counter)] = k
msg = '<p><b>' + str(counter) + '</b> User key(s) configured from old plugin customization string'
inf = info_dialog(None, _(PLUGIN_NAME + 'info_dlg'), _(msg), show=True)
val = sc.pop(PLUGIN_NAME, None)
if val is not None:
config['plugin_customization'] = sc
else:
# The existing customization string was invalid and wouldn't have
# worked anyway. Offer to save it as a text file and get rid of it.
errmsg = '<p>Unknown Error converting user supplied-customization string'
r = error_dialog(None, PLUGIN_NAME,
_(errmsg), show=True, show_copy_button=False)
self.saveOldCustomizationData(str(val))
val = sc.pop(PLUGIN_NAME, None)
if val is not None:
config['plugin_customization'] = sc
# If they don't want to convert the old string to keys then
# offer to save the old string to a text file and delete the
# the old customization string.
else:
self.saveOldCustomizationData(str(val))
val = sc.pop(PLUGIN_NAME, None)
if val is not None:
config['plugin_customization'] = sc
# First time run since upgrading to new key storage method, or 0 keys configured.
# Prompt to import pre-existing key files.
if not prefs['configured']:
title = 'Import existing key files?'
msg = '<p>This plugin no longer uses *.b64 keyfiles stored in calibre\'s configuration '+ \
'directory. Do you have any exsiting key files there (or anywhere) that you\'d '+ \
'like to migrate into the new plugin preferences method?'
if question_dialog(self, _(title), _(msg)):
self.migrate_files()
# Start Qt Gui dialog layout
layout = QVBoxLayout(self)
self.setLayout(layout)
help_layout = QHBoxLayout()
layout.addLayout(help_layout)
# Add hyperlink to a help file at the right. We will replace the correct name when it is clicked.
help_label = QLabel('<a href="http://www.foo.com/">Plugin Help</a>', self)
help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
help_label.setAlignment(Qt.AlignRight)
help_label.linkActivated.connect(self.help_link_activated)
help_layout.addWidget(help_label)
keys_group_box = QGroupBox(_('Configured Ignoble Keys:'), self)
layout.addWidget(keys_group_box)
keys_group_box_layout = QHBoxLayout()
keys_group_box.setLayout(keys_group_box_layout)
self.listy = QListWidget(self)
self.listy.setToolTip(_('<p>Stored Ignoble keys that will be used for decryption'))
self.listy.setSelectionMode(QAbstractItemView.SingleSelection)
self.populate_list()
keys_group_box_layout.addWidget(self.listy)
button_layout = QVBoxLayout()
keys_group_box_layout.addLayout(button_layout)
self._add_key_button = QtGui.QToolButton(self)
self._add_key_button.setToolTip(_('Create new key'))
self._add_key_button.setIcon(QIcon(I('plus.png')))
self._add_key_button.clicked.connect(self.add_key)
button_layout.addWidget(self._add_key_button)
self._delete_key_button = QtGui.QToolButton(self)
self._delete_key_button.setToolTip(_('Delete highlighted key'))
self._delete_key_button.setIcon(QIcon(I('list_remove.png')))
self._delete_key_button.clicked.connect(self.delete_key)
button_layout.addWidget(self._delete_key_button)
self._rename_key_button = QtGui.QToolButton(self)
self._rename_key_button.setToolTip(_('Rename highlighted key'))
self._rename_key_button.setIcon(QIcon(I('edit-select-all.png')))
self._rename_key_button.clicked.connect(self.rename_key)
button_layout.addWidget(self._rename_key_button)
self.export_key_button = QtGui.QToolButton(self)
self.export_key_button.setToolTip(_('Export highlighted key'))
self.export_key_button.setIcon(QIcon(I('save.png')))
self.export_key_button.clicked.connect(self.export_key)
button_layout.addWidget(self.export_key_button)
spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
button_layout.addItem(spacerItem)
layout.addSpacing(20)
migrate_layout = QHBoxLayout()
layout.addLayout(migrate_layout)
self.migrate_btn = QPushButton(_('Import Existing Keyfiles'), self)
self.migrate_btn.setToolTip(_('<p>Import *.b64 keyfiles (used by older versions of the plugin).'))
self.migrate_btn.clicked.connect(self.migrate_wrapper)
migrate_layout.setAlignment(Qt.AlignLeft)
migrate_layout.addWidget(self.migrate_btn)
self.resize(self.sizeHint())
def populate_list(self):
for key in self.plugin_keys.keys():
self.listy.addItem(QListWidgetItem(key))
def add_key(self):
d = AddKeyDialog(self)
d.exec_()
if d.result() != d.Accepted:
# New key generation cancelled.
return
self.plugin_keys[d.key_name] = generate_key(d.user_name, d.cc_number)
self.listy.clear()
self.populate_list()
def rename_key(self):
if not self.listy.currentItem():
errmsg = '<p>No keyfile selected to export. Highlight a keyfile first.'
r = error_dialog(None, PLUGIN_NAME,
_(errmsg), show=True, show_copy_button=False)
return
d = RenameKeyDialog(self)
d.exec_()
if d.result() != d.Accepted:
# rename cancelled or moot.
return
keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8')
if not question_dialog(self, _('Are you sure?'), _('<p>'+
'Do you really want to rename the Ignoble key named <strong>%s</strong> to <strong>%s</strong>?') % (keyname, d.key_name),
show_copy_button=False, default_yes=False):
return
self.plugin_keys[d.key_name] = self.plugin_keys[keyname]
del self.plugin_keys[keyname]
self.listy.clear()
self.populate_list()
def delete_key(self):
if not self.listy.currentItem():
return
keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8')
if not question_dialog(self, _('Are you sure?'), _('<p>'+
'Do you really want to delete the Ignoble key named <strong>%s</strong>?') % keyname,
show_copy_button=False, default_yes=False):
return
del self.plugin_keys[keyname]
self.listy.clear()
self.populate_list()
def help_link_activated(self, url):
def get_help_file_resource():
# Copy the HTML helpfile to the plugin directory each time the
# link is clicked in case the helpfile is updated in newer plugins.
file_path = os.path.join(config_dir, 'plugins', help_file_name)
with open(file_path,'w') as f:
f.write(self.help_file_data)
return file_path
url = 'file:///' + get_help_file_resource()
open_url(QUrl(url))
def save_settings(self):
prefs['keys'] = self.plugin_keys
if prefs['keys']:
prefs['configured'] = True
else:
prefs['configured'] = False
def migrate_files(self):
dynamic[PLUGIN_NAME + 'config_dir'] = config_dir
files = choose_files(self, PLUGIN_NAME + 'config_dir',
_('Select Ignoble keyfiles to import'), [('Ignoble Keyfiles', ['b64'])], False)
if files:
counter = 0
skipped = 0
for filename in files:
fpath = os.path.join(config_dir, filename)
new_key_name = os.path.splitext(os.path.basename(filename))[0]
match = False
for key in self.plugin_keys.keys():
if uStrCmp(new_key_name, key, True):
skipped += 1
msg = '<p>A key with the name <strong>' + new_key_name + '</strong> already exists! </p>' + \
'<p>Skipping key file named <strong>' + filename + '</strong>.</p>' + \
'<p>Either delete the existing key and re-migrate, or ' + \
'create that key manually with a different name.'
inf = info_dialog(None, _(PLUGIN_NAME + 'info_dlg'),
_(msg), show=True)
match = True
break
if not match:
with open(fpath, 'rb') as f:
counter += 1
self.plugin_keys[unicode(new_key_name)] = f.read()
msg = '<p>Done migrating <strong>' + str(counter) + '</strong> ' + \
'key files...</p><p>Skipped <strong>' + str(skipped) + '</strong> key files.'
inf = info_dialog(None, _(PLUGIN_NAME + 'info_dlg'),
_(msg), show=True)
return 1
return 0
def migrate_wrapper(self):
if self.migrate_files():
self.listy.clear()
self.populate_list()
def export_key(self):
if not self.listy.currentItem():
errmsg = '<p>No keyfile selected to export. Highlight a keyfile first.'
r = error_dialog(None, PLUGIN_NAME,
_(errmsg), show=True, show_copy_button=False)
return
filter = QString('Ignoble Key Files (*.b64)')
keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8')
if dynamic.get(PLUGIN_NAME + 'save_dir'):
defaultname = os.path.join(dynamic.get(PLUGIN_NAME + 'save_dir'), keyname + '.b64')
else:
defaultname = os.path.join(os.path.expanduser('~'), keyname + '.b64')
filename = unicode(QtGui.QFileDialog.getSaveFileName(self, "Save Ignoble Key File as...", defaultname,
"Ignoble Key Files (*.b64)", filter))
if filename:
dynamic[PLUGIN_NAME + 'save_dir'] = os.path.split(filename)[0]
fname = open(filename, 'w')
fname.write(self.plugin_keys[keyname])
fname.close()
def saveOldCustomizationData(self, strdata):
filter = QString('Text files (*.txt)')
default_basefilename = PLUGIN_NAME + ' old customization data.txt'
defaultname = os.path.join(os.path.expanduser('~'), default_basefilename)
filename = unicode(QtGui.QFileDialog.getSaveFileName(self, "Save old plugin style customization data as...", defaultname,
"Text Files (*.txt)", filter))
if filename:
fname = open(filename, 'w')
fname.write(strdata)
fname.close()

View File

@@ -0,0 +1,160 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
from PyQt4.Qt import (Qt, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
QGroupBox, QDialog, QDialogButtonBox)
from calibre.gui2 import error_dialog
from calibre_plugins.ignobleepub.__init__ import PLUGIN_NAME, PLUGIN_VERSION
from calibre_plugins.ignobleepub.utilities import uStrCmp
class AddKeyDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, parent)
self.parent = parent
self.setWindowTitle('Create New Ignoble Key')
layout = QVBoxLayout(self)
self.setLayout(layout)
data_group_box = QGroupBox('', self)
layout.addWidget(data_group_box)
data_group_box_layout = QVBoxLayout()
data_group_box.setLayout(data_group_box_layout)
key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel('Unique Key Name:', self))
self.key_ledit = QLineEdit('', self)
self.key_ledit.setToolTip(_('<p>Enter an identifying name for this new Ignoble key.</p>' +
'<p>It should be something that will help you remember ' +
'what personal information was used to create it.'))
key_group.addWidget(self.key_ledit)
key_label = QLabel(_(''), self)
key_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(key_label)
name_group = QHBoxLayout()
data_group_box_layout.addLayout(name_group)
name_group.addWidget(QLabel('Your Name:', self))
self.name_ledit = QLineEdit('', self)
self.name_ledit.setToolTip(_('<p>Enter your name as it appears in your B&N ' +
'account and/or on your credit card.</p>' +
'<p>It will only be used to generate this ' +
'one-time key and won\'t be stored anywhere ' +
'in calibre or on your computer.</p>' +
'<p>(ex: Jonathan Smith)'))
name_group.addWidget(self.name_ledit)
name_disclaimer_label = QLabel(_('Will not be stored/saved in configuration data:'), self)
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(name_disclaimer_label)
ccn_group = QHBoxLayout()
data_group_box_layout.addLayout(ccn_group)
ccn_group.addWidget(QLabel('Credit Card#:', self))
self.cc_ledit = QLineEdit('', self)
self.cc_ledit.setToolTip(_('<p>Enter the full credit card number on record ' +
'in your B&N account.</p>' +
'<p>No spaces or dashes... just the numbers. ' +
'This CC# will only be used to generate this ' +
'one-time key and won\'t be stored anywhere in ' +
'calibre or on your computer.'))
ccn_group.addWidget(self.cc_ledit)
ccn_disclaimer_label = QLabel(_('Will not be stored/saved in configuration data:'), self)
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(ccn_disclaimer_label)
layout.addSpacing(20)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.button_box.accepted.connect(self.accept)
self.button_box.rejected.connect(self.reject)
layout.addWidget(self.button_box)
self.resize(self.parent.sizeHint())
def accept(self):
if (self.key_ledit.text().isEmpty() or self.name_ledit.text().isEmpty()
or self.cc_ledit.text().isEmpty()):
errmsg = '<p>All fields are required!'
return error_dialog(None, PLUGIN_NAME,
_(errmsg), show=True, show_copy_button=False)
if (unicode(self.key_ledit.text()).isspace() or unicode(self.name_ledit.text()).isspace()
or unicode(self.cc_ledit.text()).isspace()):
errmsg = '<p>All fields are required!'
return error_dialog(None, PLUGIN_NAME,
_(errmsg), show=True, show_copy_button=False)
if not unicode(self.cc_ledit.text()).isdigit():
errmsg = '<p>Numbers only in the credit card number field!'
return error_dialog(None, PLUGIN_NAME,
_(errmsg), show=True, show_copy_button=False)
if len(self.key_ledit.text()) < 4:
errmsg = '<p>Key name must be at <i>least</i> 4 characters long!'
return error_dialog(None, PLUGIN_NAME,
_(errmsg), show=True, show_copy_button=False)
for k in self.parent.plugin_keys.keys():
if uStrCmp(self.key_ledit.text(), k, True):
errmsg = '<p>The key name <strong>%s</strong> is already being used.' % self.key_ledit.text()
return error_dialog(None, PLUGIN_NAME,
_(errmsg), show=True, show_copy_button=False)
QDialog.accept(self)
@property
def user_name(self):
return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
@property
def cc_number(self):
return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
@property
def key_name(self):
return unicode(self.key_ledit.text().toUtf8(), 'utf8')
class RenameKeyDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, parent)
self.parent = parent
self.setWindowTitle('Rename Ignoble Key')
layout = QVBoxLayout(self)
self.setLayout(layout)
data_group_box = QGroupBox('', self)
layout.addWidget(data_group_box)
data_group_box_layout = QVBoxLayout()
data_group_box.setLayout(data_group_box_layout)
data_group_box_layout.addWidget(QLabel('Key Name:', self))
self.key_ledit = QLineEdit(self.parent.listy.currentItem().text(), self)
self.key_ledit.setToolTip(_('<p>Enter a new name for this existing Ignoble key.'))
data_group_box_layout.addWidget(self.key_ledit)
layout.addSpacing(20)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.button_box.accepted.connect(self.accept)
self.button_box.rejected.connect(self.reject)
layout.addWidget(self.button_box)
def accept(self):
if self.key_ledit.text().isEmpty() or unicode(self.key_ledit.text()).isspace():
errmsg = '<p>Key name field cannot be empty!'
return error_dialog(None, PLUGIN_NAME,
_(errmsg), show=True, show_copy_button=False)
if len(self.key_ledit.text()) < 4:
errmsg = '<p>Key name must be at <i>least</i> 4 characters long!'
return error_dialog(None, PLUGIN_NAME,
_(errmsg), show=True, show_copy_button=False)
if uStrCmp(self.key_ledit.text(), self.parent.listy.currentItem().text()):
# Same exact name ... do nothing.
return QDialog.reject(self)
for k in self.parent.plugin_keys.keys():
if (uStrCmp(self.key_ledit.text(), k, True) and
not uStrCmp(k, self.parent.listy.currentItem().text(), True)):
errmsg = '<p>The key name <strong>%s</strong> is already being used.' % self.key_ledit.text()
return error_dialog(None, PLUGIN_NAME,
_(errmsg), show=True, show_copy_button=False)
QDialog.accept(self)
@property
def key_name(self):
return unicode(self.key_ledit.text().toUtf8(), 'utf8')

View File

@@ -0,0 +1,420 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
# ignobleepub.pyw, version 3.6
# Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102012 by some_updates, DiapDealer and Apprentice Alf
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
# install the version for Python 2.6). Save this script file as
# ineptepub.pyw and double-click on it to run it.
#
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
# program from the command line (pythonw ineptepub.pyw) or by double-clicking
# it when it has been associated with PythonLauncher.
# Revision history:
# 1 - Initial release
# 2 - Added OS X support by using OpenSSL when available
# 3 - screen out improper key lengths to prevent segfaults on Linux
# 3.1 - Allow Windows versions of libcrypto to be found
# 3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml
# 3.3 - On Windows try PyCrypto first and OpenSSL next
# 3.4 - Modify interace to allow use with import
# 3.5 - Fix for potential problem with PyCrypto
# 3.6 - Revised to allow use in calibre plugins to eliminate need for duplicate code
"""
Decrypt Barnes & Noble encrypted ePub books.
"""
__license__ = 'GPL v3'
__version__ = "3.6"
import sys
import os
import traceback
import zlib
import zipfile
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
from contextlib import closing
import xml.etree.ElementTree as etree
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
try:
from calibre.constants import iswindows, isosx
except:
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
return [u"ineptepub.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
class IGNOBLEError(Exception):
pass
def _load_crypto_libcrypto():
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
Structure, c_ulong, create_string_buffer, cast
from ctypes.util import find_library
if iswindows:
libcrypto = find_library('libeay32')
else:
libcrypto = find_library('crypto')
if libcrypto is None:
raise IGNOBLEError('libcrypto not found')
libcrypto = CDLL(libcrypto)
AES_MAXNR = 14
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
def F(restype, name, argtypes):
func = getattr(libcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
[c_char_p, c_int, AES_KEY_p])
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
c_int])
class AES(object):
def __init__(self, userkey):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise IGNOBLEError('AES improper key used')
return
key = self._key = AES_KEY()
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
if rv < 0:
raise IGNOBLEError('Failed to initialize AES key')
def decrypt(self, data):
out = create_string_buffer(len(data))
iv = ("\x00" * self._blocksize)
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
if rv == 0:
raise IGNOBLEError('AES decryption failed')
return out.raw
return AES
def _load_crypto_pycrypto():
from Crypto.Cipher import AES as _AES
class AES(object):
def __init__(self, key):
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
def decrypt(self, data):
return self._aes.decrypt(data)
return AES
def _load_crypto():
AES = None
cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
if sys.platform.startswith('win'):
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
for loader in cryptolist:
try:
AES = loader()
break
except (ImportError, IGNOBLEError):
pass
return AES
AES = _load_crypto()
META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
class ZipInfo(zipfile.ZipInfo):
def __init__(self, *args, **kwargs):
if 'compress_type' in kwargs:
compress_type = kwargs.pop('compress_type')
super(ZipInfo, self).__init__(*args, **kwargs)
self.compress_type = compress_type
class Decryptor(object):
def __init__(self, bookkey, encryption):
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
self._aes = AES(bookkey)
encryption = etree.fromstring(encryption)
self._encrypted = encrypted = set()
expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
enc('CipherReference'))
for elem in encryption.findall(expr):
path = elem.get('URI', None)
if path is not None:
path = path.encode('utf-8')
encrypted.add(path)
def decompress(self, bytes):
dc = zlib.decompressobj(-15)
bytes = dc.decompress(bytes)
ex = dc.decompress('Z') + dc.flush()
if ex:
bytes = bytes + ex
return bytes
def decrypt(self, path, data):
if path in self._encrypted:
data = self._aes.decrypt(data)[16:]
data = data[:-ord(data[-1])]
data = self.decompress(data)
return data
# check file to make check whether it's probably an Adobe Adept encrypted ePub
def ignobleBook(inpath):
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist:
return False
try:
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
if len(bookkey) == 64:
return True
except:
# if we couldn't check, assume it is
return True
return False
# return error code and error message duple
def decryptBook(keyb64, inpath, outpath):
if AES is None:
# 1 means don't try again
return (1, u"PyCrypto or OpenSSL must be installed.")
key = keyb64.decode('base64')[:16]
aes = AES(key)
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist:
return (1, u"Not a secure Barnes & Noble ePub.")
for name in META_NAMES:
namelist.remove(name)
try:
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
if len(bookkey) != 64:
return (1, u"Not a secure Barnes & Noble ePub.")
bookkey = aes.decrypt(bookkey.decode('base64'))
bookkey = bookkey[:-ord(bookkey[-1])]
encryption = inf.read('META-INF/encryption.xml')
decryptor = Decryptor(bookkey[-16:], encryption)
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
outf.writestr(zi, inf.read('mimetype'))
for path in namelist:
data = inf.read(path)
outf.writestr(path, decryptor.decrypt(path, data))
except Exception, e:
return (2, u"{0}.".format(e.args[0]))
return (0, u"Success")
def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
if len(argv) != 4:
print u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname)
return 1
keypath, inpath, outpath = argv[1:]
userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath)
print result[1]
return result[0]
def gui_main():
import Tkinter
import Tkconstants
import tkFileDialog
import traceback
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Select files for decryption")
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Key file").grid(row=0)
self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists(u"bnepubkey.b64"):
self.keypath.insert(0, u"bnepubkey.b64")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
button.grid(row=0, column=2)
Tkinter.Label(body, text=u"Input file").grid(row=1)
self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
button.grid(row=1, column=2)
Tkinter.Label(body, text=u"Output file").grid(row=2)
self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
button.grid(row=2, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
buttons, text=u"Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select Barnes & Noble \'.b64\' key file",
defaultextension=u".b64",
filetypes=[('base64-encoded files', '.b64'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
def get_inpath(self):
inpath = tkFileDialog.askopenfilename(
parent=None, title=u"Select B&N-encrypted ePub file to decrypt",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
if inpath:
inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END)
self.inpath.insert(0, inpath)
return
def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select unencrypted ePub file to produce",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return
def decrypt(self):
keypath = self.keypath.get()
inpath = self.inpath.get()
outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath):
self.status['text'] = u"Specified key file does not exist"
return
if not inpath or not os.path.exists(inpath):
self.status['text'] = u"Specified input file does not exist"
return
if not outpath:
self.status['text'] = u"Output file not specified"
return
if inpath == outpath:
self.status['text'] = u"Must have different input and output files"
return
userkey = open(keypath,'rb').read()
self.status['text'] = u"Decrypting..."
try:
decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception, e:
self.status['text'] = u"Error: {0}".format(e.args[0])
return
if decrypt_status[0] == 0:
self.status['text'] = u"File successfully decrypted"
else:
self.status['text'] = decrypt_status[1]
root = Tkinter.Tk()
root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__))
root.resizable(True, False)
root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
root.mainloop()
return 0
if __name__ == '__main__':
if len(sys.argv) > 1:
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(gui_main())

View File

@@ -1,382 +0,0 @@
#!/usr/bin/env python
# ignobleepub_plugin.py
# Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/>
#
# Requires Calibre version 0.7.55 or higher.
#
# All credit given to I <3 Cabbages for the original standalone scripts.
# I had the much easier job of converting them to Calibre a 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.
#
# 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. 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 the info with
# a colon: Your Name,1234123412341234:Other Name,2345234523452345
#
# ** Method 1 is your only option if you don't have/can't run the original
# I <3 Cabbages scripts on your particular machine. **
#
# 2) If you already have keyfiles generated with I <3 Cabbages' ignoblekeygen.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 '.b64' extension (like the ignoblekeygen
# script produces). This directory isn't touched when upgrading Calibre, so it's quite safe
# to leave then there.
#
# All keyfiles from option 2 and all data entered from option 1 will be used to attempt
# to decrypt a book. You can use option 1 or option 2, or a combination of both.
#
#
# Revision history:
# 0.1.0 - Initial release
# 0.1.1 - Allow Windows users to make use of openssl if they have it installed.
# - Incorporated SomeUpdates zipfix routine.
# 0.1.2 - bug fix for non-ascii file names in encryption.xml
# 0.1.3 - Try PyCrypto on Windows first
# 0.1.4 - update zipfix to deal with mimetype not in correct place
# 0.1.5 - update zipfix to deal with completely missing mimetype files
# 0.1.6 - update ot the new calibre plugin interface
"""
Decrypt Barnes & Noble ADEPT encrypted EPUB books.
"""
from __future__ import with_statement
__license__ = 'GPL v3'
import sys
import os
import hashlib
import zlib
import zipfile
import re
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
import xml.etree.ElementTree as etree
from contextlib import closing
global AES
global AES2
META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
class IGNOBLEError(Exception):
pass
def _load_crypto_libcrypto():
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
Structure, c_ulong, create_string_buffer, cast
from ctypes.util import find_library
if sys.platform.startswith('win'):
libcrypto = find_library('libeay32')
else:
libcrypto = find_library('crypto')
if libcrypto is None:
raise IGNOBLEError('libcrypto not found')
libcrypto = CDLL(libcrypto)
AES_MAXNR = 14
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
def F(restype, name, argtypes):
func = getattr(libcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
AES_set_encrypt_key = F(c_int, 'AES_set_encrypt_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])
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
c_int])
class AES(object):
def __init__(self, userkey):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise IGNOBLEError('AES improper key used')
return
key = self._key = AES_KEY()
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
if rv < 0:
raise IGNOBLEError('Failed to initialize AES key')
def decrypt(self, data):
out = create_string_buffer(len(data))
iv = ("\x00" * self._blocksize)
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
if rv == 0:
raise IGNOBLEError('AES decryption failed')
return out.raw
class AES2(object):
def __init__(self, userkey, iv):
self._blocksize = len(userkey)
self._iv = iv
key = self._key = AES_KEY()
rv = AES_set_encrypt_key(userkey, len(userkey) * 8, key)
if rv < 0:
raise IGNOBLEError('Failed to initialize AES Encrypt key')
def encrypt(self, data):
out = create_string_buffer(len(data))
rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1)
if rv == 0:
raise IGNOBLEError('AES encryption failed')
return out.raw
print 'IgnobleEpub: Using libcrypto.'
return (AES, AES2)
def _load_crypto_pycrypto():
from Crypto.Cipher import AES as _AES
class AES(object):
def __init__(self, key):
self._aes = _AES.new(key, _AES.MODE_CBC)
def decrypt(self, data):
return self._aes.decrypt(data)
class AES2(object):
def __init__(self, key, iv):
self._aes = _AES.new(key, _AES.MODE_CBC, iv)
def encrypt(self, data):
return self._aes.encrypt(data)
print 'IgnobleEpub: Using PyCrypto.'
return (AES, AES2)
def _load_crypto():
_aes = _aes2 = None
cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
if sys.platform.startswith('win'):
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
for loader in cryptolist:
try:
_aes, _aes2 = loader()
break
except (ImportError, IGNOBLEError):
pass
return (_aes, _aes2)
def normalize_name(name): # Strip spaces and convert to lowercase.
return ''.join(x for x in name.lower() if x != ' ')
def generate_keyfile(name, ccn):
name = normalize_name(name) + '\x00'
ccn = ccn + '\x00'
name_sha = hashlib.sha1(name).digest()[:16]
ccn_sha = hashlib.sha1(ccn).digest()[:16]
both_sha = hashlib.sha1(name + ccn).digest()
aes = AES2(ccn_sha, name_sha)
crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c))
userkey = hashlib.sha1(crypt).digest()
return userkey.encode('base64')
class ZipInfo(zipfile.ZipInfo):
def __init__(self, *args, **kwargs):
if 'compress_type' in kwargs:
compress_type = kwargs.pop('compress_type')
super(ZipInfo, self).__init__(*args, **kwargs)
self.compress_type = compress_type
class Decryptor(object):
def __init__(self, bookkey, encryption):
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
self._aes = AES(bookkey)
encryption = etree.fromstring(encryption)
self._encrypted = encrypted = set()
expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
enc('CipherReference'))
for elem in encryption.findall(expr):
path = elem.get('URI', None)
path = path.encode('utf-8')
if path is not None:
encrypted.add(path)
def decompress(self, bytes):
dc = zlib.decompressobj(-15)
bytes = dc.decompress(bytes)
ex = dc.decompress('Z') + dc.flush()
if ex:
bytes = bytes + ex
return bytes
def decrypt(self, path, data):
if path in self._encrypted:
data = self._aes.decrypt(data)[16:]
data = data[:-ord(data[-1])]
data = self.decompress(data)
return data
def plugin_main(userkey, inpath, outpath):
key = userkey.decode('base64')[:16]
aes = AES(key)
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist:
return 1
for name in META_NAMES:
namelist.remove(name)
try: # If the generated keyfile doesn't match the bookkey, this is where it's likely to blow up.
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
bookkey = aes.decrypt(bookkey.decode('base64'))
bookkey = bookkey[:-ord(bookkey[-1])]
encryption = inf.read('META-INF/encryption.xml')
decryptor = Decryptor(bookkey[-16:], encryption)
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
outf.writestr(zi, inf.read('mimetype'))
for path in namelist:
data = inf.read(path)
outf.writestr(path, decryptor.decrypt(path, data))
except:
return 2
return 0
from calibre.customize import FileTypePlugin
from calibre.constants import iswindows, isosx
class IgnobleDeDRM(FileTypePlugin):
name = 'Ignoble Epub DeDRM'
description = 'Removes DRM from secure Barnes & Noble epub files. \
Credit given to I <3 Cabbages for the original stand-alone scripts.'
supported_platforms = ['linux', 'osx', 'windows']
author = 'DiapDealer'
version = (0, 1, 6)
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
file_types = set(['epub'])
on_import = True
def run(self, path_to_ebook):
global AES
global AES2
AES, AES2 = _load_crypto()
if AES == None or AES2 == None:
# Failed to load libcrypto or PyCrypto... Adobe Epubs can't be decrypted.'
raise IGNOBLEError('IgnobleEpub - Failed to load crypto libs.')
return
# Load any keyfiles (*.b64) included Calibre's config directory.
userkeys = []
try:
# Find Calibre's configuration directory.
confpath = os.path.split(os.path.split(self.plugin_path)[0])[0]
print 'IgnobleEpub: Calibre configuration directory = %s' % confpath
files = os.listdir(confpath)
filefilter = re.compile("\.b64$", re.IGNORECASE)
files = filter(filefilter.search, files)
if files:
for filename in files:
fpath = os.path.join(confpath, filename)
with open(fpath, 'rb') as f:
userkeys.append(f.read())
print 'IgnobleEpub: Keyfile %s found in config folder.' % filename
else:
print 'IgnobleEpub: No keyfiles found. Checking plugin customization string.'
except IOError:
print 'IgnobleEpub: Error reading keyfiles from config directory.'
pass
# Get name and credit card number from Plugin Customization
if not userkeys and not self.site_customization:
# Plugin hasn't been configured... do nothing.
raise IGNOBLEError('IgnobleEpub - No keys found. Plugin not configured.')
return
if self.site_customization:
keystuff = self.site_customization
ar = keystuff.split(':')
keycount = 0
for i in ar:
try:
name, ccn = i.split(',')
keycount += 1
except ValueError:
raise IGNOBLEError('IgnobleEpub - Error parsing user supplied data.')
return
# Generate Barnes & Noble EPUB user key from name and credit card number.
userkeys.append( generate_keyfile(name, ccn) )
print 'IgnobleEpub: %d userkey(s) generated from customization data.' % keycount
# Attempt to decrypt epub with each encryption key (generated or provided).
for userkey in userkeys:
# Create a TemporaryPersistent file to work with.
# Check original epub archive for zip errors.
from calibre_plugins.ignobleepub import zipfix
inf = self.temporary_file('.epub')
try:
fr = zipfix.fixZip(path_to_ebook, inf.name)
fr.fix()
except Exception, e:
raise Exception(e)
return
of = self.temporary_file('.epub')
# Give the user key, ebook and TemporaryPersistent file to the Stripper function.
result = plugin_main(userkey, inf.name, of.name)
# Ebook is not a B&N Adept epub... do nothing and pass it on.
# This allows a non-encrypted epub to be imported without error messages.
if result == 1:
print 'IgnobleEpub: Not a B&N Adept Epub... punting.'
of.close()
return path_to_ebook
break
# Decryption was successful return the modified PersistentTemporary
# file to Calibre's import process.
if result == 0:
print 'IgnobleEpub: Encryption successfully removed.'
of.close()
return of.name
break
print 'IgnobleEpub: Encryption key invalid... trying others.'
of.close()
# Something went wrong with decryption.
# Import the original unmolested epub.
of.close
raise IGNOBLEError('IgnobleEpub - Ultimately failed to decrypt.')
return
def customization_help(self, gui=False):
return 'Enter B&N Account name and CC# (separate name and CC# with a comma)'

View File

@@ -0,0 +1,319 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
# ignoblekeygen.pyw, version 2.5
# Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102012 by some_updates, DiapDealer and Apprentice Alf
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
# install the version for Python 2.6). Save this script file as
# ignoblekeygen.pyw and double-click on it to run it.
#
# Mac OS X users: Save this script file as ignoblekeygen.pyw. You can run this
# program from the command line (pythonw ignoblekeygen.pyw) or by double-clicking
# it when it has been associated with PythonLauncher.
# Revision history:
# 1 - Initial release
# 2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
# 2.1 - Allow Windows versions of libcrypto to be found
# 2.2 - On Windows try PyCrypto first and then OpenSSL next
# 2.3 - Modify interface to allow use of import
# 2.4 - Improvements to UI and now works in plugins
# 2.5 - Additional improvement for unicode and plugin support
"""
Generate Barnes & Noble EPUB user key from name and credit card number.
"""
__license__ = 'GPL v3'
__version__ = "2.5"
import sys
import os
import hashlib
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"ignoblekeygen.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
class IGNOBLEError(Exception):
pass
def _load_crypto_libcrypto():
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
Structure, c_ulong, create_string_buffer, cast
from ctypes.util import find_library
if iswindows:
libcrypto = find_library('libeay32')
else:
libcrypto = find_library('crypto')
if libcrypto is None:
raise IGNOBLEError('libcrypto not found')
libcrypto = CDLL(libcrypto)
AES_MAXNR = 14
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
def F(restype, name, argtypes):
func = getattr(libcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
AES_set_encrypt_key = F(c_int, 'AES_set_encrypt_key',
[c_char_p, c_int, AES_KEY_p])
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
c_int])
class AES(object):
def __init__(self, userkey, iv):
self._blocksize = len(userkey)
self._iv = iv
key = self._key = AES_KEY()
rv = AES_set_encrypt_key(userkey, len(userkey) * 8, key)
if rv < 0:
raise IGNOBLEError('Failed to initialize AES Encrypt key')
def encrypt(self, data):
out = create_string_buffer(len(data))
rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1)
if rv == 0:
raise IGNOBLEError('AES encryption failed')
return out.raw
return AES
def _load_crypto_pycrypto():
from Crypto.Cipher import AES as _AES
class AES(object):
def __init__(self, key, iv):
self._aes = _AES.new(key, _AES.MODE_CBC, iv)
def encrypt(self, data):
return self._aes.encrypt(data)
return AES
def _load_crypto():
AES = None
cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
if sys.platform.startswith('win'):
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
for loader in cryptolist:
try:
AES = loader()
break
except (ImportError, IGNOBLEError):
pass
return AES
AES = _load_crypto()
def normalize_name(name):
return ''.join(x for x in name.lower() if x != ' ')
def generate_key(name, ccn):
# remove spaces and case from name and CC numbers.
if type(name)==unicode:
name = name.encode('utf-8')
if type(ccn)==unicode:
ccn = ccn.encode('utf-8')
name = normalize_name(name) + '\x00'
ccn = normalize_name(ccn) + '\x00'
name_sha = hashlib.sha1(name).digest()[:16]
ccn_sha = hashlib.sha1(ccn).digest()[:16]
both_sha = hashlib.sha1(name + ccn).digest()
aes = AES(ccn_sha, name_sha)
crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c))
userkey = hashlib.sha1(crypt).digest()
return userkey.encode('base64')
def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
if AES is None:
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
"separately. Read the top-of-script comment for details." % \
(progname,)
return 1
if len(argv) != 4:
print u"usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname)
return 1
name, ccn, keypath = argv[1:]
userkey = generate_key(name, ccn)
open(keypath,'wb').write(userkey)
return 0
def gui_main():
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Enter parameters")
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Account Name").grid(row=0)
self.name = Tkinter.Entry(body, width=40)
self.name.grid(row=0, column=1, sticky=sticky)
Tkinter.Label(body, text=u"CC#").grid(row=1)
self.ccn = Tkinter.Entry(body, width=40)
self.ccn.grid(row=1, column=1, sticky=sticky)
Tkinter.Label(body, text=u"Output file").grid(row=2)
self.keypath = Tkinter.Entry(body, width=40)
self.keypath.grid(row=2, column=1, sticky=sticky)
self.keypath.insert(2, u"bnepubkey.b64")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
button.grid(row=2, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
buttons, text=u"Generate", width=10, command=self.generate)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select B&N ePub key file to produce",
defaultextension=u".b64",
filetypes=[('base64-encoded files', '.b64'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
def generate(self):
name = self.name.get()
ccn = self.ccn.get()
keypath = self.keypath.get()
if not name:
self.status['text'] = u"Name not specified"
return
if not ccn:
self.status['text'] = u"Credit card number not specified"
return
if not keypath:
self.status['text'] = u"Output keyfile path not specified"
return
self.status['text'] = u"Generating..."
try:
userkey = generate_key(name, ccn)
except Exception, e:
self.status['text'] = u"Error: (0}".format(e.args[0])
return
open(keypath,'wb').write(userkey)
self.status['text'] = u"Keyfile successfully generated"
root = Tkinter.Tk()
if AES is None:
root.withdraw()
tkMessageBox.showerror(
"Ignoble EPUB Keyfile Generator",
"This script requires OpenSSL or PyCrypto, which must be installed "
"separately. Read the top-of-script comment for details.")
return 1
root.title(u"Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__))
root.resizable(True, False)
root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
root.mainloop()
return 0
if __name__ == '__main__':
if len(sys.argv) > 1:
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(gui_main())

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
__license__ = 'GPL v3'
DETAILED_MESSAGE = \
'You have personal information stored in this plugin\'s customization '+ \
'string from a previous version of this plugin.\n\n'+ \
'This new version of the plugin can convert that info '+ \
'into key data that the new plugin can then use (which doesn\'t '+ \
'require personal information to be stored/displayed in an insecure '+ \
'manner like the old plugin did).\n\nIf you choose NOT to migrate this data at this time '+ \
'you will be prompted to save that personal data to a file elsewhere; and you\'ll have '+ \
'to manually re-configure this plugin with your information.\n\nEither way... ' + \
'this new version of the plugin will not be responsible for storing that personal '+ \
'info in plain sight any longer.'
def uStrCmp (s1, s2, caseless=False):
import unicodedata as ud
str1 = s1 if isinstance(s1, unicode) else unicode(s1)
str2 = s2 if isinstance(s2, unicode) else unicode(s2)
if caseless:
return ud.normalize('NFC', str1.lower()) == ud.normalize('NFC', str2.lower())
else:
return ud.normalize('NFC', str1) == ud.normalize('NFC', str2)
def parseCustString(keystuff):
userkeys = []
ar = keystuff.split(':')
for i in ar:
try:
name, ccn = i.split(',')
# Generate Barnes & Noble EPUB user key from name and credit card number.
userkeys.append(generate_key(name, ccn))
except:
pass
return userkeys

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import zlib
import zipfile
import zipfilerugged
import os
import os.path
import getopt
@@ -15,7 +16,7 @@ _FILENAME_OFFSET = 30
_MAX_SIZE = 64 * 1024
_MIMETYPE = 'application/epub+zip'
class ZipInfo(zipfile.ZipInfo):
class ZipInfo(zipfilerugged.ZipInfo):
def __init__(self, *args, **kwargs):
if 'compress_type' in kwargs:
compress_type = kwargs.pop('compress_type')
@@ -27,10 +28,10 @@ class fixZip:
self.ztype = 'zip'
if zinput.lower().find('.epub') >= 0 :
self.ztype = 'epub'
self.inzip = zipfile.ZipFile(zinput,'r')
self.outzip = zipfile.ZipFile(zoutput,'w')
self.inzip = zipfilerugged.ZipFile(zinput,'r')
self.outzip = zipfilerugged.ZipFile(zoutput,'w')
# 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):
local_header_offset = zi.header_offset
@@ -76,11 +77,11 @@ class fixZip:
data = None
# if not compressed we are good to go
if zi.compress_type == zipfile.ZIP_STORED:
if zi.compress_type == zipfilerugged.ZIP_STORED:
data = self.bzf.read(zi.file_size)
# if compressed we must decompress it using zlib
if zi.compress_type == zipfile.ZIP_DEFLATED:
if zi.compress_type == zipfilerugged.ZIP_DEFLATED:
cmpdata = self.bzf.read(zi.compress_size)
data = self.uncompress(cmpdata)
@@ -95,7 +96,7 @@ class fixZip:
# if epub write mimetype file first, with no compression
if self.ztype == 'epub':
nzinfo = ZipInfo('mimetype', compress_type=zipfile.ZIP_STORED)
nzinfo = ZipInfo('mimetype', compress_type=zipfilerugged.ZIP_STORED)
self.outzip.writestr(nzinfo, _MIMETYPE)
# write the rest of the files
@@ -105,7 +106,7 @@ class fixZip:
nzinfo = zinfo
try:
data = self.inzip.read(zinfo.filename)
except zipfile.BadZipfile or zipfile.error:
except zipfilerugged.BadZipfile or zipfilerugged.error:
local_name = self.getlocalname(zinfo)
data = self.getfiledata(zinfo)
nzinfo.filename = local_name
@@ -152,5 +153,3 @@ def main(argv=sys.argv):
if __name__ == '__main__' :
sys.exit(main())

Binary file not shown.

View File

@@ -1,32 +1,37 @@
#! /usr/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
__license__ = 'GPL v3'
__docformat__ = 'restructuredtext en'
# ineptepub_plugin.py
# Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/>
#
# Requires Calibre version 0.7.55 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.
# 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 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
# 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.
#
# 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
# find the ADE installation. So if you have ADE installed on the same machine as Calibre...
# you are ready to go.
# (on Windows and Mac OS's). If successful, it will create one or more
# 'calibre-adeptkey<n>.der' files and save them in calibre's configuration directory.
# It will use those files on subsequent runs. If there is already a 'calibre-adeptkey*.der'
# file in the directory, the plugin won't attempt to find the ADE installation.
# So if you have ADE installed on the same machine as calibre you are ready to go.
#
# If you already have keyfiles generated with I <3 Cabbages' ineptkey.pyw script,
# If you already have keyfiles generated with i♥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
# configuration directory' button. Copy 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.
@@ -50,410 +55,142 @@
# 0.1.5 - update zipfix to handle out of position mimetypes
# 0.1.6 - update zipfix to handle completely missing mimetype files
# 0.1.7 - update to new calibre plugin interface
# 0.1.8 - Fix for potential problem with PyCrypto
# 0.1.9 - Fix for potential problem with ADE keys and fix possible output/unicode problem
# 0.2.0 - Major code change to use unaltered ineptepub.py file 5.8 or later.
"""
Decrypt Adobe ADEPT-encrypted EPUB books.
"""
from __future__ import with_statement
__license__ = 'GPL v3'
import sys
import os
import zlib
import zipfile
import re
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
from contextlib import closing
import xml.etree.ElementTree as etree
global AES
global RSA
META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
PLUGIN_NAME = u"Inept Epub DeDRM"
PLUGIN_VERSION_TUPLE = (0, 2, 0)
PLUGIN_VERSION = u'.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
import sys, os, re
class ADEPTError(Exception):
pass
def _load_crypto_libcrypto():
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
Structure, c_ulong, create_string_buffer, cast
from ctypes.util import find_library
if sys.platform.startswith('win'):
libcrypto = find_library('libeay32')
else:
libcrypto = find_library('crypto')
if libcrypto is None:
raise ADEPTError('libcrypto not found')
libcrypto = CDLL(libcrypto)
RSA_NO_PADDING = 3
AES_MAXNR = 14
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
class RSA(Structure):
pass
RSA_p = POINTER(RSA)
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
def F(restype, name, argtypes):
func = getattr(libcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey',
[RSA_p, c_char_pp, c_long])
RSA_size = F(c_int, 'RSA_size', [RSA_p])
RSA_private_decrypt = F(c_int, 'RSA_private_decrypt',
[c_int, c_char_p, c_char_p, RSA_p, c_int])
RSA_free = F(None, 'RSA_free', [RSA_p])
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
[c_char_p, c_int, AES_KEY_p])
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
c_int])
class RSA(object):
def __init__(self, der):
buf = create_string_buffer(der)
pp = c_char_pp(cast(buf, c_char_p))
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
if rsa is None:
raise ADEPTError('Error parsing ADEPT user key DER')
def decrypt(self, from_):
rsa = self._rsa
to = create_string_buffer(RSA_size(rsa))
dlen = RSA_private_decrypt(len(from_), from_, to, rsa,
RSA_NO_PADDING)
if dlen < 0:
raise ADEPTError('RSA decryption failed')
return to[:dlen]
def __del__(self):
if self._rsa is not None:
RSA_free(self._rsa)
self._rsa = None
class AES(object):
def __init__(self, userkey):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise ADEPTError('AES improper key used')
return
key = self._key = AES_KEY()
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
if rv < 0:
raise ADEPTError('Failed to initialize AES key')
def decrypt(self, data):
out = create_string_buffer(len(data))
iv = ("\x00" * self._blocksize)
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
if rv == 0:
raise ADEPTError('AES decryption failed')
return out.raw
print 'IneptEpub: Using libcrypto.'
return (AES, RSA)
def _load_crypto_pycrypto():
from Crypto.Cipher import AES as _AES
from Crypto.PublicKey import RSA as _RSA
# ASN.1 parsing code from tlslite
class ASN1Error(Exception):
pass
class ASN1Parser(object):
class Parser(object):
def __init__(self, bytes):
self.bytes = bytes
self.index = 0
def get(self, length):
if self.index + length > len(self.bytes):
raise ASN1Error("Error decoding ASN.1")
x = 0
for count in range(length):
x <<= 8
x |= self.bytes[self.index]
self.index += 1
return x
def getFixBytes(self, lengthBytes):
bytes = self.bytes[self.index : self.index+lengthBytes]
self.index += lengthBytes
return bytes
def getVarBytes(self, lengthLength):
lengthBytes = self.get(lengthLength)
return self.getFixBytes(lengthBytes)
def getFixList(self, length, lengthList):
l = [0] * lengthList
for x in range(lengthList):
l[x] = self.get(length)
return l
def getVarList(self, length, lengthLength):
lengthList = self.get(lengthLength)
if lengthList % length != 0:
raise ASN1Error("Error decoding ASN.1")
lengthList = int(lengthList/length)
l = [0] * lengthList
for x in range(lengthList):
l[x] = self.get(length)
return l
def startLengthCheck(self, lengthLength):
self.lengthCheck = self.get(lengthLength)
self.indexCheck = self.index
def setLengthCheck(self, length):
self.lengthCheck = length
self.indexCheck = self.index
def stopLengthCheck(self):
if (self.index - self.indexCheck) != self.lengthCheck:
raise ASN1Error("Error decoding ASN.1")
def atLengthCheck(self):
if (self.index - self.indexCheck) < self.lengthCheck:
return False
elif (self.index - self.indexCheck) == self.lengthCheck:
return True
else:
raise ASN1Error("Error decoding ASN.1")
def __init__(self, bytes):
p = self.Parser(bytes)
p.get(1)
self.length = self._getASN1Length(p)
self.value = p.getFixBytes(self.length)
def getChild(self, which):
p = self.Parser(self.value)
for x in range(which+1):
markIndex = p.index
p.get(1)
length = self._getASN1Length(p)
p.getFixBytes(length)
return ASN1Parser(p.bytes[markIndex:p.index])
def _getASN1Length(self, p):
firstLength = p.get(1)
if firstLength<=127:
return firstLength
else:
lengthLength = firstLength & 0x7F
return p.get(lengthLength)
class AES(object):
def __init__(self, key):
self._aes = _AES.new(key, _AES.MODE_CBC)
def decrypt(self, data):
return self._aes.decrypt(data)
class RSA(object):
def __init__(self, der):
key = ASN1Parser([ord(x) for x in der])
key = [key.getChild(x).value for x in xrange(1, 4)]
key = [self.bytesToNumber(v) for v in key]
self._rsa = _RSA.construct(key)
def bytesToNumber(self, bytes):
total = 0L
for byte in bytes:
total = (total << 8) + byte
return total
def decrypt(self, data):
return self._rsa.decrypt(data)
print 'IneptEpub: Using pycrypto.'
return (AES, RSA)
def _load_crypto():
_aes = _rsa = None
cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
if sys.platform.startswith('win'):
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
for loader in cryptolist:
try:
_aes, _rsa = loader()
break
except (ImportError, ADEPTError):
pass
return (_aes, _rsa)
class ZipInfo(zipfile.ZipInfo):
def __init__(self, *args, **kwargs):
if 'compress_type' in kwargs:
compress_type = kwargs.pop('compress_type')
super(ZipInfo, self).__init__(*args, **kwargs)
self.compress_type = compress_type
class Decryptor(object):
def __init__(self, bookkey, encryption):
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
self._aes = AES(bookkey)
encryption = etree.fromstring(encryption)
self._encrypted = encrypted = set()
expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
enc('CipherReference'))
for elem in encryption.findall(expr):
path = elem.get('URI', None)
path = path.encode('utf-8')
if path is not None:
encrypted.add(path)
def decompress(self, bytes):
dc = zlib.decompressobj(-15)
bytes = dc.decompress(bytes)
ex = dc.decompress('Z') + dc.flush()
if ex:
bytes = bytes + ex
return bytes
def decrypt(self, path, data):
if path in self._encrypted:
data = self._aes.decrypt(data)[16:]
data = data[:-ord(data[-1])]
data = self.decompress(data)
return data
def plugin_main(userkey, inpath, outpath):
rsa = RSA(userkey)
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist:
return 1
for name in META_NAMES:
namelist.remove(name)
try:
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
bookkey = rsa.decrypt(bookkey.decode('base64'))
# Padded as per RSAES-PKCS1-v1_5
if bookkey[-17] != '\x00':
raise ADEPTError('problem decrypting session key')
encryption = inf.read('META-INF/encryption.xml')
decryptor = Decryptor(bookkey[-16:], encryption)
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
outf.writestr(zi, inf.read('mimetype'))
for path in namelist:
data = inf.read(path)
outf.writestr(path, decryptor.decrypt(path, data))
except:
return 2
return 0
from calibre.customize import FileTypePlugin
from calibre.constants import iswindows, isosx
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
class IneptDeDRM(FileTypePlugin):
name = 'Inept Epub DeDRM'
description = 'Removes DRM from secure Adobe epub files. \
Credit given to I <3 Cabbages for the original stand-alone scripts.'
name = PLUGIN_NAME
description = u"Removes DRM from secure Adobe epub files. Credit given to i♥cabbages for the original stand-alone scripts."
supported_platforms = ['linux', 'osx', 'windows']
author = 'DiapDealer'
version = (0, 1, 7)
author = u"DiapDealer, Apprentice Alf and i♥cabbages"
version = PLUGIN_VERSION_TUPLE
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
file_types = set(['epub'])
on_import = True
priority = 100
def run(self, path_to_ebook):
global AES
global RSA
AES, RSA = _load_crypto()
# make sure any unicode output gets converted safely with 'replace'
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
if AES == None or RSA == None:
# Failed to load libcrypto or PyCrypto... Adobe Epubs can\'t be decrypted.'
raise ADEPTError('IneptEpub: Failed to load crypto libs... Adobe Epubs can\'t be decrypted.')
print u"{0} v{1}: Trying to decrypt {2}.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
# Create a TemporaryPersistent file to work with.
# Check original epub archive for zip errors.
from calibre_plugins.ineptepub import zipfix
inf = self.temporary_file(u".epub")
try:
print u"{0} v{1}: Verifying zip archive integrity.".format(PLUGIN_NAME, PLUGIN_VERSION)
fr = zipfix.fixZip(path_to_ebook, inf.name)
fr.fix()
except Exception, e:
print u"{0} v{1}: Error when checking zip archive.".format(PLUGIN_NAME, PLUGIN_VERSION)
raise Exception(e)
return
#check the book
from calibre_plugins.ineptepub import ineptepub
if not ineptepub.adeptBook(inf.name):
print u"{0} v{1}: {2} is not a secure Adobe Adept ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
# return the original file, so that no error message is generated in the GUI
return path_to_ebook
# Load any keyfiles (*.der) included Calibre's config directory.
userkeys = []
# Find Calibre's configuration directory.
# self.plugin_path is passed in unicode because we defined our name in unicode
confpath = os.path.split(os.path.split(self.plugin_path)[0])[0]
print 'IneptEpub: Calibre configuration directory = %s' % confpath
print u"{0} v{1}: Calibre configuration directory = {2}".format(PLUGIN_NAME, PLUGIN_VERSION, confpath)
files = os.listdir(confpath)
filefilter = re.compile("\.der$", re.IGNORECASE)
filefilter = re.compile(u"\.der$", re.IGNORECASE)
files = filter(filefilter.search, files)
foundDefault = False
if files:
try:
for filename in files:
if filename[:16] == u"calibre-adeptkey":
foundDefault = True
fpath = os.path.join(confpath, filename)
with open(fpath, 'rb') as f:
userkeys.append(f.read())
print 'IneptEpub: Keyfile %s found in config folder.' % filename
userkeys.append([f.read(), filename])
print u"{0} v{1}: Keyfile {2} found in config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, filename)
except IOError:
print 'IneptEpub: Error reading keyfiles from config directory.'
print u"{0} v{1}: Error reading keyfiles from config directory.".format(PLUGIN_NAME, PLUGIN_VERSION)
pass
else:
if not foundDefault:
# Try to find key from ADE install and save the key in
# Calibre's configuration directory for future use.
if iswindows or isosx:
#ignore annoying future warning from key generation
import warnings
warnings.filterwarnings('ignore', category=FutureWarning)
# ADE key retrieval script included in respective OS folder.
from calibre_plugins.ineptepub.ade_key import retrieve_key
from calibre_plugins.ineptepub.ineptkey import retrieve_keys
try:
keydata = retrieve_key()
userkeys.append(keydata)
keypath = os.path.join(confpath, 'calibre-adeptkey.der')
with open(keypath, 'wb') as f:
f.write(keydata)
print 'IneptEpub: Created keyfile from ADE install.'
keys = retrieve_keys()
for i,key in enumerate(keys):
keyname = u"calibre-adeptkey{0:d}.der".format(i)
userkeys.append([key,keyname])
keypath = os.path.join(confpath, keyname)
open(keypath, 'wb').write(key)
print u"{0} v{1}: Created keyfile {2} from ADE install.".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)
except:
print 'IneptEpub: Couldn\'t Retrieve key from ADE install.'
pass
print u"{0} v{1}: Couldn\'t Retrieve key from ADE install.".format(PLUGIN_NAME, PLUGIN_VERSION)
pass
if not userkeys:
# No user keys found... bail out.
raise ADEPTError('IneptEpub - No keys found. Check keyfile(s)/ADE install')
raise ADEPTError(u"{0} v{1}: No keys found. Check keyfile(s)/ADE install".format(PLUGIN_NAME, PLUGIN_VERSION))
return
# Attempt to decrypt epub with each encryption key found.
for userkey in userkeys:
# Create a TemporaryPersistent file to work with.
# Check original epub archive for zip errors.
from calibre_plugins.ineptepub import zipfix
inf = self.temporary_file('.epub')
try:
fr = zipfix.fixZip(path_to_ebook, inf.name)
fr.fix()
except Exception, e:
raise Exception(e)
return
of = self.temporary_file('.epub')
for userkeyinfo in userkeys:
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, userkeyinfo[1])
of = self.temporary_file(u".epub")
# Give the user key, ebook and TemporaryPersistent file to the plugin_main function.
result = plugin_main(userkey, inf.name, of.name)
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
result = ineptepub.decryptBook(userkeyinfo[0], inf.name, of.name)
# Ebook is not an Adobe Adept epub... do nothing and pass it on.
# This allows a non-encrypted epub to be imported without error messages.
if result == 1:
print 'IneptEpub: Not an Adobe Adept Epub... punting.'
print u"{0} v{1}: {2} is not a secure Adobe Adept ePub.".format(PLUGIN_NAME, PLUGIN_VERSION,os.path.basename(path_to_ebook))
of.close()
return path_to_ebook
break
@@ -461,17 +198,16 @@ class IneptDeDRM(FileTypePlugin):
# Decryption was successful return the modified PersistentTemporary
# file to Calibre's import process.
if result == 0:
print 'IneptEpub: Encryption successfully removed.'
of.close
print u"{0} v{1}: Encryption successfully removed.".format(PLUGIN_NAME, PLUGIN_VERSION)
of.close()
return of.name
break
print 'IneptEpub: Encryption key invalid... trying others.'
of.close()
print u"{0} v{1}: Encryption key incorrect.".format(PLUGIN_NAME, PLUGIN_VERSION)
of.close
# Something went wrong with decryption.
# Import the original unmolested epub.
of.close
raise ADEPTError('IneptEpub - Ultimately failed to decrypt')
raise ADEPTError(u"{0} v{1}: Ultimately failed to decrypt".format(PLUGIN_NAME, PLUGIN_VERSION))
return

View File

@@ -1,346 +0,0 @@
#!/usr/bin/env python
"""
Retrieve Adobe ADEPT user key.
"""
from __future__ import with_statement
__license__ = 'GPL v3'
import sys
import os
import struct
from calibre.constants import iswindows, isosx
class ADEPTError(Exception):
pass
if iswindows:
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, c_size_t, memmove, CDLL, c_int, \
c_long, c_ulong
from ctypes.wintypes import LPVOID, DWORD, BOOL
import _winreg as winreg
def _load_crypto_libcrypto():
from ctypes.util import find_library
libcrypto = find_library('libeay32')
if libcrypto is None:
raise ADEPTError('libcrypto not found')
libcrypto = CDLL(libcrypto)
AES_MAXNR = 14
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
def F(restype, name, argtypes):
func = getattr(libcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
[c_char_p, c_int, AES_KEY_p])
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
c_int])
class AES(object):
def __init__(self, userkey):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise ADEPTError('AES improper key used')
key = self._key = AES_KEY()
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
if rv < 0:
raise ADEPTError('Failed to initialize AES key')
def decrypt(self, data):
out = create_string_buffer(len(data))
iv = ("\x00" * self._blocksize)
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
if rv == 0:
raise ADEPTError('AES decryption failed')
return out.raw
return AES
def _load_crypto_pycrypto():
from Crypto.Cipher import AES as _AES
class AES(object):
def __init__(self, key):
self._aes = _AES.new(key, _AES.MODE_CBC)
def decrypt(self, data):
return self._aes.decrypt(data)
return AES
def _load_crypto():
AES = None
for loader in (_load_crypto_pycrypto, _load_crypto_libcrypto):
try:
AES = loader()
break
except (ImportError, ADEPTError):
pass
return AES
AES = _load_crypto()
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
MAX_PATH = 255
kernel32 = windll.kernel32
advapi32 = windll.advapi32
crypt32 = windll.crypt32
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()
PAGE_EXECUTE_READWRITE = 0x40
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
def VirtualAlloc():
_VirtualAlloc = kernel32.VirtualAlloc
_VirtualAlloc.argtypes = [LPVOID, c_size_t, DWORD, DWORD]
_VirtualAlloc.restype = LPVOID
def VirtualAlloc(addr, size, alloctype=(MEM_COMMIT | MEM_RESERVE),
protect=PAGE_EXECUTE_READWRITE):
return _VirtualAlloc(addr, size, alloctype, protect)
return VirtualAlloc
VirtualAlloc = VirtualAlloc()
MEM_RELEASE = 0x8000
def VirtualFree():
_VirtualFree = kernel32.VirtualFree
_VirtualFree.argtypes = [LPVOID, c_size_t, DWORD]
_VirtualFree.restype = BOOL
def VirtualFree(addr, size=0, freetype=MEM_RELEASE):
return _VirtualFree(addr, size, freetype)
return VirtualFree
VirtualFree = VirtualFree()
class NativeFunction(object):
def __init__(self, restype, argtypes, insns):
self._buf = buf = VirtualAlloc(None, len(insns))
memmove(buf, insns, len(insns))
ftype = CFUNCTYPE(restype, *argtypes)
self._native = ftype(buf)
def __call__(self, *args):
return self._native(*args)
def __del__(self):
if self._buf is not None:
VirtualFree(self._buf)
self._buf = None
if struct.calcsize("P") == 4:
CPUID0_INSNS = (
"\x53" # push %ebx
"\x31\xc0" # xor %eax,%eax
"\x0f\xa2" # cpuid
"\x8b\x44\x24\x08" # mov 0x8(%esp),%eax
"\x89\x18" # mov %ebx,0x0(%eax)
"\x89\x50\x04" # mov %edx,0x4(%eax)
"\x89\x48\x08" # mov %ecx,0x8(%eax)
"\x5b" # pop %ebx
"\xc3" # ret
)
CPUID1_INSNS = (
"\x53" # push %ebx
"\x31\xc0" # xor %eax,%eax
"\x40" # inc %eax
"\x0f\xa2" # cpuid
"\x5b" # pop %ebx
"\xc3" # ret
)
else:
CPUID0_INSNS = (
"\x49\x89\xd8" # mov %rbx,%r8
"\x49\x89\xc9" # mov %rcx,%r9
"\x48\x31\xc0" # xor %rax,%rax
"\x0f\xa2" # cpuid
"\x4c\x89\xc8" # mov %r9,%rax
"\x89\x18" # mov %ebx,0x0(%rax)
"\x89\x50\x04" # mov %edx,0x4(%rax)
"\x89\x48\x08" # mov %ecx,0x8(%rax)
"\x4c\x89\xc3" # mov %r8,%rbx
"\xc3" # retq
)
CPUID1_INSNS = (
"\x53" # push %rbx
"\x48\x31\xc0" # xor %rax,%rax
"\x48\xff\xc0" # inc %rax
"\x0f\xa2" # cpuid
"\x5b" # pop %rbx
"\xc3" # retq
)
def cpuid0():
_cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS)
buf = create_string_buffer(12)
def cpuid0():
_cpuid0(buf)
return buf.raw
return cpuid0
cpuid0 = cpuid0()
cpuid1 = NativeFunction(c_uint, [], CPUID1_INSNS)
class DataBlob(Structure):
_fields_ = [('cbData', c_uint),
('pbData', c_void_p)]
DataBlob_p = POINTER(DataBlob)
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 ADEPTError("Failed to decrypt user key key (sic)")
return string_at(outdata.pbData, outdata.cbData)
return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData()
def retrieve_key():
if AES is None:
tkMessageBox.showerror(
"ADEPT Key",
"This script requires PyCrypto or OpenSSL which must be installed "
"separately. Read the top-of-script comment for details.")
return False
root = GetSystemDirectory().split('\\')[0] + '\\'
serial = GetVolumeSerialNumber(root)
vendor = cpuid0()
signature = struct.pack('>I', cpuid1())[1:]
user = GetUserName()
entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
cuser = winreg.HKEY_CURRENT_USER
try:
regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH)
except WindowsError:
raise ADEPTError("Adobe Digital Editions not activated")
device = winreg.QueryValueEx(regkey, 'key')[0]
keykey = CryptUnprotectData(device, entropy)
userkey = None
try:
plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
except WindowsError:
raise ADEPTError("Could not locate ADE activation")
for i in xrange(0, 16):
try:
plkparent = winreg.OpenKey(plkroot, "%04d" % (i,))
except WindowsError:
break
ktype = winreg.QueryValueEx(plkparent, None)[0]
if ktype != 'credentials':
continue
for j in xrange(0, 16):
try:
plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
except WindowsError:
break
ktype = winreg.QueryValueEx(plkkey, None)[0]
if ktype != 'privateLicenseKey':
continue
userkey = winreg.QueryValueEx(plkkey, 'value')[0]
break
if userkey is not None:
break
if userkey is None:
raise ADEPTError('Could not locate privateLicenseKey')
userkey = userkey.decode('base64')
aes = AES(keykey)
userkey = aes.decrypt(userkey)
userkey = userkey[26:-ord(userkey[-1])]
return userkey
else:
import xml.etree.ElementTree as etree
import subprocess
NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
def findActivationDat():
home = os.getenv('HOME')
cmdline = 'find "' + home + '/Library/Application Support/Adobe/Digital Editions" -name "activation.dat"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
out1, out2 = p2.communicate()
reslst = out1.split('\n')
cnt = len(reslst)
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('activation.dat')
if pp >= 0:
ActDatPath = resline
break
if os.path.exists(ActDatPath):
return ActDatPath
return None
def retrieve_key():
actpath = findActivationDat()
if actpath is None:
raise ADEPTError("Could not locate ADE activation")
tree = etree.parse(actpath)
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey'))
userkey = tree.findtext(expr)
userkey = userkey.decode('base64')
userkey = userkey[26:]
return userkey

View File

@@ -3,11 +3,13 @@
from __future__ import with_statement
# ineptepub.pyw, version 5.6
# Copyright © 2009-2010 i♥cabbages
# ineptepub.pyw, version 5.8
# Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/>
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102012 by some_updates, DiapDealer and Apprentice Alf
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
@@ -30,23 +32,84 @@ from __future__ import with_statement
# 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml
# 5.5 - On Windows try PyCrypto first, OpenSSL next
# 5.6 - Modify interface to allow use with import
# 5.7 - Fix for potential problem with PyCrypto
# 5.8 - Revised to allow use in calibre plugins to eliminate need for duplicate code
"""
Decrypt Adobe ADEPT-encrypted EPUB books.
Decrypt Adobe Digital Editions encrypted ePub books.
"""
__license__ = 'GPL v3'
__version__ = "5.8"
import sys
import os
import traceback
import zlib
import zipfile
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
from contextlib import closing
import xml.etree.ElementTree as etree
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
try:
from calibre.constants import iswindows, isosx
except:
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
return [u"ineptepub.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
class ADEPTError(Exception):
pass
@@ -56,7 +119,7 @@ def _load_crypto_libcrypto():
Structure, c_ulong, create_string_buffer, cast
from ctypes.util import find_library
if sys.platform.startswith('win'):
if iswindows:
libcrypto = find_library('libeay32')
else:
libcrypto = find_library('crypto')
@@ -235,7 +298,7 @@ def _load_crypto_pycrypto():
class AES(object):
def __init__(self, key):
self._aes = _AES.new(key, _AES.MODE_CBC)
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
def decrypt(self, data):
return self._aes.decrypt(data)
@@ -270,6 +333,7 @@ def _load_crypto():
except (ImportError, ADEPTError):
pass
return (AES, RSA)
AES, RSA = _load_crypto()
META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
@@ -312,158 +376,181 @@ class Decryptor(object):
data = self.decompress(data)
return data
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text='Select files for decryption')
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text='Key file').grid(row=0)
self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists('adeptkey.der'):
self.keypath.insert(0, 'adeptkey.der')
button = Tkinter.Button(body, text="...", command=self.get_keypath)
button.grid(row=0, column=2)
Tkinter.Label(body, text='Input file').grid(row=1)
self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text="...", command=self.get_inpath)
button.grid(row=1, column=2)
Tkinter.Label(body, text='Output file').grid(row=2)
self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=2, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
buttons, text="Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
buttons, text="Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
parent=None, title='Select ADEPT key file',
defaultextension='.der', filetypes=[('DER-encoded files', '.der'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
def get_inpath(self):
inpath = tkFileDialog.askopenfilename(
parent=None, title='Select ADEPT-encrypted EPUB file to decrypt',
defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
('All files', '.*')])
if inpath:
inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END)
self.inpath.insert(0, inpath)
return
def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename(
parent=None, title='Select unencrypted EPUB file to produce',
defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
('All files', '.*')])
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return
def decrypt(self):
keypath = self.keypath.get()
inpath = self.inpath.get()
outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath):
self.status['text'] = 'Specified key file does not exist'
return
if not inpath or not os.path.exists(inpath):
self.status['text'] = 'Specified input file does not exist'
return
if not outpath:
self.status['text'] = 'Output file not specified'
return
if inpath == outpath:
self.status['text'] = 'Must have different input and output files'
return
argv = [sys.argv[0], keypath, inpath, outpath]
self.status['text'] = 'Decrypting...'
try:
cli_main(argv)
except Exception, e:
self.status['text'] = 'Error: ' + str(e)
return
self.status['text'] = 'File successfully decrypted'
def decryptBook(keypath, inpath, outpath):
with open(keypath, 'rb') as f:
keyder = f.read()
rsa = RSA(keyder)
# check file to make check whether it's probably an Adobe Adept encrypted ePub
def adeptBook(inpath):
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist:
raise ADEPTError('%s: not an ADEPT EPUB' % (inpath,))
return False
try:
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
if len(bookkey) == 172:
return True
except:
# if we couldn't check, assume it is
return True
return False
def decryptBook(userkey, inpath, outpath):
if AES is None:
raise ADEPTError(u"PyCrypto or OpenSSL must be installed.")
rsa = RSA(userkey)
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist:
print u"{0:s} is DRM-free.".format(os.path.basename(inpath))
return 1
for name in META_NAMES:
namelist.remove(name)
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
bookkey = rsa.decrypt(bookkey.decode('base64'))
# Padded as per RSAES-PKCS1-v1_5
if bookkey[-17] != '\x00':
raise ADEPTError('problem decrypting session key')
encryption = inf.read('META-INF/encryption.xml')
decryptor = Decryptor(bookkey[-16:], encryption)
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
outf.writestr(zi, inf.read('mimetype'))
for path in namelist:
data = inf.read(path)
outf.writestr(path, decryptor.decrypt(path, data))
try:
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
if len(bookkey) != 172:
print u"{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath))
return 1
bookkey = rsa.decrypt(bookkey.decode('base64'))
# Padded as per RSAES-PKCS1-v1_5
if bookkey[-17] != '\x00':
print u"Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath))
return 2
encryption = inf.read('META-INF/encryption.xml')
decryptor = Decryptor(bookkey[-16:], encryption)
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
outf.writestr(zi, inf.read('mimetype'))
for path in namelist:
data = inf.read(path)
outf.writestr(path, decryptor.decrypt(path, data))
except:
print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc())
return 2
return 0
def cli_main(argv=sys.argv):
def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
if AES is None:
print "%s: This script requires OpenSSL or PyCrypto, which must be" \
" installed separately. Read the top-of-script comment for" \
" details." % (progname,)
return 1
if len(argv) != 4:
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
print u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname)
return 1
keypath, inpath, outpath = argv[1:]
return decryptBook(keypath, inpath, outpath)
userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath)
if result == 0:
print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
return result
def gui_main():
import Tkinter
import Tkconstants
import tkFileDialog
import traceback
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Select files for decryption")
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Key file").grid(row=0)
self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists(u"adeptkey.der"):
self.keypath.insert(0, u"adeptkey.der")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
button.grid(row=0, column=2)
Tkinter.Label(body, text=u"Input file").grid(row=1)
self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
button.grid(row=1, column=2)
Tkinter.Label(body, text=u"Output file").grid(row=2)
self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
button.grid(row=2, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
buttons, text=u"Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select Adobe Adept \'.der\' key file",
defaultextension=u".der",
filetypes=[('Adobe Adept DER-encoded files', '.der'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
def get_inpath(self):
inpath = tkFileDialog.askopenfilename(
parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
if inpath:
inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END)
self.inpath.insert(0, inpath)
return
def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select unencrypted ePub file to produce",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return
def decrypt(self):
keypath = self.keypath.get()
inpath = self.inpath.get()
outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath):
self.status['text'] = u"Specified key file does not exist"
return
if not inpath or not os.path.exists(inpath):
self.status['text'] = u"Specified input file does not exist"
return
if not outpath:
self.status['text'] = u"Output file not specified"
return
if inpath == outpath:
self.status['text'] = u"Must have different input and output files"
return
userkey = open(keypath,'rb').read()
self.status['text'] = u"Decrypting..."
try:
decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception, e:
self.status['text'] = u"Error; {0}".format(e)
return
if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted"
else:
self.status['text'] = u"The was an error decrypting the file."
root = Tkinter.Tk()
if AES is None:
root.withdraw()
tkMessageBox.showerror(
"INEPT EPUB Decrypter",
"This script requires OpenSSL or PyCrypto, which must be"
" installed separately. Read the top-of-script comment for"
" details.")
return 1
root.title('INEPT EPUB Decrypter')
root.title(u"Adobe Adept ePub Decrypter v.{0}".format(__version__))
root.resizable(True, False)
root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
@@ -472,5 +559,7 @@ def gui_main():
if __name__ == '__main__':
if len(sys.argv) > 1:
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(gui_main())

View File

@@ -3,11 +3,11 @@
from __future__ import with_statement
# ineptkey.pyw, version 5.4
# ineptkey.pyw, version 5.6
# Copyright © 2009-2010 i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/>
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
@@ -36,6 +36,8 @@ from __future__ import with_statement
# 5.2 - added support for output of key to a particular file
# 5.3 - On Windows try PyCrypto first, OpenSSL next
# 5.4 - Modify interface to allow use of import
# 5.5 - Fix for potential problem with PyCrypto
# 5.6 - Revised to allow use in Plugins to eliminate need for duplicate code
"""
Retrieve Adobe ADEPT user key.
@@ -46,15 +48,70 @@ __license__ = 'GPL v3'
import sys
import os
import struct
import Tkinter
import Tkconstants
import tkMessageBox
import traceback
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
try:
from calibre.constants import iswindows, isosx
except:
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
return [u"ineptkey.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
class ADEPTError(Exception):
pass
if sys.platform.startswith('win'):
if iswindows:
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, c_size_t, memmove, CDLL, c_int, \
@@ -110,7 +167,7 @@ if sys.platform.startswith('win'):
from Crypto.Cipher import AES as _AES
class AES(object):
def __init__(self, key):
self._aes = _AES.new(key, _AES.MODE_CBC)
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
def decrypt(self, data):
return self._aes.decrypt(data)
return AES
@@ -292,13 +349,9 @@ if sys.platform.startswith('win'):
return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData()
def retrieve_key(keypath):
def retrieve_keys():
if AES is None:
tkMessageBox.showerror(
"ADEPT Key",
"This script requires PyCrypto or OpenSSL which must be installed "
"separately. Read the top-of-script comment for details.")
return False
raise ADEPTError("PyCrypto or OpenSSL must be installed")
root = GetSystemDirectory().split('\\')[0] + '\\'
serial = GetVolumeSerialNumber(root)
vendor = cpuid0()
@@ -308,11 +361,12 @@ if sys.platform.startswith('win'):
cuser = winreg.HKEY_CURRENT_USER
try:
regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH)
device = winreg.QueryValueEx(regkey, 'key')[0]
except WindowsError:
raise ADEPTError("Adobe Digital Editions not activated")
device = winreg.QueryValueEx(regkey, 'key')[0]
keykey = CryptUnprotectData(device, entropy)
userkey = None
keys = []
try:
plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
except WindowsError:
@@ -334,50 +388,43 @@ if sys.platform.startswith('win'):
if ktype != 'privateLicenseKey':
continue
userkey = winreg.QueryValueEx(plkkey, 'value')[0]
break
if userkey is not None:
break
if userkey is None:
userkey = userkey.decode('base64')
aes = AES(keykey)
userkey = aes.decrypt(userkey)
userkey = userkey[26:-ord(userkey[-1])]
keys.append(userkey)
if len(keys) == 0:
raise ADEPTError('Could not locate privateLicenseKey')
userkey = userkey.decode('base64')
aes = AES(keykey)
userkey = aes.decrypt(userkey)
userkey = userkey[26:-ord(userkey[-1])]
with open(keypath, 'wb') as f:
f.write(userkey)
return True
return keys
elif sys.platform.startswith('darwin'):
elif isosx:
import xml.etree.ElementTree as etree
import Carbon.File
import Carbon.Folder
import Carbon.Folders
import MacOS
import subprocess
ACTIVATION_PATH = 'Adobe/Digital Editions/activation.dat'
NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
def find_folder(domain, dtype):
try:
fsref = Carbon.Folder.FSFindFolder(domain, dtype, False)
return Carbon.File.pathname(fsref)
except MacOS.Error:
return None
def find_app_support_file(subpath):
dtype = Carbon.Folders.kApplicationSupportFolderType
for domain in Carbon.Folders.kUserDomain, Carbon.Folders.kLocalDomain:
path = find_folder(domain, dtype)
if path is None:
continue
path = os.path.join(path, subpath)
if os.path.isfile(path):
return path
def findActivationDat():
home = os.getenv('HOME')
cmdline = 'find "' + home + '/Library/Application Support/Adobe/Digital Editions" -name "activation.dat"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
out1, out2 = p2.communicate()
reslst = out1.split('\n')
cnt = len(reslst)
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('activation.dat')
if pp >= 0:
ActDatPath = resline
break
if os.path.exists(ActDatPath):
return ActDatPath
return None
def retrieve_key(keypath):
actpath = find_app_support_file(ACTIVATION_PATH)
def retrieve_keys():
actpath = findActivationDat()
if actpath is None:
raise ADEPTError("Could not locate ADE activation")
tree = etree.parse(actpath)
@@ -386,69 +433,65 @@ elif sys.platform.startswith('darwin'):
userkey = tree.findtext(expr)
userkey = userkey.decode('base64')
userkey = userkey[26:]
with open(keypath, 'wb') as f:
f.write(userkey)
return True
elif sys.platform.startswith('cygwin'):
def retrieve_key(keypath):
tkMessageBox.showerror(
"ADEPT Key",
"This script requires a Windows-native Python, and cannot be run "
"under Cygwin. Please install a Windows-native Python and/or "
"check your file associations.")
return False
return [userkey]
else:
def retrieve_key(keypath):
tkMessageBox.showerror(
"ADEPT Key",
"This script only supports Windows and Mac OS X. For Linux "
"you should be able to run ADE and this script under Wine (with "
"an appropriate version of Windows Python installed).")
return False
class ExceptionDialog(Tkinter.Frame):
def __init__(self, root, text):
Tkinter.Frame.__init__(self, root, border=5)
label = Tkinter.Label(self, text="Unexpected error:",
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
label.pack(fill=Tkconstants.X, expand=0)
self.text = Tkinter.Text(self)
self.text.pack(fill=Tkconstants.BOTH, expand=1)
self.text.insert(Tkconstants.END, text)
def retrieve_keys(keypath):
raise ADEPTError("This script only supports Windows and Mac OS X.")
return []
def retrieve_key(keypath):
keys = retrieve_keys()
with open(keypath, 'wb') as f:
f.write(keys[0])
return True
def extractKeyfile(keypath):
try:
success = retrieve_key(keypath)
except ADEPTError, e:
print "Key generation Error: " + str(e)
print u"Key generation Error: {0}".format(e.args[0])
return 1
except Exception, e:
print "General Error: " + str(e)
print "General Error: {0}".format(e.args[0])
return 1
if not success:
return 1
return 0
def cli_main(argv=sys.argv):
def cli_main(argv=unicode_argv()):
keypath = argv[1]
return extractKeyfile(keypath)
def main(argv=sys.argv):
def gui_main(argv=unicode_argv()):
import Tkinter
import Tkconstants
import tkMessageBox
import traceback
class ExceptionDialog(Tkinter.Frame):
def __init__(self, root, text):
Tkinter.Frame.__init__(self, root, border=5)
label = Tkinter.Label(self, text=u"Unexpected error:",
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
label.pack(fill=Tkconstants.X, expand=0)
self.text = Tkinter.Text(self)
self.text.pack(fill=Tkconstants.BOTH, expand=1)
self.text.insert(Tkconstants.END, text)
root = Tkinter.Tk()
root.withdraw()
progname = os.path.basename(argv[0])
keypath = 'adeptkey.der'
keypath, progname = os.path.split(argv[0])
keypath = os.path.join(keypath, u"adeptkey.der")
success = False
try:
success = retrieve_key(keypath)
except ADEPTError, e:
tkMessageBox.showerror("ADEPT Key", "Error: " + str(e))
tkMessageBox.showerror(u"ADEPT Key", "Error: {0}".format(e.args[0]))
except Exception:
root.wm_state('normal')
root.title('ADEPT Key')
@@ -458,10 +501,12 @@ def main(argv=sys.argv):
if not success:
return 1
tkMessageBox.showinfo(
"ADEPT Key", "Key successfully retrieved to %s" % (keypath))
u"ADEPT Key", u"Key successfully retrieved to {0}".format(keypath))
return 0
if __name__ == '__main__':
if len(sys.argv) > 1:
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(main())
sys.exit(gui_main())

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import zlib
import zipfile
import zipfilerugged
import os
import os.path
import getopt
@@ -15,7 +16,7 @@ _FILENAME_OFFSET = 30
_MAX_SIZE = 64 * 1024
_MIMETYPE = 'application/epub+zip'
class ZipInfo(zipfile.ZipInfo):
class ZipInfo(zipfilerugged.ZipInfo):
def __init__(self, *args, **kwargs):
if 'compress_type' in kwargs:
compress_type = kwargs.pop('compress_type')
@@ -27,10 +28,10 @@ class fixZip:
self.ztype = 'zip'
if zinput.lower().find('.epub') >= 0 :
self.ztype = 'epub'
self.inzip = zipfile.ZipFile(zinput,'r')
self.outzip = zipfile.ZipFile(zoutput,'w')
self.inzip = zipfilerugged.ZipFile(zinput,'r')
self.outzip = zipfilerugged.ZipFile(zoutput,'w')
# 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):
local_header_offset = zi.header_offset
@@ -76,11 +77,11 @@ class fixZip:
data = None
# if not compressed we are good to go
if zi.compress_type == zipfile.ZIP_STORED:
if zi.compress_type == zipfilerugged.ZIP_STORED:
data = self.bzf.read(zi.file_size)
# if compressed we must decompress it using zlib
if zi.compress_type == zipfile.ZIP_DEFLATED:
if zi.compress_type == zipfilerugged.ZIP_DEFLATED:
cmpdata = self.bzf.read(zi.compress_size)
data = self.uncompress(cmpdata)
@@ -95,7 +96,7 @@ class fixZip:
# if epub write mimetype file first, with no compression
if self.ztype == 'epub':
nzinfo = ZipInfo('mimetype', compress_type=zipfile.ZIP_STORED)
nzinfo = ZipInfo('mimetype', compress_type=zipfilerugged.ZIP_STORED)
self.outzip.writestr(nzinfo, _MIMETYPE)
# write the rest of the files
@@ -105,7 +106,7 @@ class fixZip:
nzinfo = zinfo
try:
data = self.inzip.read(zinfo.filename)
except zipfile.BadZipfile or zipfile.error:
except zipfilerugged.BadZipfile or zipfilerugged.error:
local_name = self.getlocalname(zinfo)
data = self.getfiledata(zinfo)
nzinfo.filename = local_name
@@ -152,5 +153,3 @@ def main(argv=sys.argv):
if __name__ == '__main__' :
sys.exit(main())

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,346 +0,0 @@
#!/usr/bin/env python
"""
Retrieve Adobe ADEPT user key.
"""
from __future__ import with_statement
__license__ = 'GPL v3'
import sys
import os
import struct
from calibre.constants import iswindows, isosx
class ADEPTError(Exception):
pass
if iswindows:
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, c_size_t, memmove, CDLL, c_int, \
c_long, c_ulong
from ctypes.wintypes import LPVOID, DWORD, BOOL
import _winreg as winreg
def _load_crypto_libcrypto():
from ctypes.util import find_library
libcrypto = find_library('libeay32')
if libcrypto is None:
raise ADEPTError('libcrypto not found')
libcrypto = CDLL(libcrypto)
AES_MAXNR = 14
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
def F(restype, name, argtypes):
func = getattr(libcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
[c_char_p, c_int, AES_KEY_p])
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
c_int])
class AES(object):
def __init__(self, userkey):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise ADEPTError('AES improper key used')
key = self._key = AES_KEY()
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
if rv < 0:
raise ADEPTError('Failed to initialize AES key')
def decrypt(self, data):
out = create_string_buffer(len(data))
iv = ("\x00" * self._blocksize)
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
if rv == 0:
raise ADEPTError('AES decryption failed')
return out.raw
return AES
def _load_crypto_pycrypto():
from Crypto.Cipher import AES as _AES
class AES(object):
def __init__(self, key):
self._aes = _AES.new(key, _AES.MODE_CBC)
def decrypt(self, data):
return self._aes.decrypt(data)
return AES
def _load_crypto():
AES = None
for loader in (_load_crypto_pycrypto, _load_crypto_libcrypto):
try:
AES = loader()
break
except (ImportError, ADEPTError):
pass
return AES
AES = _load_crypto()
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
MAX_PATH = 255
kernel32 = windll.kernel32
advapi32 = windll.advapi32
crypt32 = windll.crypt32
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()
PAGE_EXECUTE_READWRITE = 0x40
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
def VirtualAlloc():
_VirtualAlloc = kernel32.VirtualAlloc
_VirtualAlloc.argtypes = [LPVOID, c_size_t, DWORD, DWORD]
_VirtualAlloc.restype = LPVOID
def VirtualAlloc(addr, size, alloctype=(MEM_COMMIT | MEM_RESERVE),
protect=PAGE_EXECUTE_READWRITE):
return _VirtualAlloc(addr, size, alloctype, protect)
return VirtualAlloc
VirtualAlloc = VirtualAlloc()
MEM_RELEASE = 0x8000
def VirtualFree():
_VirtualFree = kernel32.VirtualFree
_VirtualFree.argtypes = [LPVOID, c_size_t, DWORD]
_VirtualFree.restype = BOOL
def VirtualFree(addr, size=0, freetype=MEM_RELEASE):
return _VirtualFree(addr, size, freetype)
return VirtualFree
VirtualFree = VirtualFree()
class NativeFunction(object):
def __init__(self, restype, argtypes, insns):
self._buf = buf = VirtualAlloc(None, len(insns))
memmove(buf, insns, len(insns))
ftype = CFUNCTYPE(restype, *argtypes)
self._native = ftype(buf)
def __call__(self, *args):
return self._native(*args)
def __del__(self):
if self._buf is not None:
VirtualFree(self._buf)
self._buf = None
if struct.calcsize("P") == 4:
CPUID0_INSNS = (
"\x53" # push %ebx
"\x31\xc0" # xor %eax,%eax
"\x0f\xa2" # cpuid
"\x8b\x44\x24\x08" # mov 0x8(%esp),%eax
"\x89\x18" # mov %ebx,0x0(%eax)
"\x89\x50\x04" # mov %edx,0x4(%eax)
"\x89\x48\x08" # mov %ecx,0x8(%eax)
"\x5b" # pop %ebx
"\xc3" # ret
)
CPUID1_INSNS = (
"\x53" # push %ebx
"\x31\xc0" # xor %eax,%eax
"\x40" # inc %eax
"\x0f\xa2" # cpuid
"\x5b" # pop %ebx
"\xc3" # ret
)
else:
CPUID0_INSNS = (
"\x49\x89\xd8" # mov %rbx,%r8
"\x49\x89\xc9" # mov %rcx,%r9
"\x48\x31\xc0" # xor %rax,%rax
"\x0f\xa2" # cpuid
"\x4c\x89\xc8" # mov %r9,%rax
"\x89\x18" # mov %ebx,0x0(%rax)
"\x89\x50\x04" # mov %edx,0x4(%rax)
"\x89\x48\x08" # mov %ecx,0x8(%rax)
"\x4c\x89\xc3" # mov %r8,%rbx
"\xc3" # retq
)
CPUID1_INSNS = (
"\x53" # push %rbx
"\x48\x31\xc0" # xor %rax,%rax
"\x48\xff\xc0" # inc %rax
"\x0f\xa2" # cpuid
"\x5b" # pop %rbx
"\xc3" # retq
)
def cpuid0():
_cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS)
buf = create_string_buffer(12)
def cpuid0():
_cpuid0(buf)
return buf.raw
return cpuid0
cpuid0 = cpuid0()
cpuid1 = NativeFunction(c_uint, [], CPUID1_INSNS)
class DataBlob(Structure):
_fields_ = [('cbData', c_uint),
('pbData', c_void_p)]
DataBlob_p = POINTER(DataBlob)
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 ADEPTError("Failed to decrypt user key key (sic)")
return string_at(outdata.pbData, outdata.cbData)
return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData()
def retrieve_key():
if AES is None:
tkMessageBox.showerror(
"ADEPT Key",
"This script requires PyCrypto or OpenSSL which must be installed "
"separately. Read the top-of-script comment for details.")
return False
root = GetSystemDirectory().split('\\')[0] + '\\'
serial = GetVolumeSerialNumber(root)
vendor = cpuid0()
signature = struct.pack('>I', cpuid1())[1:]
user = GetUserName()
entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
cuser = winreg.HKEY_CURRENT_USER
try:
regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH)
except WindowsError:
raise ADEPTError("Adobe Digital Editions not activated")
device = winreg.QueryValueEx(regkey, 'key')[0]
keykey = CryptUnprotectData(device, entropy)
userkey = None
try:
plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
except WindowsError:
raise ADEPTError("Could not locate ADE activation")
for i in xrange(0, 16):
try:
plkparent = winreg.OpenKey(plkroot, "%04d" % (i,))
except WindowsError:
break
ktype = winreg.QueryValueEx(plkparent, None)[0]
if ktype != 'credentials':
continue
for j in xrange(0, 16):
try:
plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
except WindowsError:
break
ktype = winreg.QueryValueEx(plkkey, None)[0]
if ktype != 'privateLicenseKey':
continue
userkey = winreg.QueryValueEx(plkkey, 'value')[0]
break
if userkey is not None:
break
if userkey is None:
raise ADEPTError('Could not locate privateLicenseKey')
userkey = userkey.decode('base64')
aes = AES(keykey)
userkey = aes.decrypt(userkey)
userkey = userkey[26:-ord(userkey[-1])]
return userkey
else:
import xml.etree.ElementTree as etree
import subprocess
NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
def findActivationDat():
home = os.getenv('HOME')
cmdline = 'find "' + home + '/Library/Application Support/Adobe/Digital Editions" -name "activation.dat"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
out1, out2 = p2.communicate()
reslst = out1.split('\n')
cnt = len(reslst)
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('activation.dat')
if pp >= 0:
ActDatPath = resline
break
if os.path.exists(ActDatPath):
return ActDatPath
return None
def retrieve_key():
actpath = findActivationDat()
if actpath is None:
raise ADEPTError("Could not locate ADE activation")
tree = etree.parse(actpath)
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey'))
userkey = tree.findtext(expr)
userkey = userkey.decode('base64')
userkey = userkey[26:]
return userkey

View File

@@ -3,11 +3,11 @@
from __future__ import with_statement
# ineptkey.pyw, version 5.4
# ineptkey.pyw, version 5.6
# Copyright © 2009-2010 i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/>
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
@@ -36,6 +36,8 @@ from __future__ import with_statement
# 5.2 - added support for output of key to a particular file
# 5.3 - On Windows try PyCrypto first, OpenSSL next
# 5.4 - Modify interface to allow use of import
# 5.5 - Fix for potential problem with PyCrypto
# 5.6 - Revised to allow use in Plugins to eliminate need for duplicate code
"""
Retrieve Adobe ADEPT user key.
@@ -46,15 +48,70 @@ __license__ = 'GPL v3'
import sys
import os
import struct
import Tkinter
import Tkconstants
import tkMessageBox
import traceback
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
try:
from calibre.constants import iswindows, isosx
except:
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
return [u"ineptkey.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
class ADEPTError(Exception):
pass
if sys.platform.startswith('win'):
if iswindows:
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, c_size_t, memmove, CDLL, c_int, \
@@ -110,7 +167,7 @@ if sys.platform.startswith('win'):
from Crypto.Cipher import AES as _AES
class AES(object):
def __init__(self, key):
self._aes = _AES.new(key, _AES.MODE_CBC)
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
def decrypt(self, data):
return self._aes.decrypt(data)
return AES
@@ -292,13 +349,9 @@ if sys.platform.startswith('win'):
return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData()
def retrieve_key(keypath):
def retrieve_keys():
if AES is None:
tkMessageBox.showerror(
"ADEPT Key",
"This script requires PyCrypto or OpenSSL which must be installed "
"separately. Read the top-of-script comment for details.")
return False
raise ADEPTError("PyCrypto or OpenSSL must be installed")
root = GetSystemDirectory().split('\\')[0] + '\\'
serial = GetVolumeSerialNumber(root)
vendor = cpuid0()
@@ -308,11 +361,12 @@ if sys.platform.startswith('win'):
cuser = winreg.HKEY_CURRENT_USER
try:
regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH)
device = winreg.QueryValueEx(regkey, 'key')[0]
except WindowsError:
raise ADEPTError("Adobe Digital Editions not activated")
device = winreg.QueryValueEx(regkey, 'key')[0]
keykey = CryptUnprotectData(device, entropy)
userkey = None
keys = []
try:
plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
except WindowsError:
@@ -334,50 +388,43 @@ if sys.platform.startswith('win'):
if ktype != 'privateLicenseKey':
continue
userkey = winreg.QueryValueEx(plkkey, 'value')[0]
break
if userkey is not None:
break
if userkey is None:
userkey = userkey.decode('base64')
aes = AES(keykey)
userkey = aes.decrypt(userkey)
userkey = userkey[26:-ord(userkey[-1])]
keys.append(userkey)
if len(keys) == 0:
raise ADEPTError('Could not locate privateLicenseKey')
userkey = userkey.decode('base64')
aes = AES(keykey)
userkey = aes.decrypt(userkey)
userkey = userkey[26:-ord(userkey[-1])]
with open(keypath, 'wb') as f:
f.write(userkey)
return True
return keys
elif sys.platform.startswith('darwin'):
elif isosx:
import xml.etree.ElementTree as etree
import Carbon.File
import Carbon.Folder
import Carbon.Folders
import MacOS
import subprocess
ACTIVATION_PATH = 'Adobe/Digital Editions/activation.dat'
NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
def find_folder(domain, dtype):
try:
fsref = Carbon.Folder.FSFindFolder(domain, dtype, False)
return Carbon.File.pathname(fsref)
except MacOS.Error:
return None
def find_app_support_file(subpath):
dtype = Carbon.Folders.kApplicationSupportFolderType
for domain in Carbon.Folders.kUserDomain, Carbon.Folders.kLocalDomain:
path = find_folder(domain, dtype)
if path is None:
continue
path = os.path.join(path, subpath)
if os.path.isfile(path):
return path
def findActivationDat():
home = os.getenv('HOME')
cmdline = 'find "' + home + '/Library/Application Support/Adobe/Digital Editions" -name "activation.dat"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
out1, out2 = p2.communicate()
reslst = out1.split('\n')
cnt = len(reslst)
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('activation.dat')
if pp >= 0:
ActDatPath = resline
break
if os.path.exists(ActDatPath):
return ActDatPath
return None
def retrieve_key(keypath):
actpath = find_app_support_file(ACTIVATION_PATH)
def retrieve_keys():
actpath = findActivationDat()
if actpath is None:
raise ADEPTError("Could not locate ADE activation")
tree = etree.parse(actpath)
@@ -386,69 +433,65 @@ elif sys.platform.startswith('darwin'):
userkey = tree.findtext(expr)
userkey = userkey.decode('base64')
userkey = userkey[26:]
with open(keypath, 'wb') as f:
f.write(userkey)
return True
elif sys.platform.startswith('cygwin'):
def retrieve_key(keypath):
tkMessageBox.showerror(
"ADEPT Key",
"This script requires a Windows-native Python, and cannot be run "
"under Cygwin. Please install a Windows-native Python and/or "
"check your file associations.")
return False
return [userkey]
else:
def retrieve_key(keypath):
tkMessageBox.showerror(
"ADEPT Key",
"This script only supports Windows and Mac OS X. For Linux "
"you should be able to run ADE and this script under Wine (with "
"an appropriate version of Windows Python installed).")
return False
class ExceptionDialog(Tkinter.Frame):
def __init__(self, root, text):
Tkinter.Frame.__init__(self, root, border=5)
label = Tkinter.Label(self, text="Unexpected error:",
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
label.pack(fill=Tkconstants.X, expand=0)
self.text = Tkinter.Text(self)
self.text.pack(fill=Tkconstants.BOTH, expand=1)
self.text.insert(Tkconstants.END, text)
def retrieve_keys(keypath):
raise ADEPTError("This script only supports Windows and Mac OS X.")
return []
def retrieve_key(keypath):
keys = retrieve_keys()
with open(keypath, 'wb') as f:
f.write(keys[0])
return True
def extractKeyfile(keypath):
try:
success = retrieve_key(keypath)
except ADEPTError, e:
print "Key generation Error: " + str(e)
print u"Key generation Error: {0}".format(e.args[0])
return 1
except Exception, e:
print "General Error: " + str(e)
print "General Error: {0}".format(e.args[0])
return 1
if not success:
return 1
return 0
def cli_main(argv=sys.argv):
def cli_main(argv=unicode_argv()):
keypath = argv[1]
return extractKeyfile(keypath)
def main(argv=sys.argv):
def gui_main(argv=unicode_argv()):
import Tkinter
import Tkconstants
import tkMessageBox
import traceback
class ExceptionDialog(Tkinter.Frame):
def __init__(self, root, text):
Tkinter.Frame.__init__(self, root, border=5)
label = Tkinter.Label(self, text=u"Unexpected error:",
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
label.pack(fill=Tkconstants.X, expand=0)
self.text = Tkinter.Text(self)
self.text.pack(fill=Tkconstants.BOTH, expand=1)
self.text.insert(Tkconstants.END, text)
root = Tkinter.Tk()
root.withdraw()
progname = os.path.basename(argv[0])
keypath = 'adeptkey.der'
keypath, progname = os.path.split(argv[0])
keypath = os.path.join(keypath, u"adeptkey.der")
success = False
try:
success = retrieve_key(keypath)
except ADEPTError, e:
tkMessageBox.showerror("ADEPT Key", "Error: " + str(e))
tkMessageBox.showerror(u"ADEPT Key", "Error: {0}".format(e.args[0]))
except Exception:
root.wm_state('normal')
root.title('ADEPT Key')
@@ -458,10 +501,12 @@ def main(argv=sys.argv):
if not success:
return 1
tkMessageBox.showinfo(
"ADEPT Key", "Key successfully retrieved to %s" % (keypath))
u"ADEPT Key", u"Key successfully retrieved to {0}".format(keypath))
return 0
if __name__ == '__main__':
if len(sys.argv) > 1:
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(main())
sys.exit(gui_main())

View File

@@ -1,13 +1,25 @@
#! /usr/bin/env python
# ineptpdf.pyw, version 7.11
#! /usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import with_statement
# To run this program install Python 2.6 from http://www.python.org/download/
# and OpenSSL (already installed on Mac OS X and Linux) OR
# PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# (make sure to install the version for Python 2.6). Save this script file as
# ineptpdf.pyw and double-click on it to run it.
# ineptpdf.pyw, version 7.11
# Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102012 by some_updates, DiapDealer and Apprentice Alf
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
# install the version for Python 2.6). Save this script file as
# ineptepub.pyw and double-click on it to run it.
#
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
# program from the command line (pythonw ineptepub.pyw) or by double-clicking
# it when it has been associated with PythonLauncher.
# Revision history:
# 1 - Initial release
@@ -36,12 +48,14 @@ from __future__ import with_statement
# 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.
# 7.12 - Revised to allow use in calibre plugins to eliminate need for duplicate code
"""
Decrypts Adobe ADEPT-encrypted PDF files.
"""
__license__ = 'GPL v3'
__version__ = "7.12"
import sys
import os
@@ -51,10 +65,63 @@ import struct
import hashlib
from itertools import chain, islice
import xml.etree.ElementTree as etree
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
return [u"ineptepub.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
class ADEPTError(Exception):
pass
@@ -1520,9 +1587,7 @@ class PDFDocument(object):
def initialize_ebx(self, password, docid, param):
self.is_printable = self.is_modifiable = self.is_extractable = True
with open(password, 'rb') as f:
keyder = f.read()
rsa = RSA(keyder)
rsa = RSA(password)
length = int_value(param.get('Length', 0)) / 8
rights = str_value(param.get('ADEPT_LICENSE')).decode('base64')
rights = zlib.decompress(rights, -15)
@@ -1907,14 +1972,14 @@ class PDFObjStrmParser(PDFParser):
### My own code, for which there is none else to blame
class PDFSerializer(object):
def __init__(self, inf, keypath):
def __init__(self, inf, userkey):
global GEN_XREF_STM, gen_xref_stm
gen_xref_stm = GEN_XREF_STM > 1
self.version = inf.read(8)
inf.seek(0)
self.doc = doc = PDFDocument()
parser = PDFParser(doc, inf)
doc.initialize(keypath)
doc.initialize(userkey)
self.objids = objids = set()
for xref in reversed(doc.xrefs):
trailer = xref.trailer
@@ -2097,142 +2162,144 @@ class PDFSerializer(object):
self.write('endobj\n')
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
ltext='Select file for decryption\n'
self.status = Tkinter.Label(self, text=ltext)
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text='Key file').grid(row=0)
self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists('adeptkey.der'):
self.keypath.insert(0, 'adeptkey.der')
button = Tkinter.Button(body, text="...", command=self.get_keypath)
button.grid(row=0, column=2)
Tkinter.Label(body, text='Input file').grid(row=1)
self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text="...", command=self.get_inpath)
button.grid(row=1, column=2)
Tkinter.Label(body, text='Output file').grid(row=2)
self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=2, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
buttons, text="Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
buttons, text="Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
parent=None, title='Select ADEPT key file',
defaultextension='.der', filetypes=[('DER-encoded files', '.der'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(os.path.realpath(keypath))
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
def get_inpath(self):
inpath = tkFileDialog.askopenfilename(
parent=None, title='Select ADEPT encrypted PDF file to decrypt',
defaultextension='.pdf', filetypes=[('PDF files', '.pdf'),
('All files', '.*')])
if inpath:
inpath = os.path.normpath(os.path.realpath(inpath))
self.inpath.delete(0, Tkconstants.END)
self.inpath.insert(0, inpath)
return
def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename(
parent=None, title='Select unencrypted PDF file to produce',
defaultextension='.pdf', filetypes=[('PDF files', '.pdf'),
('All files', '.*')])
if outpath:
outpath = os.path.normpath(os.path.realpath(outpath))
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return
def decrypt(self):
keypath = self.keypath.get()
inpath = self.inpath.get()
outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath):
# keyfile doesn't exist
self.status['text'] = 'Specified Adept key file does not exist'
return
if not inpath or not os.path.exists(inpath):
self.status['text'] = 'Specified input file does not exist'
return
if not outpath:
self.status['text'] = 'Output file not specified'
return
if inpath == outpath:
self.status['text'] = 'Must have different input and output files'
return
# patch for non-ascii characters
argv = [sys.argv[0], keypath, inpath, outpath]
self.status['text'] = 'Processing ...'
try:
cli_main(argv)
except Exception, a:
self.status['text'] = 'Error: ' + str(a)
return
self.status['text'] = 'File successfully decrypted.\n'+\
'Close this window or decrypt another pdf file.'
return
def decryptBook(keypath, inpath, outpath):
def decryptBook(userkey, inpath, outpath):
if RSA is None:
raise ADEPTError(u"PyCrypto or OpenSSL must be installed.")
with open(inpath, 'rb') as inf:
try:
serializer = PDFSerializer(inf, keypath)
serializer = PDFSerializer(inf, userkey)
except:
print "Error serializing pdf. Probably wrong key."
return 1
print u"Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath))
return 2
# hope this will fix the 'bad file descriptor' problem
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
try:
serializer.dump(outf)
except:
print "error writing pdf."
return 1
except Exception, e:
print u"error writing pdf: {0}".format(e.args[0])
return 2
return 0
def cli_main(argv=sys.argv):
def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
if RSA is None:
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
"separately. Read the top-of-script comment for details." % \
(progname,)
return 1
if len(argv) != 4:
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
print u"usage: {0} <keyfile.der> <inbook.pdf> <outbook.pdf>".format(progname)
return 1
keypath, inpath, outpath = argv[1:]
return decryptBook(keypath, inpath, outpath)
userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath)
if result == 0:
print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
return result
def gui_main():
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Select files for decryption")
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Key file").grid(row=0)
self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists(u"adeptkey.der"):
self.keypath.insert(0, u"adeptkey.der")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
button.grid(row=0, column=2)
Tkinter.Label(body, text=u"Input file").grid(row=1)
self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
button.grid(row=1, column=2)
Tkinter.Label(body, text=u"Output file").grid(row=2)
self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
button.grid(row=2, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
buttons, text=u"Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select Adobe Adept \'.der\' key file",
defaultextension=u".der",
filetypes=[('Adobe Adept DER-encoded files', '.der'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
def get_inpath(self):
inpath = tkFileDialog.askopenfilename(
parent=None, title=u"Select ADEPT-encrypted PDF file to decrypt",
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
if inpath:
inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END)
self.inpath.insert(0, inpath)
return
def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select unencrypted PDF file to produce",
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return
def decrypt(self):
keypath = self.keypath.get()
inpath = self.inpath.get()
outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath):
self.status['text'] = u"Specified key file does not exist"
return
if not inpath or not os.path.exists(inpath):
self.status['text'] = u"Specified input file does not exist"
return
if not outpath:
self.status['text'] = u"Output file not specified"
return
if inpath == outpath:
self.status['text'] = u"Must have different input and output files"
return
userkey = open(keypath,'rb').read()
self.status['text'] = u"Decrypting..."
try:
decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception, e:
self.status['text'] = u"Error; {0}".format(e.args[0])
return
if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted"
else:
self.status['text'] = u"The was an error decrypting the file."
root = Tkinter.Tk()
if RSA is None:
root.withdraw()
@@ -2241,7 +2308,7 @@ def gui_main():
"This script requires OpenSSL or PyCrypto, which must be installed "
"separately. Read the top-of-script comment for details.")
return 1
root.title('INEPT PDF Decrypter')
root.title(u"Adobe Adept PDF Decrypter v.{0}".format(__version__))
root.resizable(True, False)
root.minsize(370, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
@@ -2251,5 +2318,7 @@ def gui_main():
if __name__ == '__main__':
if len(sys.argv) > 1:
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(gui_main())

View File

@@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# standlone set of Mac OSX specific routines needed for KindleBooks
from __future__ import with_statement
@@ -22,7 +25,7 @@ def _load_crypto_libcrypto():
libcrypto = find_library('crypto')
if libcrypto is None:
raise DrmException('libcrypto not found')
raise DrmException(u"libcrypto not found")
libcrypto = CDLL(libcrypto)
# From OpenSSL's crypto aes header
@@ -80,14 +83,14 @@ def _load_crypto_libcrypto():
def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise DrmException('AES improper key used')
raise DrmException(u"AES improper key used")
return
keyctx = self._keyctx = AES_KEY()
self._iv = iv
self._userkey = userkey
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0:
raise DrmException('Failed to initialize AES key')
raise DrmException(u"Failed to initialize AES key")
def decrypt(self, data):
out = create_string_buffer(len(data))
@@ -95,7 +98,7 @@ def _load_crypto_libcrypto():
keyctx = self._keyctx
rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
if rv == 0:
raise DrmException('AES decryption failed')
raise DrmException(u"AES decryption failed")
return out.raw
def keyivgen(self, passwd, salt, iter, keylen):
@@ -139,20 +142,20 @@ def SHA256(message):
return ctx.digest()
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
charMap2 = 'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
# 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
charMap5 = charMap2
# new in K4M 1.9.X
testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
testMap8 = 'YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD'
def encode(data, map):
result = ""
result = ''
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
@@ -167,14 +170,14 @@ def encodeHash(data,map):
# Decode the string in data with the characters in map. Returns the decoded bytes
def decode(data,map):
result = ""
result = ''
for i in range (0,len(data)-1,2):
high = map.find(data[i])
low = map.find(data[i+1])
if (high == -1) or (low == -1) :
break
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
result += pack("B",value)
result += pack('B',value)
return result
# For K4M 1.6.X and later
@@ -200,7 +203,7 @@ def primes(n):
# uses a sub process to get the Hard Drive Serial Number using ioreg
# returns with the serial number of drive whose BSD Name is "disk0"
# returns with the serial number of drive whose BSD Name is 'disk0'
def GetVolumeSerialNumber():
sernum = os.getenv('MYSERIALNUMBER')
if sernum != None:
@@ -216,11 +219,11 @@ def GetVolumeSerialNumber():
foundIt = False
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('"Serial Number" = "')
pp = resline.find('\"Serial Number\" = \"')
if pp >= 0:
sernum = resline[pp+19:-1]
sernum = sernum.strip()
bb = resline.find('"BSD Name" = "')
bb = resline.find('\"BSD Name\" = \"')
if bb >= 0:
bsdname = resline[bb+14:-1]
bsdname = bsdname.strip()
@@ -233,7 +236,7 @@ def GetVolumeSerialNumber():
def GetUserHomeAppSupKindleDirParitionName():
home = os.getenv('HOME')
dpath = home + '/Library/Application Support/Kindle'
dpath = home + '/Library'
cmdline = '/sbin/mount'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
@@ -277,7 +280,7 @@ def GetDiskPartitionUUID(diskpart):
nest += 1
if resline.find('}') >= 0:
nest -= 1
pp = resline.find('"UUID" = "')
pp = resline.find('\"UUID\" = \"')
if pp >= 0:
uuidnum = resline[pp+10:-1]
uuidnum = uuidnum.strip()
@@ -285,7 +288,7 @@ def GetDiskPartitionUUID(diskpart):
if partnest == uuidnest and uuidnest > 0:
foundIt = True
break
bb = resline.find('"BSD Name" = "')
bb = resline.find('\"BSD Name\" = \"')
if bb >= 0:
bsdname = resline[bb+14:-1]
bsdname = bsdname.strip()
@@ -323,7 +326,7 @@ def GetMACAddressMunged():
if pp >= 0:
macnum = resline[pp+6:-1]
macnum = macnum.strip()
# print "original mac", macnum
# print 'original mac', macnum
# now munge it up the way Kindle app does
# by xoring it with 0xa5 and swapping elements 3 and 4
maclst = macnum.split(':')
@@ -340,7 +343,7 @@ def GetMACAddressMunged():
mlst[2] = maclst[2] ^ 0xa5
mlst[1] = maclst[1] ^ 0xa5
mlst[0] = maclst[0] ^ 0xa5
macnum = "%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x" % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
macnum = '%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x' % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
foundIt = True
break
if not foundIt:
@@ -358,11 +361,28 @@ def isNewInstall():
# soccer game fan anyone
dpath = home + '/Library/Application Support/Kindle/storage/.pes2011'
# 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):
return True
return False
class Memoize:
"""Memoize(fn) - an instance which acts like fn but memoizes its arguments
Will only work on functions with non-mutable arguments
"""
def __init__(self, fn):
self.fn = fn
self.memo = {}
def __call__(self, *args):
if not self.memo.has_key(args):
self.memo[args] = self.fn(*args)
return self.memo[args]
@Memoize
def GetIDString():
# K4Mac now has an extensive set of ids strings it uses
# in encoding pids and in creating unique passwords
@@ -490,51 +510,44 @@ class CryptUnprotectDataV3(object):
# Locate the .kindle-info files
def getKindleInfoFiles(kInfoFiles):
# first search for current .kindle-info files
home = os.getenv('HOME')
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
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')
kinfopath = 'NONE'
def getKindleInfoFiles():
# file searches can take a long time on some systems, so just look in known specific places.
kInfoFiles=[]
found = False
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found K4Mac kindle-info file: ' + resline)
found = True
# add any .rainier*-kinf files
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
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 kinf file: ' + resline)
found = True
# add 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
home = os.getenv('HOME')
# check for .kinf2011 file in new location (App Store Kindle for Mac)
testpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.kinf2011'
if os.path.isfile(testpath):
kInfoFiles.append(testpath)
print('Found k4Mac kinf2011 file: ' + testpath)
found = True
# check for .kinf2011 files
testpath = home + '/Library/Application Support/Kindle/storage/.kinf2011'
if os.path.isfile(testpath):
kInfoFiles.append(testpath)
print('Found k4Mac kinf2011 file: ' + testpath)
found = True
# check for .rainier-2.1.1-kinf files
testpath = home + '/Library/Application Support/Kindle/storage/.rainier-2.1.1-kinf'
if os.path.isfile(testpath):
kInfoFiles.append(testpath)
print('Found k4Mac rainier file: ' + testpath)
found = True
# check for .rainier-2.1.1-kinf files
testpath = home + '/Library/Application Support/Kindle/storage/.kindle-info'
if os.path.isfile(testpath):
kInfoFiles.append(testpath)
print('Found k4Mac kindle-info file: ' + testpath)
found = True
if not found:
print('No k4Mac kindle-info/kinf/kinf2011 files have been found.')
print('No k4Mac kindle-info/rainier/kinf2011 files have been found.')
return kInfoFiles
# determine type of kindle info provided and return a
# database of keynames and values
def getDBfromFile(kInfoFile):
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF']
DB = {}
cnt = 0
infoReader = open(kInfoFile, 'r')
@@ -549,12 +562,12 @@ def getDBfromFile(kInfoFile):
for item in items:
if item != '':
keyhash, rawdata = item.split(':')
keyname = "unknown"
keyname = 'unknown'
for name in names:
if encodeHash(name,charMap2) == keyhash:
keyname = name
break
if keyname == "unknown":
if keyname == 'unknown':
keyname = keyhash
encryptedValue = decode(rawdata,charMap2)
cleartext = cud.decrypt(encryptedValue)
@@ -567,8 +580,8 @@ def getDBfromFile(kInfoFile):
if hdr == '/':
# 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
# 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()
@@ -582,11 +595,11 @@ def getDBfromFile(kInfoFile):
# 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"
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' not used for K4Mac only K4PC
# entropy = SHA1(keyhash)
# the remainder of the first record when decoded with charMap5
@@ -603,12 +616,12 @@ def getDBfromFile(kInfoFile):
item = items.pop(0)
edlst.append(item)
keyname = "unknown"
keyname = 'unknown'
for name in names:
if encodeHash(name,charMap5) == keyhash:
keyname = name
break
if keyname == "unknown":
if keyname == 'unknown':
keyname = keyhash
# the charMap5 encoded contents data has had a length
@@ -619,10 +632,10 @@ def getDBfromFile(kInfoFile):
# 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)
# (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)
encdata = ''.join(edlst)
contlen = len(encdata)
# now properly split and recombine
@@ -671,7 +684,7 @@ def getDBfromFile(kInfoFile):
# 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"
keyname = 'unknown'
# unlike K4PC the keyhash is not used in generating entropy
# entropy = SHA1(keyhash) + added_entropy
@@ -691,12 +704,12 @@ def getDBfromFile(kInfoFile):
item = items.pop(0)
edlst.append(item)
keyname = "unknown"
keyname = 'unknown'
for name in names:
if encodeHash(name,testMap8) == keyhash:
keyname = name
break
if keyname == "unknown":
if keyname == 'unknown':
keyname = keyhash
# the testMap8 encoded contents data has had a length
@@ -707,10 +720,10 @@ def getDBfromFile(kInfoFile):
# The offset into the testMap8 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)
# (in other words split 'about' 2/3rds of the way through)
# move first offsets chars to end to align for decode by testMap8
encdata = "".join(edlst)
encdata = ''.join(edlst)
contlen = len(encdata)
# now properly split and recombine

View File

@@ -1,4 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# K4PC Windows specific routines
from __future__ import with_statement
@@ -203,46 +205,64 @@ CryptUnprotectData = CryptUnprotectData()
# Locate all of the kindle-info style files and return as list
def getKindleInfoFiles(kInfoFiles):
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
def getKindleInfoFiles():
kInfoFiles = []
# 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
path = ""
if 'LOCALAPPDATA' in os.environ.keys():
path = os.environ['LOCALAPPDATA']
else:
# User Shell Folders show take precedent over Shell Folders if present
try:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
if not os.path.isdir(path):
path = ""
try:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
if not os.path.isdir(path):
path = ""
except RegError:
pass
except RegError:
pass
print('searching for kinfoFiles in ' + path)
found = False
if path == "":
print ('Could not find the folder in which to look for kinfoFiles.')
else:
print('searching for kinfoFiles in ' + path)
# first look for older kindle-info files
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if os.path.isfile(kinfopath):
found = True
print('Found K4PC kindle.info file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# first look for older kindle-info files
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if os.path.isfile(kinfopath):
found = True
print('Found K4PC kindle.info file: ' + 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'
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.5.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath)
kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.5.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# 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'
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.6.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# 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'
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.6.X kinf file: ' + 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)
# 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.')

View File

@@ -1,5 +1,11 @@
#!/usr/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# mobidedrm.py, version 0.38
# Copyright © 2008 The Dark Reverser
#
# Modified 20082012 by some_updates, DiapDealer and Apprentice Alf
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
@@ -59,26 +65,78 @@
# 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
# 0.38 - Unicode used wherever possible, cope with absent alfcrypto
__version__ = '0.37'
__version__ = u"0.38"
import sys
import os
import struct
import binascii
try:
from alfcrypto import Pukall_Cipher
except:
print u"AlfCrypto not found. Using python PC1 implementation."
class Unbuffered:
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
sys.stdout=Unbuffered(sys.stdout)
import os
import struct
import binascii
from alfcrypto import Pukall_Cipher
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = 'utf-8'
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
class DrmException(Exception):
pass
@@ -90,40 +148,45 @@ class DrmException(Exception):
# Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True):
return Pukall_Cipher().PC1(key,src,decryption)
# 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
# if we can get it from alfcrypto, use that
try:
return Pukall_Cipher().PC1(key,src,decryption)
except NameError:
pass
# use slow python version, since Pukall_Cipher didn't load
sum1 = 0;
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
DrmException (u"PC1: Bad key length")
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
def checksumPid(s):
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
crc = crc ^ (crc >> 16)
res = s
@@ -171,17 +234,24 @@ class MobiBook:
off = self.sections[section][0]
return self.data_file[off:endoff]
def __init__(self, infile, announce = True):
if announce:
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
def cleanup(self):
# to match function in Topaz book
pass
def __init__(self, infile):
print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
try:
from alfcrypto import Pukall_Cipher
except:
print u"AlfCrypto not found. Using python PC1 implementation."
# initial sanity check on file
self.data_file = file(infile, 'rb').read()
self.mobi_data = ''
self.header = self.data_file[0:78]
if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
raise DrmException("invalid file format")
raise DrmException(u"Invalid file format")
self.magic = self.header[0x3C:0x3C+8]
self.crypto_type = -1
@@ -199,7 +269,7 @@ class MobiBook:
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic
print u"PalmDoc format book detected."
self.extra_data_flags = 0
self.mobi_length = 0
self.mobi_codepage = 1252
@@ -209,11 +279,11 @@ class MobiBook:
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
print u"MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length)
self.extra_data_flags = 0
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags
print u"Extra Data Flags: {0:d}".format(self.extra_data_flags)
if (self.compression != 17480):
# multibyte utf8 data is included in the encryption for PalmDoc compression
# so clear that byte so that we leave it to be decrypted.
@@ -223,10 +293,10 @@ class MobiBook:
self.meta_array = {}
try:
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
exth = 'NONE'
exth = ''
if exth_flag & 0x40:
exth = self.sect[16 + self.mobi_length:]
if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
if (len(exth) >= 12) and (exth[:4] == 'EXTH'):
nitems, = struct.unpack('>I', exth[8:12])
pos = 12
for i in xrange(nitems):
@@ -236,10 +306,10 @@ class MobiBook:
# reset the text to speech flag and clipping limit, if present
if type == 401 and size == 9:
# set clipping limit to 100%
self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8)
self.patchSection(0, '\144', 16 + self.mobi_length + pos + 8)
elif type == 404 and size == 9:
# make sure text to speech is enabled
self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8)
self.patchSection(0, '\0', 16 + self.mobi_length + pos + 8)
# print type, size, content, content.encode('hex')
pos += size
except:
@@ -265,8 +335,8 @@ class MobiBook:
codec = codec_map[self.mobi_codepage]
if title == '':
title = self.header[:32]
title = title.split("\0")[0]
return unicode(title, codec).encode('utf-8')
title = title.split('\0')[0]
return unicode(title, codec)
def getPIDMetaInfo(self):
rec209 = ''
@@ -297,7 +367,7 @@ class MobiBook:
def parseDRM(self, data, count, pidlist):
found_key = None
keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
keyvec1 = '\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96'
for pid in pidlist:
bigpid = pid.ljust(16,'\0')
temp_key = PC1(keyvec1, bigpid, False)
@@ -315,7 +385,7 @@ class MobiBook:
break
if not found_key:
# Then try the default encoding that doesn't require a PID
pid = "00000000"
pid = '00000000'
temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff
for i in xrange(count):
@@ -328,82 +398,90 @@ class MobiBook:
break
return [found_key,pid]
def getMobiFile(self, outpath):
def getFile(self, outpath):
file(outpath,'wb').write(self.mobi_data)
def getMobiVersion(self):
return self.mobi_version
def getBookType(self):
if self.print_replica:
return u"Print Replica"
if self.mobi_version >= 8:
return u"Kindle Format 8"
return u"Mobipocket"
def getPrintReplica(self):
return self.print_replica
def getBookExtension(self):
if self.print_replica:
return u".azw4"
if self.mobi_version >= 8:
return u".azw3"
return u".mobi"
def processBook(self, pidlist):
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
print 'Crypto Type is: ', crypto_type
print u"Crypto Type is: {0:d}".format(crypto_type)
self.crypto_type = crypto_type
if crypto_type == 0:
print "This book is not encrypted."
print u"This book is not encrypted."
# we must still check for Print Replica
self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
self.mobi_data = self.data_file
return
if crypto_type != 2 and crypto_type != 1:
raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
raise DrmException(u"Cannot decode unknown Mobipocket encryption type {0:d}".format(crypto_type))
if 406 in self.meta_array:
data406 = self.meta_array[406]
val406, = struct.unpack('>Q',data406)
if val406 != 0:
raise DrmException("Cannot decode library or rented ebooks.")
raise DrmException(u"Cannot decode library or rented ebooks.")
goodpids = []
for pid in pidlist:
if len(pid)==10:
if checksumPid(pid[0:-2]) != pid:
print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2])
print u"Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2]))
goodpids.append(pid[0:-2])
elif len(pid)==8:
goodpids.append(pid)
if self.crypto_type == 1:
t1_keyvec = "QDCVEPMU675RUBSZ"
t1_keyvec = 'QDCVEPMU675RUBSZ'
if self.magic == 'TEXtREAd':
bookkey_data = self.sect[0x0E:0x0E+16]
elif self.mobi_version < 0:
bookkey_data = self.sect[0x90:0x90+16]
else:
bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32]
pid = "00000000"
pid = '00000000'
found_key = PC1(t1_keyvec, bookkey_data)
else :
# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
if drm_count == 0:
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
raise DrmException(u"Encryption not initialised. Must be opened with Mobipocket Reader first.")
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key:
raise DrmException("No key found in " + str(len(goodpids)) + " keys tried. Please report this failure for help.")
raise DrmException(u"No key found in {0:d} keys tried. Read the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(len(goodpids)))
# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
self.patchSection(0, '\0' * drm_size, drm_ptr)
# kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
self.patchSection(0, '\xff' * 4 + '\0' * 12, 0xA8)
if pid=="00000000":
print "File has default encryption, no specific PID."
if pid=='00000000':
print u"File has default encryption, no specific key needed."
else:
print "File is encoded with PID "+checksumPid(pid)+"."
print u"File is encoded with PID {0}.".format(checksumPid(pid))
# clear the crypto type
self.patchSection(0, "\0" * 2, 0xC)
# decrypt sections
print "Decrypting. Please wait . . .",
print u"Decrypting. Please wait . . .",
mobidataList = []
mobidataList.append(self.data_file[:self.sections[1][0]])
for i in xrange(1, self.records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
if i%100 == 0:
print ".",
print u".",
# print "record %d, extra_size %d" %(i,extra_size)
decoded_data = PC1(found_key, data[0:len(data) - extra_size])
if i==1:
@@ -414,31 +492,24 @@ class MobiBook:
if self.num_sections > self.records+1:
mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
self.mobi_data = "".join(mobidataList)
print "done"
print u"done"
return
def getUnencryptedBook(infile,pid,announce=True):
def getUnencryptedBook(infile,pidlist):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile,announce)
book.processBook([pid])
return book.mobi_data
def getUnencryptedBookWithList(infile,pidlist,announce=True):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile, announce)
raise DrmException(u"Input File Not Found.")
book = MobiBook(infile)
book.processBook(pidlist)
return book.mobi_data
def main(argv=sys.argv):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
if len(argv)<3 or len(argv)>4:
print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
print "Usage:"
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
print u"MobiDeDrm v{0}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
print u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
print u"Usage:"
print u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(os.path.basename(sys.argv[0]))
return 1
else:
infile = argv[1]
@@ -446,15 +517,17 @@ def main(argv=sys.argv):
if len(argv) is 4:
pidlist = argv[3].split(',')
else:
pidlist = {}
pidlist = []
try:
stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
stripped_file = getUnencryptedBook(infile, pidlist)
file(outfile, 'wb').write(stripped_file)
except DrmException, e:
print "Error: %s" % e
print u"MobiDeDRM v{0} Error: {0:s}".format(__version__,e.args[0])
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
if __name__ == '__main__':
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())

View File

@@ -9,12 +9,13 @@
\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.\
\cf0 DeDRM is an application that packs all of the python dm-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:\
To remove the DRM of Kindle ebooks from eInk Kindles, eReader pdb ebooks, Barnes&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\
eInk Kindle (not Kindle Fire): 16 digit Serial Number\
Kindle for iOS: 40 digit UDID, but this probably won't work anymore\
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\
@@ -23,14 +24,24 @@ A final preference is the destination folder for the DRM-free copies of your ebo
\
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. \
This program requires Mac OS X 10.4 or above. It will not work on Mac OS X 10.3 or earlier.\
\
\
\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_vX.X\\DeDRM_Applications\\Macintosh (the location of this ReadMe) to your Applications folder, or anywhere else you find convenient.\
Mac OS X 10.4
\i only
\i0 : You
\i must
\i0 first install Python 2.7.3 from http://python.org/. At the time of writing, the direct download link is http://www.python.org/ftp/python/2.7.3/python-2.7.3-macosx10.3.dmg.\
\
Mac OS X 10.5 and above: You do
\i not
\i0 need to install Python.\
\
Drag the DeDRM application from from tools_v5.5\\DeDRM_Applications\\Macintosh (the location of this ReadMe) to your Applications folder, or anywhere else you find convenient.\
\
\
@@ -42,5 +53,6 @@ Drag the DeDRM application from from tools_vX.X\\DeDRM_Applications\\Macintosh (
\
\b Troubleshooting\
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural
\b0 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.}
\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,31 +24,33 @@
<key>CFBundleExecutable</key>
<string>droplet</string>
<key>CFBundleGetInfoString</key>
<string>DeDRM 5.2, Written 20102012 by Apprentice Alf and others.</string>
<string>DeDRM 5.5. AppleScript written 20102012 by Apprentice Alf and others.</string>
<key>CFBundleIconFile</key>
<string>DeDRM</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>DeDRM 5.2</string>
<string>DeDRM 5.5</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>5.2</string>
<string>5.5</string>
<key>CFBundleSignature</key>
<string>dplt</string>
<key>LSMinimumSystemVersion</key>
<string>10.5.0</string>
<key>LSRequiresCarbon</key>
<true/>
<key>WindowState</key>
<dict>
<key>dividerCollapsed</key>
<true/>
<key>eventLogLevel</key>
<integer>-1</integer>
<key>name</key>
<string>ScriptWindowState</string>
<key>positionOfDivider</key>
<real>554</real>
<real>0</real>
<key>savedFrame</key>
<string>42 60 922 818 0 0 1440 878 </string>
<string>1846 -16 800 473 1440 -180 1920 1080 </string>
<key>selectedTabView</key>
<string>event log</string>
</dict>

View File

@@ -27,15 +27,15 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>DTCompiler</key>
<string></string>
<string>4.0</string>
<key>DTPlatformBuild</key>
<string>10M2518</string>
<key>DTPlatformVersion</key>
<string>PG</string>
<key>DTSDKBuild</key>
<string>9L31a</string>
<string>8S2167</string>
<key>DTSDKName</key>
<string>macosx10.5</string>
<string>macosx10.4</string>
<key>DTXcode</key>
<string>0400</string>
<key>DTXcodeBuild</key>

View File

@@ -1,11 +1,18 @@
#! /usr/bin/env python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# crypto library mainly by some_updates
# pbkdf2.py pbkdf2 code taken from pbkdf2.py
# pbkdf2.py Copyright © 2004 Matt Johnston <matt @ ucc asn au>
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
# pbkdf2.py This code may be freely used and modified for any purpose.
import sys, os
import hmac
from struct import pack
import hashlib
# interface to needed routines libalfcrypto
def _load_libalfcrypto():
import ctypes
@@ -27,7 +34,7 @@ def _load_libalfcrypto():
else:
name_of_lib = 'libalfcrypto64.so'
libalfcrypto = sys.path[0] + os.sep + name_of_lib
libalfcrypto = os.path.join(sys.path[0],name_of_lib)
if not os.path.isfile(libalfcrypto):
raise Exception('libalfcrypto not found')
@@ -147,7 +154,7 @@ def _load_libalfcrypto():
topazCryptoDecrypt(ctx, data, out, len(data))
return out.raw
print "Using Library AlfCrypto DLL/DYLIB/SO"
print u"Using Library AlfCrypto DLL/DYLIB/SO"
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
@@ -164,8 +171,7 @@ def _load_python_alfcrypto():
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
print "Bad key length!"
return None
raise Exception('Pukall_Cipher: Bad key length.')
wkey = []
for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
@@ -234,6 +240,7 @@ def _load_python_alfcrypto():
cleartext = self.aes.decrypt(iv + data)
return cleartext
print u"Using Library AlfCrypto Python"
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)

View File

@@ -1,899 +0,0 @@
#! /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

@@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from PyQt4.Qt import QWidget, QVBoxLayout, QLabel, QLineEdit
from calibre.utils.config import JSONConfig
@@ -20,7 +23,7 @@ class ConfigWidget(QWidget):
self.l = QVBoxLayout()
self.setLayout(self.l)
self.serialLabel = QLabel('Kindle Serial numbers (separate with commas, no spaces)')
self.serialLabel = QLabel('eInk Kindle Serial numbers (First character B, 16 characters, use commas if more than one)')
self.l.addWidget(self.serialLabel)
self.serials = QLineEdit(self)
@@ -28,7 +31,7 @@ class ConfigWidget(QWidget):
self.l.addWidget(self.serials)
self.serialLabel.setBuddy(self.serials)
self.pidLabel = QLabel('Mobipocket PIDs (separate with commas, no spaces)')
self.pidLabel = QLabel('Mobipocket PIDs (8 or 10 characters, use commas if more than one)')
self.l.addWidget(self.pidLabel)
self.pids = QLineEdit(self)
@@ -50,8 +53,8 @@ class ConfigWidget(QWidget):
self.wpLabel.setBuddy(self.wineprefix)
def save_settings(self):
prefs['pids'] = str(self.pids.text())
prefs['serials'] = str(self.serials.text())
prefs['pids'] = str(self.pids.text()).replace(" ","")
prefs['serials'] = str(self.serials.text()).replace(" ","")
winepref=str(self.wineprefix.text())
if winepref.strip() != '':
prefs['WINEPREFIX'] = winepref

View File

@@ -230,6 +230,7 @@ class PageParser(object):
'empty' : (1, 'snippets', 1, 0),
'page' : (1, 'snippets', 1, 0),
'page.class' : (1, 'scalar_text', 0, 0),
'page.pageid' : (1, 'scalar_text', 0, 0),
'page.pagelabel' : (1, 'scalar_text', 0, 0),
'page.type' : (1, 'scalar_text', 0, 0),
@@ -238,11 +239,13 @@ class PageParser(object):
'page.startID' : (1, 'scalar_number', 0, 0),
'group' : (1, 'snippets', 1, 0),
'group.class' : (1, 'scalar_text', 0, 0),
'group.type' : (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.class' : (1, 'scalar_text', 0, 0),
'region.type' : (1, 'scalar_text', 0, 0),
'region.x' : (1, 'scalar_number', 0, 0),
'region.y' : (1, 'scalar_number', 0, 0),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

After

Width:  |  Height:  |  Size: 362 B

View File

@@ -0,0 +1,169 @@
#!/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 drmcheck
# 1.00 - Initial version, with code from various other scripts
# 1.01 - Moved authorship announcement to usage section.
#
# Changelog drmcheck
# 1.00 - Cut to drmtest.py, testing ePub files only by Apprentice Alf
#
# Written in 2011 by Paul Durrant
# Released with unlicense. See http://unlicense.org/
#
#############################################################################
#
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
#############################################################################
#
# It's still polite to give attribution if you do reuse this code.
#
from __future__ import with_statement
__version__ = '1.00'
import sys, struct, os
import zlib
import zipfile
import xml.etree.ElementTree as etree
NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
def unicode_argv():
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
_FILENAME_LEN_OFFSET = 26
_EXTRA_LEN_OFFSET = 28
_FILENAME_OFFSET = 30
_MAX_SIZE = 64 * 1024
def uncompress(cmpdata):
dc = zlib.decompressobj(-15)
data = ''
while len(cmpdata) > 0:
if len(cmpdata) > _MAX_SIZE :
newdata = cmpdata[0:_MAX_SIZE]
cmpdata = cmpdata[_MAX_SIZE:]
else:
newdata = cmpdata
cmpdata = ''
newdata = dc.decompress(newdata)
unprocessed = dc.unconsumed_tail
if len(unprocessed) == 0:
newdata += dc.flush()
data += newdata
cmpdata += unprocessed
unprocessed = ''
return data
def getfiledata(file, zi):
# get file name length and exta data length to find start of file data
local_header_offset = zi.header_offset
file.seek(local_header_offset + _FILENAME_LEN_OFFSET)
leninfo = file.read(2)
local_name_length, = struct.unpack('<H', leninfo)
file.seek(local_header_offset + _EXTRA_LEN_OFFSET)
exinfo = file.read(2)
extra_field_length, = struct.unpack('<H', exinfo)
file.seek(local_header_offset + _FILENAME_OFFSET + local_name_length + extra_field_length)
data = None
# if not compressed we are good to go
if zi.compress_type == zipfile.ZIP_STORED:
data = file.read(zi.file_size)
# if compressed we must decompress it using zlib
if zi.compress_type == zipfile.ZIP_DEFLATED:
cmpdata = file.read(zi.compress_size)
data = uncompress(cmpdata)
return data
def main(argv=unicode_argv()):
infile = argv[1]
kind = "Unknown"
encryption = "Unknown"
with file(infile,'rb') as infileobject:
bookdata = infileobject.read(58)
# Check for Mobipocket/Kindle
if bookdata[0:0+2] == "PK":
if bookdata[30:30+28] == 'mimetypeapplication/epub+zip':
kind = "ePub"
else:
kind = "ZIP"
encryption = "Unencrypted"
foundrights = False
foundencryption = False
inzip = zipfile.ZipFile(infile,'r')
namelist = set(inzip.namelist())
if 'META-INF/rights.xml' not in namelist or 'META-INF/encryption.xml' not in namelist:
encryption = "Unencrypted"
else:
rights = etree.fromstring(inzip.read('META-INF/rights.xml'))
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
if len(bookkey) == 172:
encryption = "Adobe"
elif len(bookkey) == 64:
encryption = "B&N"
else:
encryption = "Unknown"
print u"{0} {1}".format(encryption, kind)
return 0
if __name__ == "__main__":
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(main())

View File

@@ -1,8 +1,11 @@
#!/usr/bin/env python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
#
# -*- coding: utf-8 -*-
# erdr2pml.py
# Copyright © 2008 The Dark Reverser
#
# Modified 20082012 by some_updates, DiapDealer and Apprentice Alf
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
# Changelog
@@ -62,19 +65,11 @@
# 0.21 - Support eReader (drm) version 11.
# - Don't reject dictionary format.
# - Ignore sidebars for dictionaries (different format?)
# 0.22 - Unicode and plugin support, different image folders for PMLZ and source
__version__='0.21'
__version__='0.22'
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
import sys, re
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile
if 'calibre' in sys.modules:
@@ -82,8 +77,66 @@ if 'calibre' in sys.modules:
else:
inCalibre = False
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
Des = None
if sys.platform.startswith('win'):
if iswindows:
# first try with pycrypto
if inCalibre:
from calibre_plugins.erdrpdb2pml import pycrypto_des
@@ -168,17 +221,30 @@ class Sectionizer(object):
off = self.sections[section][0]
return self.contents[off:end_off]
def sanitizeFileName(s):
r = ''
for c in s:
if c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-":
r += c
return r
# cleanup unicode filenames
# borrowed from calibre from calibre/src/calibre/__init__.py
# added in removal of control (<32) chars
# and removal of . at start and end
# and with some (heavily edited) code from Paul Durrant's kindlenamer.py
def sanitizeFileName(name):
# substitute filename unfriendly characters
name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" ").replace(u": ",u" ").replace(u":",u"").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'")
# delete control characters
name = u"".join(char for char in name if ord(char)>=32)
# white space to single space, delete leading and trailing while space
name = re.sub(ur"\s", u" ", name).strip()
# remove leading dots
while len(name)>0 and name[0] == u".":
name = name[1:]
# remove trailing dots (Windows doesn't like them)
if name.endswith(u'.'):
name = name[:-1]
return name
def fixKey(key):
def fixByte(b):
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):
r=''
@@ -191,7 +257,7 @@ def deXOR(text, sp, table):
return r
class EreaderProcessor(object):
def __init__(self, sect, username, creditcard):
def __init__(self, sect, user_key):
self.section_reader = sect.loadSection
data = self.section_reader(0)
version, = struct.unpack('>H', data[0:2])
@@ -212,18 +278,10 @@ class EreaderProcessor(object):
for i in xrange(len(data)):
j = (j + shuf) % len(data)
r[j] = data[i]
assert len("".join(r)) == len(data)
assert len("".join(r)) == len(data)
return "".join(r)
r = unshuff(input[0:-8], cookie_shuf)
def fixUsername(s):
r = ''
for c in s.lower():
if (c >= 'a' and c <= 'z' or c >= '0' and c <= '9'):
r += c
return r
user_key = struct.pack('>LL', binascii.crc32(fixUsername(username)) & 0xffffffff, binascii.crc32(creditcard[-8:])& 0xffffffff)
drm_sub_version = struct.unpack('>H', r[0:2])[0]
self.num_text_pages = struct.unpack('>H', r[2:4])[0] - 1
self.num_image_pages = struct.unpack('>H', r[26:26+2])[0]
@@ -302,7 +360,7 @@ class EreaderProcessor(object):
sect = self.section_reader(self.first_image_page + i)
name = sect[4:4+32].strip('\0')
data = sect[62:]
return sanitizeFileName(name), data
return sanitizeFileName(unicode(name,'windows-1252')), data
# def getChapterNamePMLOffsetData(self):
@@ -399,60 +457,53 @@ class EreaderProcessor(object):
return r
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
for k in xrange(128,256):
badChar = chr(k)
pml2 = pml2.replace(badChar, '\\a%03d' % k)
return pml2
def convertEreaderToPml(infile, name, cc, outdir):
if not os.path.exists(outdir):
os.makedirs(outdir)
def decryptBook(infile, outpath, make_pmlz, user_key):
bookname = os.path.splitext(os.path.basename(infile))[0]
print " Decoding File"
sect = Sectionizer(infile, 'PNRdPPrs')
er = EreaderProcessor(sect, name, cc)
if er.getNumImages() > 0:
print " Extracting images"
imagedir = bookname + '_img/'
imagedirpath = os.path.join(outdir,imagedir)
if not os.path.exists(imagedirpath):
os.makedirs(imagedirpath)
for i in xrange(er.getNumImages()):
name, contents = er.getImage(i)
file(os.path.join(imagedirpath, name), 'wb').write(contents)
print " Extracting pml"
pml_string = er.getText()
pmlfilename = bookname + ".pml"
file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
# bkinfo = er.getBookInfo()
# if bkinfo != '':
# print " Extracting book meta information"
# file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo)
def decryptBook(infile, outdir, name, cc, make_pmlz):
if make_pmlz :
# ignore specified outdir, use tempdir instead
if make_pmlz:
# outpath is actually pmlz name
pmlzname = outpath
outdir = tempfile.mkdtemp()
imagedirpath = os.path.join(outdir,u"images")
else:
pmlzname = None
outdir = outpath
imagedirpath = os.path.join(outdir,bookname + u"_img")
try:
print "Processing..."
convertEreaderToPml(infile, name, cc, outdir)
if make_pmlz :
if not os.path.exists(outdir):
os.makedirs(outdir)
print u"Decoding File"
sect = Sectionizer(infile, 'PNRdPPrs')
er = EreaderProcessor(sect, user_key)
if er.getNumImages() > 0:
print u"Extracting images"
if not os.path.exists(imagedirpath):
os.makedirs(imagedirpath)
for i in xrange(er.getNumImages()):
name, contents = er.getImage(i)
file(os.path.join(imagedirpath, name), 'wb').write(contents)
print u"Extracting pml"
pml_string = er.getText()
pmlfilename = bookname + ".pml"
file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
if pmlzname is not None:
import zipfile
import shutil
print " Creating PMLZ file"
zipname = infile[:-4] + '.pmlz'
myZipFile = zipfile.ZipFile(zipname,'w',zipfile.ZIP_STORED, False)
print u"Creating PMLZ file {0}".format(os.path.basename(pmlzname))
myZipFile = zipfile.ZipFile(pmlzname,'w',zipfile.ZIP_STORED, False)
list = os.listdir(outdir)
for file in list:
localname = file
filePath = os.path.join(outdir,file)
for filename in list:
localname = filename
filePath = os.path.join(outdir,filename)
if os.path.isfile(filePath):
myZipFile.write(filePath, localname)
elif os.path.isdir(filePath):
@@ -466,36 +517,46 @@ def decryptBook(infile, outdir, name, cc, make_pmlz):
myZipFile.close()
# remove temporary directory
shutil.rmtree(outdir, True)
print 'output is %s' % zipname
print u"Output is {0}".format(pmlzname)
else :
print 'output in %s' % outdir
print u"Output is in {0}".format(outdir)
print "done"
except ValueError, e:
print "Error: %s" % e
print u"Error: {0}".format(e.args[0])
return 1
return 0
def usage():
print "Converts DRMed eReader books to PML Source"
print "Usage:"
print " erdr2pml [options] infile.pdb [outdir] \"your name\" credit_card_number "
print " "
print "Options: "
print " -h prints this message"
print " --make-pmlz create PMLZ instead of using output directory"
print " "
print "Note:"
print " if ommitted, outdir defaults based on 'infile.pdb'"
print " It's enough to enter the last 8 digits of the credit card number"
print u"Converts DRMed eReader books to PML Source"
print u"Usage:"
print u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number"
print u" "
print u"Options: "
print u" -h prints this message"
print u" -p create PMLZ instead of source folder"
print u" --make-pmlz create PMLZ instead of source folder"
print u" "
print u"Note:"
print u" if outpath is ommitted, creates source in 'infile_Source' folder"
print u" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'"
print u" if source folder created, images are in infile_img folder"
print u" if pmlz file created, images are in images folder"
print u" It's enough to enter the last 8 digits of the credit card number"
return
def getuser_key(name,cc):
newname = "".join(c for c in name.lower() if c >= 'a' and c <= 'z' or c >= '0' and c <= '9')
cc = cc.replace(" ","")
return struct.pack('>LL', binascii.crc32(newname) & 0xffffffff,binascii.crc32(cc[-8:])& 0xffffffff)
def cli_main(argv=unicode_argv()):
print u"eRdr2Pml v{0}. Copyright © 20092012 The Dark Reverser et al.".format(__version__)
def main(argv=None):
try:
opts, args = getopt.getopt(sys.argv[1:], "h", ["make-pmlz"])
opts, args = getopt.getopt(argv[1:], "hp", ["make-pmlz"])
except getopt.GetoptError, err:
print str(err)
print err.args[0]
usage()
return 1
make_pmlz = False
@@ -503,24 +564,31 @@ def main(argv=None):
if o == "-h":
usage()
return 0
elif o == "-p":
make_pmlz = True
elif o == "--make-pmlz":
make_pmlz = True
print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__
if len(args)!=3 and len(args)!=4:
usage()
return 1
if len(args)==3:
infile, name, cc = args[0], args[1], args[2]
outdir = infile[:-4] + '_Source'
infile, name, cc = args
if make_pmlz:
outpath = os.path.splitext(infile)[0] + u".pmlz"
else:
outpath = os.path.splitext(infile)[0] + u"_Source"
elif len(args)==4:
infile, outdir, name, cc = args[0], args[1], args[2], args[3]
infile, outpath, name, cc = args
return decryptBook(infile, outdir, name, cc, make_pmlz)
print getuser_key(name,cc).encode('hex')
return decryptBook(infile, outpath, make_pmlz, getuser_key(name,cc))
if __name__ == "__main__":
sys.stdout=Unbuffered(sys.stdout)
sys.exit(main())
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())

View File

@@ -5,8 +5,9 @@
#
# Changelog
# 1.00 - Initial version
# 1.01 - getPidList interface change
__version__ = '1.00'
__version__ = '1.01'
import sys
@@ -46,12 +47,12 @@ def getK4PCpids(path_to_ebook):
md1, md2 = mb.getPIDMetaInfo()
return kgenpids.getPidList(md1, md2, True, [], [], [])
return kgenpids.getPidList(md1, md2)
def main(argv=sys.argv):
print ('getk4pcpids.py v%(__version__)s. '
'Copyright 2012 Apprentic Alf' % globals())
'Copyright 2012 Apprentice Alf' % globals())
if len(argv)<2 or len(argv)>3:
print "Gets the possible book-specific PIDs from K4PC for a particular book"

View File

@@ -1,13 +1,25 @@
#! /usr/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
# ignobleepub.pyw, version 3.4
# ignobleepub.pyw, version 3.6
# Copyright © 2009-2010 by i♥cabbages
# To run this program install Python 2.6 from <http://www.python.org/download/>
# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# (make sure to install the version for Python 2.6). Save this script file as
# ignobleepub.pyw and double-click on it to run it.
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102012 by some_updates, DiapDealer and Apprentice Alf
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
# install the version for Python 2.6). Save this script file as
# ineptepub.pyw and double-click on it to run it.
#
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
# program from the command line (pythonw ineptepub.pyw) or by double-clicking
# it when it has been associated with PythonLauncher.
# Revision history:
# 1 - Initial release
@@ -17,21 +29,84 @@ from __future__ import with_statement
# 3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml
# 3.3 - On Windows try PyCrypto first and OpenSSL next
# 3.4 - Modify interace to allow use with import
# 3.5 - Fix for potential problem with PyCrypto
# 3.6 - Revised to allow use in calibre plugins to eliminate need for duplicate code
"""
Decrypt Barnes & Noble encrypted ePub books.
"""
__license__ = 'GPL v3'
__version__ = "3.6"
import sys
import os
import traceback
import zlib
import zipfile
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
from contextlib import closing
import xml.etree.ElementTree as etree
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
try:
from calibre.constants import iswindows, isosx
except:
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
return [u"ineptepub.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
class IGNOBLEError(Exception):
pass
@@ -41,10 +116,11 @@ def _load_crypto_libcrypto():
Structure, c_ulong, create_string_buffer, cast
from ctypes.util import find_library
if sys.platform.startswith('win'):
if iswindows:
libcrypto = find_library('libeay32')
else:
libcrypto = find_library('crypto')
if libcrypto is None:
raise IGNOBLEError('libcrypto not found')
libcrypto = CDLL(libcrypto)
@@ -65,9 +141,6 @@ def _load_crypto_libcrypto():
func.argtypes = argtypes
return func
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])
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
@@ -100,7 +173,7 @@ def _load_crypto_pycrypto():
class AES(object):
def __init__(self, key):
self._aes = _AES.new(key, _AES.MODE_CBC)
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
def decrypt(self, data):
return self._aes.decrypt(data)
@@ -122,13 +195,6 @@ def _load_crypto():
AES = _load_crypto()
"""
Decrypt Barnes & Noble ADEPT encrypted EPUB books.
"""
META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
@@ -143,7 +209,6 @@ class ZipInfo(zipfile.ZipInfo):
class Decryptor(object):
def __init__(self, bookkey, encryption):
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
# self._aes = AES.new(bookkey, AES.MODE_CBC)
self._aes = AES(bookkey)
encryption = etree.fromstring(encryption)
self._encrypted = encrypted = set()
@@ -151,8 +216,8 @@ class Decryptor(object):
enc('CipherReference'))
for elem in encryption.findall(expr):
path = elem.get('URI', None)
path = path.encode('utf-8')
if path is not None:
path = path.encode('utf-8')
encrypted.add(path)
def decompress(self, bytes):
@@ -170,167 +235,186 @@ class Decryptor(object):
data = self.decompress(data)
return data
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text='Select files for decryption')
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text='Key file').grid(row=0)
self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists('bnepubkey.b64'):
self.keypath.insert(0, 'bnepubkey.b64')
button = Tkinter.Button(body, text="...", command=self.get_keypath)
button.grid(row=0, column=2)
Tkinter.Label(body, text='Input file').grid(row=1)
self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text="...", command=self.get_inpath)
button.grid(row=1, column=2)
Tkinter.Label(body, text='Output file').grid(row=2)
self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=2, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
buttons, text="Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
buttons, text="Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
parent=None, title='Select B&N EPUB key file',
defaultextension='.b64',
filetypes=[('base64-encoded files', '.b64'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
def get_inpath(self):
inpath = tkFileDialog.askopenfilename(
parent=None, title='Select B&N-encrypted EPUB file to decrypt',
defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
('All files', '.*')])
if inpath:
inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END)
self.inpath.insert(0, inpath)
return
def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename(
parent=None, title='Select unencrypted EPUB file to produce',
defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
('All files', '.*')])
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return
def decrypt(self):
keypath = self.keypath.get()
inpath = self.inpath.get()
outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath):
self.status['text'] = 'Specified key file does not exist'
return
if not inpath or not os.path.exists(inpath):
self.status['text'] = 'Specified input file does not exist'
return
if not outpath:
self.status['text'] = 'Output file not specified'
return
if inpath == outpath:
self.status['text'] = 'Must have different input and output files'
return
argv = [sys.argv[0], keypath, inpath, outpath]
self.status['text'] = 'Decrypting...'
try:
cli_main(argv)
except Exception, e:
self.status['text'] = 'Error: ' + str(e)
return
self.status['text'] = 'File successfully decrypted'
def decryptBook(keypath, inpath, outpath):
with open(keypath, 'rb') as f:
keyb64 = f.read()
key = keyb64.decode('base64')[:16]
# aes = AES.new(key, AES.MODE_CBC)
aes = AES(key)
# check file to make check whether it's probably an Adobe Adept encrypted ePub
def ignobleBook(inpath):
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist:
raise IGNOBLEError('%s: not an B&N ADEPT EPUB' % (inpath,))
return False
try:
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
if len(bookkey) == 64:
return True
except:
# if we couldn't check, assume it is
return True
return False
# return error code and error message duple
def decryptBook(keyb64, inpath, outpath):
if AES is None:
# 1 means don't try again
return (1, u"PyCrypto or OpenSSL must be installed.")
key = keyb64.decode('base64')[:16]
aes = AES(key)
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist:
return (1, u"Not a secure Barnes & Noble ePub.")
for name in META_NAMES:
namelist.remove(name)
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
bookkey = aes.decrypt(bookkey.decode('base64'))
bookkey = bookkey[:-ord(bookkey[-1])]
encryption = inf.read('META-INF/encryption.xml')
decryptor = Decryptor(bookkey[-16:], encryption)
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
outf.writestr(zi, inf.read('mimetype'))
for path in namelist:
data = inf.read(path)
outf.writestr(path, decryptor.decrypt(path, data))
return 0
try:
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
if len(bookkey) != 64:
return (1, u"Not a secure Barnes & Noble ePub.")
bookkey = aes.decrypt(bookkey.decode('base64'))
bookkey = bookkey[:-ord(bookkey[-1])]
encryption = inf.read('META-INF/encryption.xml')
decryptor = Decryptor(bookkey[-16:], encryption)
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
outf.writestr(zi, inf.read('mimetype'))
for path in namelist:
data = inf.read(path)
outf.writestr(path, decryptor.decrypt(path, data))
except Exception, e:
return (2, u"{0}.".format(e.args[0]))
return (0, u"Success")
def cli_main(argv=sys.argv):
def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
if AES is None:
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
"separately. Read the top-of-script comment for details." % \
(progname,)
return 1
if len(argv) != 4:
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
print u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname)
return 1
keypath, inpath, outpath = argv[1:]
return decryptBook(keypath, inpath, outpath)
userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath)
print result[1]
return result[0]
def gui_main():
import Tkinter
import Tkconstants
import tkFileDialog
import traceback
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Select files for decryption")
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Key file").grid(row=0)
self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists(u"bnepubkey.b64"):
self.keypath.insert(0, u"bnepubkey.b64")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
button.grid(row=0, column=2)
Tkinter.Label(body, text=u"Input file").grid(row=1)
self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
button.grid(row=1, column=2)
Tkinter.Label(body, text=u"Output file").grid(row=2)
self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
button.grid(row=2, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
buttons, text=u"Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select Barnes & Noble \'.b64\' key file",
defaultextension=u".b64",
filetypes=[('base64-encoded files', '.b64'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
def get_inpath(self):
inpath = tkFileDialog.askopenfilename(
parent=None, title=u"Select B&N-encrypted ePub file to decrypt",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
if inpath:
inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END)
self.inpath.insert(0, inpath)
return
def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select unencrypted ePub file to produce",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return
def decrypt(self):
keypath = self.keypath.get()
inpath = self.inpath.get()
outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath):
self.status['text'] = u"Specified key file does not exist"
return
if not inpath or not os.path.exists(inpath):
self.status['text'] = u"Specified input file does not exist"
return
if not outpath:
self.status['text'] = u"Output file not specified"
return
if inpath == outpath:
self.status['text'] = u"Must have different input and output files"
return
userkey = open(keypath,'rb').read()
self.status['text'] = u"Decrypting..."
try:
decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception, e:
self.status['text'] = u"Error: {0}".format(e.args[0])
return
if decrypt_status[0] == 0:
self.status['text'] = u"File successfully decrypted"
else:
self.status['text'] = decrypt_status[1]
root = Tkinter.Tk()
if AES is None:
root.withdraw()
tkMessageBox.showerror(
"Ignoble EPUB Decrypter",
"This script requires OpenSSL or PyCrypto, which must be installed "
"separately. Read the top-of-script comment for details.")
return 1
root.title('Ignoble EPUB Decrypter')
root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__))
root.resizable(True, False)
root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
root.mainloop()
return 0
if __name__ == '__main__':
if len(sys.argv) > 1:
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(gui_main())

View File

@@ -1,13 +1,25 @@
#! /usr/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
# ignoblekeygen.pyw, version 2.3
# ignoblekeygen.pyw, version 2.5
# Copyright © 2009-2010 by i♥cabbages
# To run this program install Python 2.6 from <http://www.python.org/download/>
# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# (make sure to install the version for Python 2.6). Save this script file as
# ignoblekeygen.pyw and double-click on it to run it.
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102012 by some_updates, DiapDealer and Apprentice Alf
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
# install the version for Python 2.6). Save this script file as
# ignoblekeygen.pyw and double-click on it to run it.
#
# Mac OS X users: Save this script file as ignoblekeygen.pyw. You can run this
# program from the command line (pythonw ignoblekeygen.pyw) or by double-clicking
# it when it has been associated with PythonLauncher.
# Revision history:
# 1 - Initial release
@@ -15,40 +27,93 @@ from __future__ import with_statement
# 2.1 - Allow Windows versions of libcrypto to be found
# 2.2 - On Windows try PyCrypto first and then OpenSSL next
# 2.3 - Modify interface to allow use of import
# 2.4 - Improvements to UI and now works in plugins
# 2.5 - Additional improvement for unicode and plugin support
"""
Generate Barnes & Noble EPUB user key from name and credit card number.
"""
__license__ = 'GPL v3'
__version__ = "2.5"
import sys
import os
import hashlib
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"ignoblekeygen.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
# use openssl's libcrypt if it exists in place of pycrypto
# code extracted from the Adobe Adept DRM removal code also by I HeartCabbages
class IGNOBLEError(Exception):
pass
def _load_crypto_libcrypto():
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
Structure, c_ulong, create_string_buffer, cast
from ctypes.util import find_library
if sys.platform.startswith('win'):
if iswindows:
libcrypto = find_library('libeay32')
else:
libcrypto = find_library('crypto')
if libcrypto is None:
print 'libcrypto not found'
raise IGNOBLEError('libcrypto not found')
libcrypto = CDLL(libcrypto)
@@ -73,6 +138,7 @@ def _load_crypto_libcrypto():
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
c_int])
class AES(object):
def __init__(self, userkey, iv):
self._blocksize = len(userkey)
@@ -91,7 +157,6 @@ def _load_crypto_libcrypto():
return AES
def _load_crypto_pycrypto():
from Crypto.Cipher import AES as _AES
@@ -123,86 +188,28 @@ def normalize_name(name):
return ''.join(x for x in name.lower() if x != ' ')
def generate_keyfile(name, ccn, outpath):
def generate_key(name, ccn):
# remove spaces and case from name and CC numbers.
if type(name)==unicode:
name = name.encode('utf-8')
if type(ccn)==unicode:
ccn = ccn.encode('utf-8')
name = normalize_name(name) + '\x00'
ccn = ccn + '\x00'
ccn = normalize_name(ccn) + '\x00'
name_sha = hashlib.sha1(name).digest()[:16]
ccn_sha = hashlib.sha1(ccn).digest()[:16]
both_sha = hashlib.sha1(name + ccn).digest()
aes = AES(ccn_sha, name_sha)
crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c))
userkey = hashlib.sha1(crypt).digest()
with open(outpath, 'wb') as f:
f.write(userkey.encode('base64'))
return userkey
return userkey.encode('base64')
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text='Enter parameters')
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text='Name').grid(row=1)
self.name = Tkinter.Entry(body, width=30)
self.name.grid(row=1, column=1, sticky=sticky)
Tkinter.Label(body, text='CC#').grid(row=2)
self.ccn = Tkinter.Entry(body, width=30)
self.ccn.grid(row=2, column=1, sticky=sticky)
Tkinter.Label(body, text='Output file').grid(row=0)
self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky)
self.keypath.insert(0, 'bnepubkey.b64')
button = Tkinter.Button(body, text="...", command=self.get_keypath)
button.grid(row=0, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
buttons, text="Generate", width=10, command=self.generate)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
buttons, text="Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.asksaveasfilename(
parent=None, title='Select B&N EPUB key file to produce',
defaultextension='.b64',
filetypes=[('base64-encoded files', '.b64'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
def generate(self):
name = self.name.get()
ccn = self.ccn.get()
keypath = self.keypath.get()
if not name:
self.status['text'] = 'Name not specified'
return
if not ccn:
self.status['text'] = 'Credit card number not specified'
return
if not keypath:
self.status['text'] = 'Output keyfile path not specified'
return
self.status['text'] = 'Generating...'
try:
generate_keyfile(name, ccn, keypath)
except Exception, e:
self.status['text'] = 'Error: ' + str(e)
return
self.status['text'] = 'Keyfile successfully generated'
def cli_main(argv=sys.argv):
def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
if AES is None:
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
@@ -210,14 +217,85 @@ def cli_main(argv=sys.argv):
(progname,)
return 1
if len(argv) != 4:
print "usage: %s NAME CC# OUTFILE" % (progname,)
print u"usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname)
return 1
name, ccn, outpath = argv[1:]
generate_keyfile(name, ccn, outpath)
name, ccn, keypath = argv[1:]
userkey = generate_key(name, ccn)
open(keypath,'wb').write(userkey)
return 0
def gui_main():
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Enter parameters")
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Account Name").grid(row=0)
self.name = Tkinter.Entry(body, width=40)
self.name.grid(row=0, column=1, sticky=sticky)
Tkinter.Label(body, text=u"CC#").grid(row=1)
self.ccn = Tkinter.Entry(body, width=40)
self.ccn.grid(row=1, column=1, sticky=sticky)
Tkinter.Label(body, text=u"Output file").grid(row=2)
self.keypath = Tkinter.Entry(body, width=40)
self.keypath.grid(row=2, column=1, sticky=sticky)
self.keypath.insert(2, u"bnepubkey.b64")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
button.grid(row=2, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
buttons, text=u"Generate", width=10, command=self.generate)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select B&N ePub key file to produce",
defaultextension=u".b64",
filetypes=[('base64-encoded files', '.b64'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
def generate(self):
name = self.name.get()
ccn = self.ccn.get()
keypath = self.keypath.get()
if not name:
self.status['text'] = u"Name not specified"
return
if not ccn:
self.status['text'] = u"Credit card number not specified"
return
if not keypath:
self.status['text'] = u"Output keyfile path not specified"
return
self.status['text'] = u"Generating..."
try:
userkey = generate_key(name, ccn)
except Exception, e:
self.status['text'] = u"Error: (0}".format(e.args[0])
return
open(keypath,'wb').write(userkey)
self.status['text'] = u"Keyfile successfully generated"
root = Tkinter.Tk()
if AES is None:
root.withdraw()
@@ -226,7 +304,7 @@ def gui_main():
"This script requires OpenSSL or PyCrypto, which must be installed "
"separately. Read the top-of-script comment for details.")
return 1
root.title('Ignoble EPUB Keyfile Generator')
root.title(u"Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__))
root.resizable(True, False)
root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
@@ -235,5 +313,7 @@ def gui_main():
if __name__ == '__main__':
if len(sys.argv) > 1:
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(gui_main())

View File

@@ -3,11 +3,13 @@
from __future__ import with_statement
# ineptepub.pyw, version 5.6
# Copyright © 2009-2010 i♥cabbages
# ineptepub.pyw, version 5.8
# Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/>
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102012 by some_updates, DiapDealer and Apprentice Alf
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
@@ -30,23 +32,84 @@ from __future__ import with_statement
# 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml
# 5.5 - On Windows try PyCrypto first, OpenSSL next
# 5.6 - Modify interface to allow use with import
# 5.7 - Fix for potential problem with PyCrypto
# 5.8 - Revised to allow use in calibre plugins to eliminate need for duplicate code
"""
Decrypt Adobe ADEPT-encrypted EPUB books.
Decrypt Adobe Digital Editions encrypted ePub books.
"""
__license__ = 'GPL v3'
__version__ = "5.8"
import sys
import os
import traceback
import zlib
import zipfile
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
from contextlib import closing
import xml.etree.ElementTree as etree
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
try:
from calibre.constants import iswindows, isosx
except:
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
return [u"ineptepub.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
class ADEPTError(Exception):
pass
@@ -56,7 +119,7 @@ def _load_crypto_libcrypto():
Structure, c_ulong, create_string_buffer, cast
from ctypes.util import find_library
if sys.platform.startswith('win'):
if iswindows:
libcrypto = find_library('libeay32')
else:
libcrypto = find_library('crypto')
@@ -235,7 +298,7 @@ def _load_crypto_pycrypto():
class AES(object):
def __init__(self, key):
self._aes = _AES.new(key, _AES.MODE_CBC)
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
def decrypt(self, data):
return self._aes.decrypt(data)
@@ -270,6 +333,7 @@ def _load_crypto():
except (ImportError, ADEPTError):
pass
return (AES, RSA)
AES, RSA = _load_crypto()
META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
@@ -312,158 +376,181 @@ class Decryptor(object):
data = self.decompress(data)
return data
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text='Select files for decryption')
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text='Key file').grid(row=0)
self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists('adeptkey.der'):
self.keypath.insert(0, 'adeptkey.der')
button = Tkinter.Button(body, text="...", command=self.get_keypath)
button.grid(row=0, column=2)
Tkinter.Label(body, text='Input file').grid(row=1)
self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text="...", command=self.get_inpath)
button.grid(row=1, column=2)
Tkinter.Label(body, text='Output file').grid(row=2)
self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=2, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
buttons, text="Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
buttons, text="Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
parent=None, title='Select ADEPT key file',
defaultextension='.der', filetypes=[('DER-encoded files', '.der'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
def get_inpath(self):
inpath = tkFileDialog.askopenfilename(
parent=None, title='Select ADEPT-encrypted EPUB file to decrypt',
defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
('All files', '.*')])
if inpath:
inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END)
self.inpath.insert(0, inpath)
return
def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename(
parent=None, title='Select unencrypted EPUB file to produce',
defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
('All files', '.*')])
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return
def decrypt(self):
keypath = self.keypath.get()
inpath = self.inpath.get()
outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath):
self.status['text'] = 'Specified key file does not exist'
return
if not inpath or not os.path.exists(inpath):
self.status['text'] = 'Specified input file does not exist'
return
if not outpath:
self.status['text'] = 'Output file not specified'
return
if inpath == outpath:
self.status['text'] = 'Must have different input and output files'
return
argv = [sys.argv[0], keypath, inpath, outpath]
self.status['text'] = 'Decrypting...'
try:
cli_main(argv)
except Exception, e:
self.status['text'] = 'Error: ' + str(e)
return
self.status['text'] = 'File successfully decrypted'
def decryptBook(keypath, inpath, outpath):
with open(keypath, 'rb') as f:
keyder = f.read()
rsa = RSA(keyder)
# check file to make check whether it's probably an Adobe Adept encrypted ePub
def adeptBook(inpath):
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist:
raise ADEPTError('%s: not an ADEPT EPUB' % (inpath,))
return False
try:
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
if len(bookkey) == 172:
return True
except:
# if we couldn't check, assume it is
return True
return False
def decryptBook(userkey, inpath, outpath):
if AES is None:
raise ADEPTError(u"PyCrypto or OpenSSL must be installed.")
rsa = RSA(userkey)
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist:
print u"{0:s} is DRM-free.".format(os.path.basename(inpath))
return 1
for name in META_NAMES:
namelist.remove(name)
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
bookkey = rsa.decrypt(bookkey.decode('base64'))
# Padded as per RSAES-PKCS1-v1_5
if bookkey[-17] != '\x00':
raise ADEPTError('problem decrypting session key')
encryption = inf.read('META-INF/encryption.xml')
decryptor = Decryptor(bookkey[-16:], encryption)
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
outf.writestr(zi, inf.read('mimetype'))
for path in namelist:
data = inf.read(path)
outf.writestr(path, decryptor.decrypt(path, data))
try:
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
if len(bookkey) != 172:
print u"{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath))
return 1
bookkey = rsa.decrypt(bookkey.decode('base64'))
# Padded as per RSAES-PKCS1-v1_5
if bookkey[-17] != '\x00':
print u"Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath))
return 2
encryption = inf.read('META-INF/encryption.xml')
decryptor = Decryptor(bookkey[-16:], encryption)
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
outf.writestr(zi, inf.read('mimetype'))
for path in namelist:
data = inf.read(path)
outf.writestr(path, decryptor.decrypt(path, data))
except:
print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc())
return 2
return 0
def cli_main(argv=sys.argv):
def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
if AES is None:
print "%s: This script requires OpenSSL or PyCrypto, which must be" \
" installed separately. Read the top-of-script comment for" \
" details." % (progname,)
return 1
if len(argv) != 4:
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
print u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname)
return 1
keypath, inpath, outpath = argv[1:]
return decryptBook(keypath, inpath, outpath)
userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath)
if result == 0:
print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
return result
def gui_main():
import Tkinter
import Tkconstants
import tkFileDialog
import traceback
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Select files for decryption")
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Key file").grid(row=0)
self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists(u"adeptkey.der"):
self.keypath.insert(0, u"adeptkey.der")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
button.grid(row=0, column=2)
Tkinter.Label(body, text=u"Input file").grid(row=1)
self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
button.grid(row=1, column=2)
Tkinter.Label(body, text=u"Output file").grid(row=2)
self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
button.grid(row=2, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
buttons, text=u"Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select Adobe Adept \'.der\' key file",
defaultextension=u".der",
filetypes=[('Adobe Adept DER-encoded files', '.der'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
def get_inpath(self):
inpath = tkFileDialog.askopenfilename(
parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
if inpath:
inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END)
self.inpath.insert(0, inpath)
return
def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select unencrypted ePub file to produce",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return
def decrypt(self):
keypath = self.keypath.get()
inpath = self.inpath.get()
outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath):
self.status['text'] = u"Specified key file does not exist"
return
if not inpath or not os.path.exists(inpath):
self.status['text'] = u"Specified input file does not exist"
return
if not outpath:
self.status['text'] = u"Output file not specified"
return
if inpath == outpath:
self.status['text'] = u"Must have different input and output files"
return
userkey = open(keypath,'rb').read()
self.status['text'] = u"Decrypting..."
try:
decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception, e:
self.status['text'] = u"Error; {0}".format(e)
return
if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted"
else:
self.status['text'] = u"The was an error decrypting the file."
root = Tkinter.Tk()
if AES is None:
root.withdraw()
tkMessageBox.showerror(
"INEPT EPUB Decrypter",
"This script requires OpenSSL or PyCrypto, which must be"
" installed separately. Read the top-of-script comment for"
" details.")
return 1
root.title('INEPT EPUB Decrypter')
root.title(u"Adobe Adept ePub Decrypter v.{0}".format(__version__))
root.resizable(True, False)
root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
@@ -472,5 +559,7 @@ def gui_main():
if __name__ == '__main__':
if len(sys.argv) > 1:
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(gui_main())

View File

@@ -3,11 +3,11 @@
from __future__ import with_statement
# ineptkey.pyw, version 5.4
# ineptkey.pyw, version 5.6
# Copyright © 2009-2010 i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/>
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
@@ -36,6 +36,8 @@ from __future__ import with_statement
# 5.2 - added support for output of key to a particular file
# 5.3 - On Windows try PyCrypto first, OpenSSL next
# 5.4 - Modify interface to allow use of import
# 5.5 - Fix for potential problem with PyCrypto
# 5.6 - Revised to allow use in Plugins to eliminate need for duplicate code
"""
Retrieve Adobe ADEPT user key.
@@ -46,15 +48,70 @@ __license__ = 'GPL v3'
import sys
import os
import struct
import Tkinter
import Tkconstants
import tkMessageBox
import traceback
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
try:
from calibre.constants import iswindows, isosx
except:
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
return [u"ineptkey.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
class ADEPTError(Exception):
pass
if sys.platform.startswith('win'):
if iswindows:
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, c_size_t, memmove, CDLL, c_int, \
@@ -110,7 +167,7 @@ if sys.platform.startswith('win'):
from Crypto.Cipher import AES as _AES
class AES(object):
def __init__(self, key):
self._aes = _AES.new(key, _AES.MODE_CBC)
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
def decrypt(self, data):
return self._aes.decrypt(data)
return AES
@@ -292,13 +349,9 @@ if sys.platform.startswith('win'):
return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData()
def retrieve_key(keypath):
def retrieve_keys():
if AES is None:
tkMessageBox.showerror(
"ADEPT Key",
"This script requires PyCrypto or OpenSSL which must be installed "
"separately. Read the top-of-script comment for details.")
return False
raise ADEPTError("PyCrypto or OpenSSL must be installed")
root = GetSystemDirectory().split('\\')[0] + '\\'
serial = GetVolumeSerialNumber(root)
vendor = cpuid0()
@@ -308,11 +361,12 @@ if sys.platform.startswith('win'):
cuser = winreg.HKEY_CURRENT_USER
try:
regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH)
device = winreg.QueryValueEx(regkey, 'key')[0]
except WindowsError:
raise ADEPTError("Adobe Digital Editions not activated")
device = winreg.QueryValueEx(regkey, 'key')[0]
keykey = CryptUnprotectData(device, entropy)
userkey = None
keys = []
try:
plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
except WindowsError:
@@ -334,50 +388,43 @@ if sys.platform.startswith('win'):
if ktype != 'privateLicenseKey':
continue
userkey = winreg.QueryValueEx(plkkey, 'value')[0]
break
if userkey is not None:
break
if userkey is None:
userkey = userkey.decode('base64')
aes = AES(keykey)
userkey = aes.decrypt(userkey)
userkey = userkey[26:-ord(userkey[-1])]
keys.append(userkey)
if len(keys) == 0:
raise ADEPTError('Could not locate privateLicenseKey')
userkey = userkey.decode('base64')
aes = AES(keykey)
userkey = aes.decrypt(userkey)
userkey = userkey[26:-ord(userkey[-1])]
with open(keypath, 'wb') as f:
f.write(userkey)
return True
return keys
elif sys.platform.startswith('darwin'):
elif isosx:
import xml.etree.ElementTree as etree
import Carbon.File
import Carbon.Folder
import Carbon.Folders
import MacOS
import subprocess
ACTIVATION_PATH = 'Adobe/Digital Editions/activation.dat'
NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
def find_folder(domain, dtype):
try:
fsref = Carbon.Folder.FSFindFolder(domain, dtype, False)
return Carbon.File.pathname(fsref)
except MacOS.Error:
return None
def find_app_support_file(subpath):
dtype = Carbon.Folders.kApplicationSupportFolderType
for domain in Carbon.Folders.kUserDomain, Carbon.Folders.kLocalDomain:
path = find_folder(domain, dtype)
if path is None:
continue
path = os.path.join(path, subpath)
if os.path.isfile(path):
return path
def findActivationDat():
home = os.getenv('HOME')
cmdline = 'find "' + home + '/Library/Application Support/Adobe/Digital Editions" -name "activation.dat"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
out1, out2 = p2.communicate()
reslst = out1.split('\n')
cnt = len(reslst)
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('activation.dat')
if pp >= 0:
ActDatPath = resline
break
if os.path.exists(ActDatPath):
return ActDatPath
return None
def retrieve_key(keypath):
actpath = find_app_support_file(ACTIVATION_PATH)
def retrieve_keys():
actpath = findActivationDat()
if actpath is None:
raise ADEPTError("Could not locate ADE activation")
tree = etree.parse(actpath)
@@ -386,69 +433,65 @@ elif sys.platform.startswith('darwin'):
userkey = tree.findtext(expr)
userkey = userkey.decode('base64')
userkey = userkey[26:]
with open(keypath, 'wb') as f:
f.write(userkey)
return True
elif sys.platform.startswith('cygwin'):
def retrieve_key(keypath):
tkMessageBox.showerror(
"ADEPT Key",
"This script requires a Windows-native Python, and cannot be run "
"under Cygwin. Please install a Windows-native Python and/or "
"check your file associations.")
return False
return [userkey]
else:
def retrieve_key(keypath):
tkMessageBox.showerror(
"ADEPT Key",
"This script only supports Windows and Mac OS X. For Linux "
"you should be able to run ADE and this script under Wine (with "
"an appropriate version of Windows Python installed).")
return False
class ExceptionDialog(Tkinter.Frame):
def __init__(self, root, text):
Tkinter.Frame.__init__(self, root, border=5)
label = Tkinter.Label(self, text="Unexpected error:",
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
label.pack(fill=Tkconstants.X, expand=0)
self.text = Tkinter.Text(self)
self.text.pack(fill=Tkconstants.BOTH, expand=1)
self.text.insert(Tkconstants.END, text)
def retrieve_keys(keypath):
raise ADEPTError("This script only supports Windows and Mac OS X.")
return []
def retrieve_key(keypath):
keys = retrieve_keys()
with open(keypath, 'wb') as f:
f.write(keys[0])
return True
def extractKeyfile(keypath):
try:
success = retrieve_key(keypath)
except ADEPTError, e:
print "Key generation Error: " + str(e)
print u"Key generation Error: {0}".format(e.args[0])
return 1
except Exception, e:
print "General Error: " + str(e)
print "General Error: {0}".format(e.args[0])
return 1
if not success:
return 1
return 0
def cli_main(argv=sys.argv):
def cli_main(argv=unicode_argv()):
keypath = argv[1]
return extractKeyfile(keypath)
def main(argv=sys.argv):
def gui_main(argv=unicode_argv()):
import Tkinter
import Tkconstants
import tkMessageBox
import traceback
class ExceptionDialog(Tkinter.Frame):
def __init__(self, root, text):
Tkinter.Frame.__init__(self, root, border=5)
label = Tkinter.Label(self, text=u"Unexpected error:",
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
label.pack(fill=Tkconstants.X, expand=0)
self.text = Tkinter.Text(self)
self.text.pack(fill=Tkconstants.BOTH, expand=1)
self.text.insert(Tkconstants.END, text)
root = Tkinter.Tk()
root.withdraw()
progname = os.path.basename(argv[0])
keypath = 'adeptkey.der'
keypath, progname = os.path.split(argv[0])
keypath = os.path.join(keypath, u"adeptkey.der")
success = False
try:
success = retrieve_key(keypath)
except ADEPTError, e:
tkMessageBox.showerror("ADEPT Key", "Error: " + str(e))
tkMessageBox.showerror(u"ADEPT Key", "Error: {0}".format(e.args[0]))
except Exception:
root.wm_state('normal')
root.title('ADEPT Key')
@@ -458,10 +501,12 @@ def main(argv=sys.argv):
if not success:
return 1
tkMessageBox.showinfo(
"ADEPT Key", "Key successfully retrieved to %s" % (keypath))
u"ADEPT Key", u"Key successfully retrieved to {0}".format(keypath))
return 0
if __name__ == '__main__':
if len(sys.argv) > 1:
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(main())
sys.exit(gui_main())

View File

@@ -1,13 +1,25 @@
#! /usr/bin/env python
# ineptpdf.pyw, version 7.11
#! /usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import with_statement
# To run this program install Python 2.6 from http://www.python.org/download/
# and OpenSSL (already installed on Mac OS X and Linux) OR
# PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# (make sure to install the version for Python 2.6). Save this script file as
# ineptpdf.pyw and double-click on it to run it.
# ineptpdf.pyw, version 7.11
# Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102012 by some_updates, DiapDealer and Apprentice Alf
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
# install the version for Python 2.6). Save this script file as
# ineptepub.pyw and double-click on it to run it.
#
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
# program from the command line (pythonw ineptepub.pyw) or by double-clicking
# it when it has been associated with PythonLauncher.
# Revision history:
# 1 - Initial release
@@ -36,12 +48,14 @@ from __future__ import with_statement
# 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.
# 7.12 - Revised to allow use in calibre plugins to eliminate need for duplicate code
"""
Decrypts Adobe ADEPT-encrypted PDF files.
"""
__license__ = 'GPL v3'
__version__ = "7.12"
import sys
import os
@@ -51,10 +65,63 @@ import struct
import hashlib
from itertools import chain, islice
import xml.etree.ElementTree as etree
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
return [u"ineptepub.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
class ADEPTError(Exception):
pass
@@ -1520,9 +1587,7 @@ class PDFDocument(object):
def initialize_ebx(self, password, docid, param):
self.is_printable = self.is_modifiable = self.is_extractable = True
with open(password, 'rb') as f:
keyder = f.read()
rsa = RSA(keyder)
rsa = RSA(password)
length = int_value(param.get('Length', 0)) / 8
rights = str_value(param.get('ADEPT_LICENSE')).decode('base64')
rights = zlib.decompress(rights, -15)
@@ -1907,14 +1972,14 @@ class PDFObjStrmParser(PDFParser):
### My own code, for which there is none else to blame
class PDFSerializer(object):
def __init__(self, inf, keypath):
def __init__(self, inf, userkey):
global GEN_XREF_STM, gen_xref_stm
gen_xref_stm = GEN_XREF_STM > 1
self.version = inf.read(8)
inf.seek(0)
self.doc = doc = PDFDocument()
parser = PDFParser(doc, inf)
doc.initialize(keypath)
doc.initialize(userkey)
self.objids = objids = set()
for xref in reversed(doc.xrefs):
trailer = xref.trailer
@@ -2097,142 +2162,144 @@ class PDFSerializer(object):
self.write('endobj\n')
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
ltext='Select file for decryption\n'
self.status = Tkinter.Label(self, text=ltext)
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text='Key file').grid(row=0)
self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists('adeptkey.der'):
self.keypath.insert(0, 'adeptkey.der')
button = Tkinter.Button(body, text="...", command=self.get_keypath)
button.grid(row=0, column=2)
Tkinter.Label(body, text='Input file').grid(row=1)
self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text="...", command=self.get_inpath)
button.grid(row=1, column=2)
Tkinter.Label(body, text='Output file').grid(row=2)
self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=2, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
buttons, text="Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
buttons, text="Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
parent=None, title='Select ADEPT key file',
defaultextension='.der', filetypes=[('DER-encoded files', '.der'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(os.path.realpath(keypath))
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
def get_inpath(self):
inpath = tkFileDialog.askopenfilename(
parent=None, title='Select ADEPT encrypted PDF file to decrypt',
defaultextension='.pdf', filetypes=[('PDF files', '.pdf'),
('All files', '.*')])
if inpath:
inpath = os.path.normpath(os.path.realpath(inpath))
self.inpath.delete(0, Tkconstants.END)
self.inpath.insert(0, inpath)
return
def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename(
parent=None, title='Select unencrypted PDF file to produce',
defaultextension='.pdf', filetypes=[('PDF files', '.pdf'),
('All files', '.*')])
if outpath:
outpath = os.path.normpath(os.path.realpath(outpath))
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return
def decrypt(self):
keypath = self.keypath.get()
inpath = self.inpath.get()
outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath):
# keyfile doesn't exist
self.status['text'] = 'Specified Adept key file does not exist'
return
if not inpath or not os.path.exists(inpath):
self.status['text'] = 'Specified input file does not exist'
return
if not outpath:
self.status['text'] = 'Output file not specified'
return
if inpath == outpath:
self.status['text'] = 'Must have different input and output files'
return
# patch for non-ascii characters
argv = [sys.argv[0], keypath, inpath, outpath]
self.status['text'] = 'Processing ...'
try:
cli_main(argv)
except Exception, a:
self.status['text'] = 'Error: ' + str(a)
return
self.status['text'] = 'File successfully decrypted.\n'+\
'Close this window or decrypt another pdf file.'
return
def decryptBook(keypath, inpath, outpath):
def decryptBook(userkey, inpath, outpath):
if RSA is None:
raise ADEPTError(u"PyCrypto or OpenSSL must be installed.")
with open(inpath, 'rb') as inf:
try:
serializer = PDFSerializer(inf, keypath)
serializer = PDFSerializer(inf, userkey)
except:
print "Error serializing pdf. Probably wrong key."
return 1
print u"Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath))
return 2
# hope this will fix the 'bad file descriptor' problem
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
try:
serializer.dump(outf)
except:
print "error writing pdf."
return 1
except Exception, e:
print u"error writing pdf: {0}".format(e.args[0])
return 2
return 0
def cli_main(argv=sys.argv):
def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
if RSA is None:
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
"separately. Read the top-of-script comment for details." % \
(progname,)
return 1
if len(argv) != 4:
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
print u"usage: {0} <keyfile.der> <inbook.pdf> <outbook.pdf>".format(progname)
return 1
keypath, inpath, outpath = argv[1:]
return decryptBook(keypath, inpath, outpath)
userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath)
if result == 0:
print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
return result
def gui_main():
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Select files for decryption")
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Key file").grid(row=0)
self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists(u"adeptkey.der"):
self.keypath.insert(0, u"adeptkey.der")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
button.grid(row=0, column=2)
Tkinter.Label(body, text=u"Input file").grid(row=1)
self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
button.grid(row=1, column=2)
Tkinter.Label(body, text=u"Output file").grid(row=2)
self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
button.grid(row=2, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
buttons, text=u"Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select Adobe Adept \'.der\' key file",
defaultextension=u".der",
filetypes=[('Adobe Adept DER-encoded files', '.der'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
def get_inpath(self):
inpath = tkFileDialog.askopenfilename(
parent=None, title=u"Select ADEPT-encrypted PDF file to decrypt",
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
if inpath:
inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END)
self.inpath.insert(0, inpath)
return
def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select unencrypted PDF file to produce",
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return
def decrypt(self):
keypath = self.keypath.get()
inpath = self.inpath.get()
outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath):
self.status['text'] = u"Specified key file does not exist"
return
if not inpath or not os.path.exists(inpath):
self.status['text'] = u"Specified input file does not exist"
return
if not outpath:
self.status['text'] = u"Output file not specified"
return
if inpath == outpath:
self.status['text'] = u"Must have different input and output files"
return
userkey = open(keypath,'rb').read()
self.status['text'] = u"Decrypting..."
try:
decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception, e:
self.status['text'] = u"Error; {0}".format(e.args[0])
return
if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted"
else:
self.status['text'] = u"The was an error decrypting the file."
root = Tkinter.Tk()
if RSA is None:
root.withdraw()
@@ -2241,7 +2308,7 @@ def gui_main():
"This script requires OpenSSL or PyCrypto, which must be installed "
"separately. Read the top-of-script comment for details.")
return 1
root.title('INEPT PDF Decrypter')
root.title(u"Adobe Adept PDF Decrypter v.{0}".format(__version__))
root.resizable(True, False)
root.minsize(370, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
@@ -2251,5 +2318,7 @@ def gui_main():
if __name__ == '__main__':
if len(sys.argv) > 1:
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(gui_main())

View File

@@ -1,333 +0,0 @@
# engine to remove drm from Kindle for Mac books
# for personal use for archiving and converting your ebooks
# PLEASE DO NOT PIRATE!
# We want all authors and Publishers, and eBook stores to live long and prosperous lives
#
# it borrows heavily from works by CMBDTC, IHeartCabbages, skindle,
# unswindle, DiapDealer, some_updates and many many others
from __future__ import with_statement
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 os, csv, getopt
from struct import pack
from struct import unpack
import zlib
# for handling sub processes
import subprocess
from subprocess import Popen, PIPE, STDOUT
import subasyncio
from subasyncio import Process
#Exception Handling
class K4MDEDRMError(Exception):
pass
class K4MDEDRMFatal(Exception):
pass
#
# crypto routines
#
import hashlib
def MD5(message):
ctx = hashlib.md5()
ctx.update(message)
return ctx.digest()
def SHA1(message):
ctx = hashlib.sha1()
ctx.update(message)
return ctx.digest()
def SHA256(message):
ctx = hashlib.sha256()
ctx.update(message)
return ctx.digest()
# interface to needed routines in openssl's libcrypto
def _load_crypto_libcrypto():
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
from ctypes.util import find_library
libcrypto = find_library('crypto')
if libcrypto is None:
raise K4MDEDRMError('libcrypto not found')
libcrypto = CDLL(libcrypto)
AES_MAXNR = 14
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
def F(restype, name, argtypes):
func = getattr(libcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
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])
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])
class LibCrypto(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 K4MDEDRMError('AES 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 K4MDEDRMError('Failed to initialize AES key')
def decrypt(self, data):
out = create_string_buffer(len(data))
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
if rv == 0:
raise K4MDEDRMError('AES decryption failed')
return out.raw
def keyivgen(self, passwd):
salt = '16743'
saltlen = 5
passlen = len(passwd)
iter = 0x3e8
keylen = 80
out = create_string_buffer(keylen)
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
return out.raw
return LibCrypto
def _load_crypto():
LibCrypto = None
try:
LibCrypto = _load_crypto_libcrypto()
except (ImportError, K4MDEDRMError):
pass
return LibCrypto
LibCrypto = _load_crypto()
#
# Utility Routines
#
# uses a sub process to get the Hard Drive Serial Number using ioreg
# returns with the first found serial number in that class
def GetVolumeSerialNumber():
sernum = os.getenv('MYSERIALNUMBER')
if sernum != None:
return sernum
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
poll = p.wait('wait')
results = p.read()
reslst = results.split('\n')
cnt = len(reslst)
bsdname = None
sernum = None
foundIt = False
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('"Serial Number" = "')
if pp >= 0:
sernum = resline[pp+19:-1]
sernum = sernum.strip()
bb = resline.find('"BSD Name" = "')
if bb >= 0:
bsdname = resline[bb+14:-1]
bsdname = bsdname.strip()
if (bsdname == 'disk0') and (sernum != None):
foundIt = True
break
if not foundIt:
sernum = '9999999999'
return sernum
# uses unix env to get username instead of using sysctlbyname
def GetUserName():
username = os.getenv('USER')
return username
MAX_PATH = 255
#
# start of Kindle specific routines
#
global kindleDatabase
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
# 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)-1,2):
high = map.find(data[i])
low = map.find(data[i+1])
if (high == -1) or (low == -1) :
break
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
result += pack("B",value)
return result
# implements an Pseudo Mac Version of Windows built-in Crypto routine
def CryptUnprotectData(encryptedData):
sp = GetVolumeSerialNumber() + '!@#' + GetUserName()
passwdData = encode(SHA256(sp),charMap1)
crp = LibCrypto()
key_iv = crp.keyivgen(passwdData)
key = key_iv[0:32]
iv = key_iv[32:48]
crp.set_decrypt_key(key,iv)
cleartext = crp.decrypt(encryptedData)
return cleartext
# Locate and open the .kindle-info file
def openKindleInfo():
home = os.getenv('HOME')
kinfopath = home + '/Library/Application Support/Amazon/Kindle/storage/.kindle-info'
if not os.path.exists(kinfopath):
kinfopath = home + '/Library/Application Support/Amazon/Kindle for Mac/storage/.kindle-info'
if not os.path.exists(kinfopath):
raise K4MDEDRMError('Error: .kindle-info file can not be found')
return open(kinfopath,'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
# 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)
cleartext = CryptUnprotectData(encryptedValue)
return decode(cleartext, charMap1)
# 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))
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string.
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 result
# 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 ("--------------------------")
else :
print ("Unknown Record")
print getKindleInfoValueForHash(record)
print "\n"
#
# PID generation routines
#
# 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
#
# Main
#
def main(argv=sys.argv):
global kindleDatabase
kindleDatabase = None
#
# Read the encrypted database
#
try:
kindleDatabase = parseKindleInfo()
except Exception, message:
print(message)
if kindleDatabase != None :
printKindleInfo()
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -1,7 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
# ignobleepub.pyw, version 3.6
# Copyright © 2009-2012 by DiapDealer et al.
# engine to remove drm from Kindle for Mac and Kindle for PC books
# for personal use for archiving and converting your ebooks
@@ -12,29 +16,51 @@ from __future__ import with_statement
# be able to read OUR books on whatever device we want and to keep
# readable for a long, long time
# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
# and many many others
# Special thanks to The Dark Reverser for MobiDeDrm and CMBDTC for cmbdtc_dump
# from which this script borrows most unashamedly.
__version__ = '4.4'
# Changelog
# 1.0 - Name change to k4mobidedrm. Adds Mac support, Adds plugin code
# 1.1 - Adds support for additional kindle.info files
# 1.2 - Better error handling for older Mobipocket
# 1.3 - Don't try to decrypt Topaz books
# 1.7 - Add support for Topaz books and Kindle serial numbers. Split code.
# 1.9 - Tidy up after Topaz, minor exception changes
# 2.1 - Topaz fix and filename sanitizing
# 2.2 - Topaz Fix and minor Mac code fix
# 2.3 - More Topaz fixes
# 2.4 - K4PC/Mac key generation fix
# 2.6 - Better handling of non-K4PC/Mac ebooks
# 2.7 - Better trailing bytes handling in mobidedrm
# 2.8 - Moved parsing of kindle.info files to mac & pc util files.
# 3.1 - Updated for new calibre interface. Now __init__ in plugin.
# 3.5 - Now support Kindle for PC/Mac 1.6
# 3.6 - Even better trailing bytes handling in mobidedrm
# 3.7 - Add support for Amazon Print Replica ebooks.
# 3.8 - Improved Topaz support
# 4.1 - Improved Topaz support and faster decryption with alfcrypto
# 4.2 - Added support for Amazon's KF8 format ebooks
# 4.4 - Linux calls to Wine added, and improved configuration dialog
# 4.5 - Linux works again without Wine. Some Mac key file search changes
# 4.6 - First attempt to handle unicode properly
# 4.7 - Added timing reports, and changed search for Mac key files
# 4.8 - Much better unicode handling, matching the updated inept and ignoble scripts
# - Moved back into plugin, __init__ in plugin now only contains plugin code.
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)
__version__ = '4.8'
import sys
import os, csv, getopt
import string
import sys, os, re
import csv
import getopt
import re
import traceback
buildXML = False
import time
import htmlentitydefs
class DrmException(Exception):
pass
@@ -53,147 +79,203 @@ else:
import topazextract
import kgenpids
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
# cleanup bytestring filenames
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
# cleanup unicode filenames
# borrowed from calibre from calibre/src/calibre/__init__.py
# added in removal of non-printing chars
# and removal of . at start
# convert underscores to spaces (we're OK with spaces in file names)
# added in removal of control (<32) chars
# and removal of . at start and end
# and with some (heavily edited) code from Paul Durrant's kindlenamer.py
def cleanup_name(name):
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
substitute='_'
one = ''.join(char for char in name if char in string.printable)
one = _filename_sanitize.sub(substitute, one)
one = re.sub(r'\s', ' ', one).strip()
one = re.sub(r'^\.+$', '_', one)
one = one.replace('..', substitute)
# Windows doesn't like path components that end with a period
if one.endswith('.'):
one = one[:-1]+substitute
# Mac and Unix don't like file names that begin with a full stop
if len(one) > 0 and one[0] == '.':
one = substitute+one[1:]
one = one.replace('_',' ')
return one
# substitute filename unfriendly characters
name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" ").replace(u": ",u" ").replace(u":",u"").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'")
# delete control characters
name = u"".join(char for char in name if ord(char)>=32)
# white space to single space, delete leading and trailing while space
name = re.sub(ur"\s", u" ", name).strip()
# remove leading dots
while len(name)>0 and name[0] == u".":
name = name[1:]
# remove trailing dots (Windows doesn't like them)
if name.endswith(u'.'):
name = name[:-1]
return name
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
global buildXML
# must be passed unicode
def unescape(text):
def fixup(m):
text = m.group(0)
if text[:2] == u"&#":
# character reference
try:
if text[:3] == u"&#x":
return unichr(int(text[3:-1], 16))
else:
return unichr(int(text[2:-1]))
except ValueError:
pass
else:
# named entity
try:
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
except KeyError:
pass
return text # leave as is
return re.sub(u"&#?\w+;", fixup, text)
def GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime = time.time()):
# handle the obvious cases at the beginning
if not os.path.isfile(infile):
print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: Input file does not exist"
return 1
raise DRMException (u"Input file does not exist.")
mobi = True
magic3 = file(infile,'rb').read(3)
if magic3 == 'TPZ':
mobi = False
bookname = os.path.splitext(os.path.basename(infile))[0]
if mobi:
mb = mobidedrm.MobiBook(infile)
else:
mb = topazextract.TopazBook(infile)
title = mb.getBookTitle()
print "Processing Book: ", title
filenametitle = cleanup_name(title)
outfilename = cleanup_name(bookname)
bookname = unescape(mb.getBookTitle())
print u"Decrypting {1} ebook: {0}".format(bookname, mb.getBookType())
# generate 'sensible' filename, that will sort with the original name,
# but is close to the name from the file.
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
# extend PID list with book-specific PIDs
md1, md2 = mb.getPIDMetaInfo()
pids.extend(kgenpids.getPidList(md1, md2, serials, kInfoFiles))
print u"Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(pids))
try:
mb.processBook(pids)
except:
mb.cleanup
raise
print u"Decryption succeeded after {0:.1f} seconds".format(time.time()-starttime)
return mb
# infile, outdir and kInfoFiles should be unicode strings
def decryptBook(infile, outdir, kInfoFiles, serials, pids):
starttime = time.time()
print "Starting decryptBook routine."
try:
book = GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime)
except Exception, e:
print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
return 1
# if we're saving to the same folder as the original, use file name_
# if to a different folder, use book name
if os.path.normcase(os.path.normpath(outdir)) == os.path.normcase(os.path.normpath(os.path.dirname(infile))):
outfilename = os.path.splitext(os.path.basename(infile))[0]
else:
outfilename = cleanup_name(book.getBookTitle())
# avoid excessively long file names
if len(outfilename)>150:
outfilename = outfilename[:150]
# build pid list
md1, md2 = mb.getPIDMetaInfo()
pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles)
outfilename = outfilename+u"_nodrm"
outfile = os.path.join(outdir, outfilename + book.getBookExtension())
try:
mb.processBook(pidlst)
book.getFile(outfile)
print u"Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename)
except mobidedrm.DrmException, e:
print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
return 1
except topazextract.TpzDRMError, e:
print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
return 1
except Exception, e:
print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
return 1
if mobi:
if mb.getPrintReplica():
outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw4')
elif mb.getMobiVersion() >= 8:
outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw3')
else:
outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi')
mb.getMobiFile(outfile)
return 0
# topaz:
print " Creating NoDRM HTMLZ Archive"
zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz')
mb.getHTMLZip(zipname)
print " Creating SVG ZIP Archive"
zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip')
mb.getSVGZip(zipname)
if buildXML:
print " Creating XML ZIP Archive"
zipname = os.path.join(outdir, outfilename + '_XML' + '.zip')
mb.getXMLZip(zipname)
if book.getBookType()==u"Topaz":
zipname = os.path.join(outdir, outfilename + u"_SVG.zip")
book.getSVGZip(zipname)
print u"Saved SVG ZIP Archive for {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename)
# remove internal temporary directory of Topaz pieces
mb.cleanup()
return 0
book.cleanup()
def usage(progname):
print "Removes DRM protection from K4PC/M, Kindle, Mobi and Topaz ebooks"
print "Usage:"
print " %s [-k <kindle.info>] [-p <pidnums>] [-s <kindleSerialNumbers>] <infile> <outdir> " % progname
print u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks"
print u"Usage:"
print u" {0} [-k <kindle.info>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname)
#
# Main
#
def main(argv=sys.argv):
def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
k4 = False
kInfoFiles = []
serials = []
pids = []
print ('K4MobiDeDrm v%(__version__)s '
'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
try:
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
except getopt.GetoptError, err:
print str(err)
print u"Error in options or arguments: {0}".format(err.args[0])
usage(progname)
sys.exit(2)
if len(args)<2:
usage(progname)
sys.exit(2)
infile = args[0]
outdir = args[1]
kInfoFiles = []
serials = []
pids = []
for o, a in opts:
if o == "-k":
if a == None :
@@ -208,16 +290,13 @@ def main(argv=sys.argv):
raise DrmException("Invalid parameter for -s")
serials = a.split(',')
# try with built in Kindle Info files
k4 = True
if sys.platform.startswith('linux'):
k4 = False
kInfoFiles = None
infile = args[0]
outdir = args[1]
return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids)
# try with built in Kindle Info files if not on Linux
k4 = not sys.platform.startswith('linux')
return decryptBook(infile, outdir, kInfoFiles, serials, pids)
if __name__ == '__main__':
sys.stdout=Unbuffered(sys.stdout)
sys.exit(main())
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())

View File

@@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# standlone set of Mac OSX specific routines needed for KindleBooks
from __future__ import with_statement
@@ -22,7 +25,7 @@ def _load_crypto_libcrypto():
libcrypto = find_library('crypto')
if libcrypto is None:
raise DrmException('libcrypto not found')
raise DrmException(u"libcrypto not found")
libcrypto = CDLL(libcrypto)
# From OpenSSL's crypto aes header
@@ -80,14 +83,14 @@ def _load_crypto_libcrypto():
def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise DrmException('AES improper key used')
raise DrmException(u"AES improper key used")
return
keyctx = self._keyctx = AES_KEY()
self._iv = iv
self._userkey = userkey
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0:
raise DrmException('Failed to initialize AES key')
raise DrmException(u"Failed to initialize AES key")
def decrypt(self, data):
out = create_string_buffer(len(data))
@@ -95,7 +98,7 @@ def _load_crypto_libcrypto():
keyctx = self._keyctx
rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
if rv == 0:
raise DrmException('AES decryption failed')
raise DrmException(u"AES decryption failed")
return out.raw
def keyivgen(self, passwd, salt, iter, keylen):
@@ -139,20 +142,20 @@ def SHA256(message):
return ctx.digest()
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
charMap2 = 'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
# 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
charMap5 = charMap2
# new in K4M 1.9.X
testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
testMap8 = 'YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD'
def encode(data, map):
result = ""
result = ''
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
@@ -167,14 +170,14 @@ def encodeHash(data,map):
# Decode the string in data with the characters in map. Returns the decoded bytes
def decode(data,map):
result = ""
result = ''
for i in range (0,len(data)-1,2):
high = map.find(data[i])
low = map.find(data[i+1])
if (high == -1) or (low == -1) :
break
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
result += pack("B",value)
result += pack('B',value)
return result
# For K4M 1.6.X and later
@@ -200,7 +203,7 @@ def primes(n):
# uses a sub process to get the Hard Drive Serial Number using ioreg
# returns with the serial number of drive whose BSD Name is "disk0"
# returns with the serial number of drive whose BSD Name is 'disk0'
def GetVolumeSerialNumber():
sernum = os.getenv('MYSERIALNUMBER')
if sernum != None:
@@ -216,11 +219,11 @@ def GetVolumeSerialNumber():
foundIt = False
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('"Serial Number" = "')
pp = resline.find('\"Serial Number\" = \"')
if pp >= 0:
sernum = resline[pp+19:-1]
sernum = sernum.strip()
bb = resline.find('"BSD Name" = "')
bb = resline.find('\"BSD Name\" = \"')
if bb >= 0:
bsdname = resline[bb+14:-1]
bsdname = bsdname.strip()
@@ -233,7 +236,7 @@ def GetVolumeSerialNumber():
def GetUserHomeAppSupKindleDirParitionName():
home = os.getenv('HOME')
dpath = home + '/Library/Application Support/Kindle'
dpath = home + '/Library'
cmdline = '/sbin/mount'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
@@ -277,7 +280,7 @@ def GetDiskPartitionUUID(diskpart):
nest += 1
if resline.find('}') >= 0:
nest -= 1
pp = resline.find('"UUID" = "')
pp = resline.find('\"UUID\" = \"')
if pp >= 0:
uuidnum = resline[pp+10:-1]
uuidnum = uuidnum.strip()
@@ -285,7 +288,7 @@ def GetDiskPartitionUUID(diskpart):
if partnest == uuidnest and uuidnest > 0:
foundIt = True
break
bb = resline.find('"BSD Name" = "')
bb = resline.find('\"BSD Name\" = \"')
if bb >= 0:
bsdname = resline[bb+14:-1]
bsdname = bsdname.strip()
@@ -323,7 +326,7 @@ def GetMACAddressMunged():
if pp >= 0:
macnum = resline[pp+6:-1]
macnum = macnum.strip()
# print "original mac", macnum
# print 'original mac', macnum
# now munge it up the way Kindle app does
# by xoring it with 0xa5 and swapping elements 3 and 4
maclst = macnum.split(':')
@@ -340,7 +343,7 @@ def GetMACAddressMunged():
mlst[2] = maclst[2] ^ 0xa5
mlst[1] = maclst[1] ^ 0xa5
mlst[0] = maclst[0] ^ 0xa5
macnum = "%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x" % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
macnum = '%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x' % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
foundIt = True
break
if not foundIt:
@@ -358,11 +361,28 @@ def isNewInstall():
# soccer game fan anyone
dpath = home + '/Library/Application Support/Kindle/storage/.pes2011'
# 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):
return True
return False
class Memoize:
"""Memoize(fn) - an instance which acts like fn but memoizes its arguments
Will only work on functions with non-mutable arguments
"""
def __init__(self, fn):
self.fn = fn
self.memo = {}
def __call__(self, *args):
if not self.memo.has_key(args):
self.memo[args] = self.fn(*args)
return self.memo[args]
@Memoize
def GetIDString():
# K4Mac now has an extensive set of ids strings it uses
# in encoding pids and in creating unique passwords
@@ -490,51 +510,44 @@ class CryptUnprotectDataV3(object):
# Locate the .kindle-info files
def getKindleInfoFiles(kInfoFiles):
# first search for current .kindle-info files
home = os.getenv('HOME')
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
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')
kinfopath = 'NONE'
def getKindleInfoFiles():
# file searches can take a long time on some systems, so just look in known specific places.
kInfoFiles=[]
found = False
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found K4Mac kindle-info file: ' + resline)
found = True
# add any .rainier*-kinf files
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
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 kinf file: ' + resline)
found = True
# add 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
home = os.getenv('HOME')
# check for .kinf2011 file in new location (App Store Kindle for Mac)
testpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.kinf2011'
if os.path.isfile(testpath):
kInfoFiles.append(testpath)
print('Found k4Mac kinf2011 file: ' + testpath)
found = True
# check for .kinf2011 files
testpath = home + '/Library/Application Support/Kindle/storage/.kinf2011'
if os.path.isfile(testpath):
kInfoFiles.append(testpath)
print('Found k4Mac kinf2011 file: ' + testpath)
found = True
# check for .rainier-2.1.1-kinf files
testpath = home + '/Library/Application Support/Kindle/storage/.rainier-2.1.1-kinf'
if os.path.isfile(testpath):
kInfoFiles.append(testpath)
print('Found k4Mac rainier file: ' + testpath)
found = True
# check for .rainier-2.1.1-kinf files
testpath = home + '/Library/Application Support/Kindle/storage/.kindle-info'
if os.path.isfile(testpath):
kInfoFiles.append(testpath)
print('Found k4Mac kindle-info file: ' + testpath)
found = True
if not found:
print('No k4Mac kindle-info/kinf/kinf2011 files have been found.')
print('No k4Mac kindle-info/rainier/kinf2011 files have been found.')
return kInfoFiles
# determine type of kindle info provided and return a
# database of keynames and values
def getDBfromFile(kInfoFile):
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF']
DB = {}
cnt = 0
infoReader = open(kInfoFile, 'r')
@@ -549,12 +562,12 @@ def getDBfromFile(kInfoFile):
for item in items:
if item != '':
keyhash, rawdata = item.split(':')
keyname = "unknown"
keyname = 'unknown'
for name in names:
if encodeHash(name,charMap2) == keyhash:
keyname = name
break
if keyname == "unknown":
if keyname == 'unknown':
keyname = keyhash
encryptedValue = decode(rawdata,charMap2)
cleartext = cud.decrypt(encryptedValue)
@@ -567,8 +580,8 @@ def getDBfromFile(kInfoFile):
if hdr == '/':
# 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
# 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()
@@ -582,11 +595,11 @@ def getDBfromFile(kInfoFile):
# 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"
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' not used for K4Mac only K4PC
# entropy = SHA1(keyhash)
# the remainder of the first record when decoded with charMap5
@@ -603,12 +616,12 @@ def getDBfromFile(kInfoFile):
item = items.pop(0)
edlst.append(item)
keyname = "unknown"
keyname = 'unknown'
for name in names:
if encodeHash(name,charMap5) == keyhash:
keyname = name
break
if keyname == "unknown":
if keyname == 'unknown':
keyname = keyhash
# the charMap5 encoded contents data has had a length
@@ -619,10 +632,10 @@ def getDBfromFile(kInfoFile):
# 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)
# (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)
encdata = ''.join(edlst)
contlen = len(encdata)
# now properly split and recombine
@@ -671,7 +684,7 @@ def getDBfromFile(kInfoFile):
# 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"
keyname = 'unknown'
# unlike K4PC the keyhash is not used in generating entropy
# entropy = SHA1(keyhash) + added_entropy
@@ -691,12 +704,12 @@ def getDBfromFile(kInfoFile):
item = items.pop(0)
edlst.append(item)
keyname = "unknown"
keyname = 'unknown'
for name in names:
if encodeHash(name,testMap8) == keyhash:
keyname = name
break
if keyname == "unknown":
if keyname == 'unknown':
keyname = keyhash
# the testMap8 encoded contents data has had a length
@@ -707,10 +720,10 @@ def getDBfromFile(kInfoFile):
# The offset into the testMap8 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)
# (in other words split 'about' 2/3rds of the way through)
# move first offsets chars to end to align for decode by testMap8
encdata = "".join(edlst)
encdata = ''.join(edlst)
contlen = len(encdata)
# now properly split and recombine

View File

@@ -1,4 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# K4PC Windows specific routines
from __future__ import with_statement
@@ -203,46 +205,64 @@ CryptUnprotectData = CryptUnprotectData()
# Locate all of the kindle-info style files and return as list
def getKindleInfoFiles(kInfoFiles):
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
def getKindleInfoFiles():
kInfoFiles = []
# 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
path = ""
if 'LOCALAPPDATA' in os.environ.keys():
path = os.environ['LOCALAPPDATA']
else:
# User Shell Folders show take precedent over Shell Folders if present
try:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
if not os.path.isdir(path):
path = ""
try:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
if not os.path.isdir(path):
path = ""
except RegError:
pass
except RegError:
pass
print('searching for kinfoFiles in ' + path)
found = False
if path == "":
print ('Could not find the folder in which to look for kinfoFiles.')
else:
print('searching for kinfoFiles in ' + path)
# first look for older kindle-info files
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if os.path.isfile(kinfopath):
found = True
print('Found K4PC kindle.info file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# first look for older kindle-info files
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if os.path.isfile(kinfopath):
found = True
print('Found K4PC kindle.info file: ' + 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'
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.5.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath)
kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.5.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# 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'
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.6.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# 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'
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.6.X kinf file: ' + 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)
# 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.')

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
import sys
@@ -17,26 +18,24 @@ global charMap4
if 'calibre' in sys.modules:
inCalibre = True
else:
inCalibre = False
if inCalibre:
if sys.platform.startswith('win'):
from calibre.constants import iswindows, isosx
if iswindows:
from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
if sys.platform.startswith('darwin'):
if isosx:
from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
else:
if sys.platform.startswith('win'):
inCalibre = False
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
if iswindows:
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
if sys.platform.startswith('darwin'):
if isosx:
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
charMap3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
charMap4 = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
# crypto digestroutines
import hashlib
@@ -54,7 +53,7 @@ def SHA1(message):
# Encode the bytes in data with the characters in map
def encode(data, map):
result = ""
result = ''
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
@@ -69,14 +68,14 @@ def encodeHash(data,map):
# Decode the string in data with the characters in map. Returns the decoded bytes
def decode(data,map):
result = ""
result = ''
for i in range (0,len(data)-1,2):
high = map.find(data[i])
low = map.find(data[i+1])
if (high == -1) or (low == -1) :
break
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
result += pack("B",value)
result += pack('B',value)
return result
#
@@ -98,7 +97,7 @@ def getSixBitsFromBitField(bitField,offset):
# 8 bits to six bits encoding from hash to generate PID string
def encodePID(hash):
global charMap3
PID = ""
PID = ''
for position in range (0,8):
PID += charMap3[getSixBitsFromBitField(hash,position)]
return PID
@@ -129,7 +128,7 @@ def generatePidSeed(table,dsn) :
def generateDevicePID(table,dsn,nbRoll):
global charMap4
seed = generatePidSeed(table,dsn)
pidAscii = ""
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):
@@ -176,28 +175,31 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum):
def getKindlePids(rec209, token, serialnum):
pids=[]
# Compute book PID
pidHash = SHA1(serialnum+rec209+token)
bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID)
pidlst.append(bookPID)
pids.append(bookPID)
# compute fixed pid for old pre 2.5 firmware update pid as well
bookPID = pidFromSerial(serialnum, 7) + "*"
bookPID = checksumPid(bookPID)
pidlst.append(bookPID)
kindlePID = pidFromSerial(serialnum, 7) + "*"
kindlePID = checksumPid(kindlePID)
pids.append(kindlePID)
return pidlst
return pids
# parse the Kindleinfo file to calculate the book pid.
keynames = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
keynames = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber']
def getK4Pids(pidlst, rec209, token, kInfoFile):
def getK4Pids(rec209, token, kInfoFile):
global charMap1
kindleDatabase = None
pids = []
try:
kindleDatabase = getDBfromFile(kInfoFile)
except Exception, message:
@@ -206,17 +208,17 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
pass
if kindleDatabase == None :
return pidlst
return pids
try:
# Get the Mazama Random number
MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"]
MazamaRandomNumber = kindleDatabase['MazamaRandomNumber']
# Get the kindle account token
kindleAccountToken = kindleDatabase["kindle.account.tokens"]
kindleAccountToken = kindleDatabase['kindle.account.tokens']
except KeyError:
print "Keys not found in " + kInfoFile
return pidlst
print u"Keys not found in {0}".format(os.path.basename(kInfoFile))
return pids
# Get the ID string used
encodedIDString = encodeHash(GetIDString(),charMap1)
@@ -231,7 +233,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
table = generatePidEncryptionTable()
devicePID = generateDevicePID(table,DSN,4)
devicePID = checksumPid(devicePID)
pidlst.append(devicePID)
pids.append(devicePID)
# Compute book PIDs
@@ -239,38 +241,38 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID)
pidlst.append(bookPID)
pids.append(bookPID)
# variant 1
pidHash = SHA1(kindleAccountToken+rec209+token)
bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID)
pidlst.append(bookPID)
pids.append(bookPID)
# variant 2
pidHash = SHA1(DSN+rec209+token)
bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID)
pidlst.append(bookPID)
pids.append(bookPID)
return pidlst
return pids
def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
def getPidList(md1, md2, serials=[], kInfoFiles=[]):
pidlst = []
if kInfoFiles is None:
kInfoFiles = []
if k4:
kInfoFiles = getKindleInfoFiles(kInfoFiles)
if serials is None:
serials = []
if iswindows or isosx:
kInfoFiles.extend(getKindleInfoFiles())
for infoFile in kInfoFiles:
try:
pidlst = getK4Pids(pidlst, md1, md2, infoFile)
except Exception, message:
print("Error getting PIDs from " + infoFile + ": " + message)
pidlst.extend(getK4Pids(md1, md2, infoFile))
except Exception, e:
print u"Error getting PIDs from {0}: {1}".format(os.path.basename(infoFile),e.args[0])
for serialnum in serials:
try:
pidlst = getKindlePid(pidlst, md1, md2, serialnum)
pidlst.extend(getKindlePids(md1, md2, serialnum))
except Exception, message:
print("Error getting PIDs from " + serialnum + ": " + message)
for pid in pids:
pidlst.append(pid)
print u"Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0])
return pidlst

View File

@@ -1,29 +1,80 @@
#!/usr/bin/python
# Mobipocket PID calculator v0.2 for Amazon Kindle.
# -*- coding: utf-8 -*-
# Mobipocket PID calculator v0.4 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:
# 0.3 updated for unicode
import sys
import binascii
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
import sys
sys.stdout=Unbuffered(sys.stdout)
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
import binascii
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
if sys.hexversion >= 0x3000000:
print "This script is incompatible with Python 3.x. Please install Python 2.6.x from python.org"
print 'This script is incompatible with Python 3.x. Please install Python 2.7.x.'
sys.exit(2)
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
def crc32(s):
return (~binascii.crc32(s,-1))&0xFFFFFFFF
@@ -53,39 +104,39 @@ def pidFromSerial(s, l):
for i in xrange(l):
arr1[i] ^= crc_bytes[i&3]
pid = ""
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"
def cli_main(argv=unicode_argv()):
print u"Mobipocket PID calculator for Amazon Kindle. Copyright © 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>"
print u"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"
print u"Kindle serial number detected"
else:
print "Warning: unrecognized serial number. Please recheck input."
print u"Warning: unrecognized serial number. Please recheck input."
return 1
pid = pidFromSerial(serial,7)+"*"
print "Mobipocket PID for Kindle serial# "+serial+" is "+checksumPid(pid)
pid = pidFromSerial(serial.encode("utf-8"),7)+'*'
print u"Mobipocket PID for Kindle serial#{0} is {1} ".format(serial,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)
print u"iPhone serial number (UDID) detected"
pid = pidFromSerial(serial.encode("utf-8"),8)
print u"Mobipocket PID for iPhone serial#{0} is {1} ".format(serial,checksumPid(pid))
return 0
else:
print "Warning: unrecognized serial number. Please recheck input."
return 1
return 0
print u"Warning: unrecognized serial number. Please recheck input."
return 1
if __name__ == "__main__":
sys.exit(main())
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())

View File

@@ -1,5 +1,11 @@
#!/usr/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# mobidedrm.py, version 0.38
# Copyright © 2008 The Dark Reverser
#
# Modified 20082012 by some_updates, DiapDealer and Apprentice Alf
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
@@ -59,26 +65,78 @@
# 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
# 0.38 - Unicode used wherever possible, cope with absent alfcrypto
__version__ = '0.37'
__version__ = u"0.38"
import sys
import os
import struct
import binascii
try:
from alfcrypto import Pukall_Cipher
except:
print u"AlfCrypto not found. Using python PC1 implementation."
class Unbuffered:
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
sys.stdout=Unbuffered(sys.stdout)
import os
import struct
import binascii
from alfcrypto import Pukall_Cipher
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = 'utf-8'
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
class DrmException(Exception):
pass
@@ -90,40 +148,45 @@ class DrmException(Exception):
# Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True):
return Pukall_Cipher().PC1(key,src,decryption)
# 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
# if we can get it from alfcrypto, use that
try:
return Pukall_Cipher().PC1(key,src,decryption)
except NameError:
pass
# use slow python version, since Pukall_Cipher didn't load
sum1 = 0;
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
DrmException (u"PC1: Bad key length")
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
def checksumPid(s):
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
crc = crc ^ (crc >> 16)
res = s
@@ -171,17 +234,24 @@ class MobiBook:
off = self.sections[section][0]
return self.data_file[off:endoff]
def __init__(self, infile, announce = True):
if announce:
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
def cleanup(self):
# to match function in Topaz book
pass
def __init__(self, infile):
print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
try:
from alfcrypto import Pukall_Cipher
except:
print u"AlfCrypto not found. Using python PC1 implementation."
# initial sanity check on file
self.data_file = file(infile, 'rb').read()
self.mobi_data = ''
self.header = self.data_file[0:78]
if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
raise DrmException("invalid file format")
raise DrmException(u"Invalid file format")
self.magic = self.header[0x3C:0x3C+8]
self.crypto_type = -1
@@ -199,7 +269,7 @@ class MobiBook:
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic
print u"PalmDoc format book detected."
self.extra_data_flags = 0
self.mobi_length = 0
self.mobi_codepage = 1252
@@ -209,11 +279,11 @@ class MobiBook:
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
print u"MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length)
self.extra_data_flags = 0
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags
print u"Extra Data Flags: {0:d}".format(self.extra_data_flags)
if (self.compression != 17480):
# multibyte utf8 data is included in the encryption for PalmDoc compression
# so clear that byte so that we leave it to be decrypted.
@@ -223,10 +293,10 @@ class MobiBook:
self.meta_array = {}
try:
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
exth = 'NONE'
exth = ''
if exth_flag & 0x40:
exth = self.sect[16 + self.mobi_length:]
if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
if (len(exth) >= 12) and (exth[:4] == 'EXTH'):
nitems, = struct.unpack('>I', exth[8:12])
pos = 12
for i in xrange(nitems):
@@ -236,10 +306,10 @@ class MobiBook:
# reset the text to speech flag and clipping limit, if present
if type == 401 and size == 9:
# set clipping limit to 100%
self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8)
self.patchSection(0, '\144', 16 + self.mobi_length + pos + 8)
elif type == 404 and size == 9:
# make sure text to speech is enabled
self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8)
self.patchSection(0, '\0', 16 + self.mobi_length + pos + 8)
# print type, size, content, content.encode('hex')
pos += size
except:
@@ -265,8 +335,8 @@ class MobiBook:
codec = codec_map[self.mobi_codepage]
if title == '':
title = self.header[:32]
title = title.split("\0")[0]
return unicode(title, codec).encode('utf-8')
title = title.split('\0')[0]
return unicode(title, codec)
def getPIDMetaInfo(self):
rec209 = ''
@@ -297,7 +367,7 @@ class MobiBook:
def parseDRM(self, data, count, pidlist):
found_key = None
keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
keyvec1 = '\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96'
for pid in pidlist:
bigpid = pid.ljust(16,'\0')
temp_key = PC1(keyvec1, bigpid, False)
@@ -315,7 +385,7 @@ class MobiBook:
break
if not found_key:
# Then try the default encoding that doesn't require a PID
pid = "00000000"
pid = '00000000'
temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff
for i in xrange(count):
@@ -328,82 +398,90 @@ class MobiBook:
break
return [found_key,pid]
def getMobiFile(self, outpath):
def getFile(self, outpath):
file(outpath,'wb').write(self.mobi_data)
def getMobiVersion(self):
return self.mobi_version
def getBookType(self):
if self.print_replica:
return u"Print Replica"
if self.mobi_version >= 8:
return u"Kindle Format 8"
return u"Mobipocket"
def getPrintReplica(self):
return self.print_replica
def getBookExtension(self):
if self.print_replica:
return u".azw4"
if self.mobi_version >= 8:
return u".azw3"
return u".mobi"
def processBook(self, pidlist):
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
print 'Crypto Type is: ', crypto_type
print u"Crypto Type is: {0:d}".format(crypto_type)
self.crypto_type = crypto_type
if crypto_type == 0:
print "This book is not encrypted."
print u"This book is not encrypted."
# we must still check for Print Replica
self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
self.mobi_data = self.data_file
return
if crypto_type != 2 and crypto_type != 1:
raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
raise DrmException(u"Cannot decode unknown Mobipocket encryption type {0:d}".format(crypto_type))
if 406 in self.meta_array:
data406 = self.meta_array[406]
val406, = struct.unpack('>Q',data406)
if val406 != 0:
raise DrmException("Cannot decode library or rented ebooks.")
raise DrmException(u"Cannot decode library or rented ebooks.")
goodpids = []
for pid in pidlist:
if len(pid)==10:
if checksumPid(pid[0:-2]) != pid:
print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2])
print u"Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2]))
goodpids.append(pid[0:-2])
elif len(pid)==8:
goodpids.append(pid)
if self.crypto_type == 1:
t1_keyvec = "QDCVEPMU675RUBSZ"
t1_keyvec = 'QDCVEPMU675RUBSZ'
if self.magic == 'TEXtREAd':
bookkey_data = self.sect[0x0E:0x0E+16]
elif self.mobi_version < 0:
bookkey_data = self.sect[0x90:0x90+16]
else:
bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32]
pid = "00000000"
pid = '00000000'
found_key = PC1(t1_keyvec, bookkey_data)
else :
# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
if drm_count == 0:
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
raise DrmException(u"Encryption not initialised. Must be opened with Mobipocket Reader first.")
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key:
raise DrmException("No key found in " + str(len(goodpids)) + " keys tried. Please report this failure for help.")
raise DrmException(u"No key found in {0:d} keys tried. Read the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(len(goodpids)))
# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
self.patchSection(0, '\0' * drm_size, drm_ptr)
# kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
self.patchSection(0, '\xff' * 4 + '\0' * 12, 0xA8)
if pid=="00000000":
print "File has default encryption, no specific PID."
if pid=='00000000':
print u"File has default encryption, no specific key needed."
else:
print "File is encoded with PID "+checksumPid(pid)+"."
print u"File is encoded with PID {0}.".format(checksumPid(pid))
# clear the crypto type
self.patchSection(0, "\0" * 2, 0xC)
# decrypt sections
print "Decrypting. Please wait . . .",
print u"Decrypting. Please wait . . .",
mobidataList = []
mobidataList.append(self.data_file[:self.sections[1][0]])
for i in xrange(1, self.records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
if i%100 == 0:
print ".",
print u".",
# print "record %d, extra_size %d" %(i,extra_size)
decoded_data = PC1(found_key, data[0:len(data) - extra_size])
if i==1:
@@ -414,31 +492,24 @@ class MobiBook:
if self.num_sections > self.records+1:
mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
self.mobi_data = "".join(mobidataList)
print "done"
print u"done"
return
def getUnencryptedBook(infile,pid,announce=True):
def getUnencryptedBook(infile,pidlist):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile,announce)
book.processBook([pid])
return book.mobi_data
def getUnencryptedBookWithList(infile,pidlist,announce=True):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile, announce)
raise DrmException(u"Input File Not Found.")
book = MobiBook(infile)
book.processBook(pidlist)
return book.mobi_data
def main(argv=sys.argv):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
if len(argv)<3 or len(argv)>4:
print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
print "Usage:"
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
print u"MobiDeDrm v{0}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
print u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
print u"Usage:"
print u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(os.path.basename(sys.argv[0]))
return 1
else:
infile = argv[1]
@@ -446,15 +517,17 @@ def main(argv=sys.argv):
if len(argv) is 4:
pidlist = argv[3].split(',')
else:
pidlist = {}
pidlist = []
try:
stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
stripped_file = getUnencryptedBook(infile, pidlist)
file(outfile, 'wb').write(stripped_file)
except DrmException, e:
print "Error: %s" % e
print u"MobiDeDRM v{0} Error: {0:s}".format(__version__,e.args[0])
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
if __name__ == '__main__':
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())

View File

@@ -1,68 +0,0 @@
# 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

@@ -1,43 +1,90 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
class Unbuffered:
# topazextract.py, version ?
# Mostly written by some_updates based on code from many others
__version__ = '4.8'
import sys
import os, csv, getopt
import zlib, zipfile, tempfile, shutil
import traceback
from struct import pack
from struct import unpack
from alfcrypto import Topaz_Cipher
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
import sys
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'.
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = 'utf-8'
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
if 'calibre' in sys.modules:
inCalibre = True
else:
inCalibre = False
buildXML = False
import os, csv, getopt
import zlib, zipfile, tempfile, shutil
from struct import pack
from struct import unpack
from alfcrypto import Topaz_Cipher
class TpzDRMError(Exception):
pass
# local support routines
if inCalibre:
from calibre_plugins.k4mobidedrm import kgenpids
else:
inCalibre = False
import kgenpids
class DrmException(Exception):
pass
# recursive zip creation support routine
def zipUpDir(myzip, tdir, localname):
currentdir = tdir
if localname != "":
if localname != u"":
currentdir = os.path.join(currentdir,localname)
list = os.listdir(currentdir)
for file in list:
@@ -73,7 +120,7 @@ def bookReadEncodedNumber(fo):
# Get a length prefixed string from file
def bookReadString(fo):
stringLength = bookReadEncodedNumber(fo)
return unpack(str(stringLength)+"s",fo.read(stringLength))[0]
return unpack(str(stringLength)+'s',fo.read(stringLength))[0]
#
# crypto routines
@@ -112,13 +159,13 @@ def decryptRecord(data,PID):
# Try to decrypt a dkey record (contains the bookPID)
def decryptDkeyRecord(data,PID):
record = decryptRecord(data,PID)
fields = unpack("3sB8sB8s3s",record)
if fields[0] != "PID" or fields[5] != "pid" :
raise TpzDRMError("Didn't find PID magic numbers in record")
fields = unpack('3sB8sB8s3s',record)
if fields[0] != 'PID' or fields[5] != 'pid' :
raise DrmException(u"Didn't find PID magic numbers in record")
elif fields[1] != 8 or fields[3] != 8 :
raise TpzDRMError("Record didn't contain correct length fields")
raise DrmException(u"Record didn't contain correct length fields")
elif fields[2] != PID :
raise TpzDRMError("Record didn't contain PID")
raise DrmException(u"Record didn't contain PID")
return fields[4]
# Decrypt all dkey records (contain the book PID)
@@ -131,11 +178,11 @@ def decryptDkeyRecords(data,PID):
try:
key = decryptDkeyRecord(data[1:length+1],PID)
records.append(key)
except TpzDRMError:
except DrmException:
pass
data = data[1+length:]
if len(records) == 0:
raise TpzDRMError("BookKey Not Found")
raise DrmException(u"BookKey Not Found")
return records
@@ -148,9 +195,9 @@ class TopazBook:
self.bookHeaderRecords = {}
self.bookMetadata = {}
self.bookKey = None
magic = unpack("4s",self.fo.read(4))[0]
magic = unpack('4s',self.fo.read(4))[0]
if magic != 'TPZ0':
raise TpzDRMError("Parse Error : Invalid Header, not a Topaz file")
raise DrmException(u"Parse Error : Invalid Header, not a Topaz file")
self.parseTopazHeaders()
self.parseMetadata()
@@ -167,7 +214,7 @@ class TopazBook:
# Read and parse one header record at the current book file position and return the associated data
# [[offset,decompressedLength,compressedLength],...]
if ord(self.fo.read(1)) != 0x63:
raise TpzDRMError("Parse Error : Invalid Header")
raise DrmException(u"Parse Error : Invalid Header")
tag = bookReadString(self.fo)
record = bookReadHeaderRecordData()
return [tag,record]
@@ -177,15 +224,15 @@ class TopazBook:
# print result[0], result[1]
self.bookHeaderRecords[result[0]] = result[1]
if ord(self.fo.read(1)) != 0x64 :
raise TpzDRMError("Parse Error : Invalid Header")
raise DrmException(u"Parse Error : Invalid Header")
self.bookPayloadOffset = self.fo.tell()
def parseMetadata(self):
# Parse the metadata record from the book payload and return a list of [key,values]
self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords["metadata"][0][0])
self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords['metadata'][0][0])
tag = bookReadString(self.fo)
if tag != "metadata" :
raise TpzDRMError("Parse Error : Record Names Don't Match")
if tag != 'metadata' :
raise DrmException(u"Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1))
# print nbRecords
@@ -210,7 +257,7 @@ class TopazBook:
title = ''
if 'Title' in self.bookMetadata:
title = self.bookMetadata['Title']
return title
return title.decode('utf-8')
def setBookKey(self, key):
self.bookKey = key
@@ -223,13 +270,13 @@ class TopazBook:
try:
recordOffset = self.bookHeaderRecords[name][index][0]
except:
raise TpzDRMError("Parse Error : Invalid Record, record not found")
raise DrmException("Parse Error : Invalid Record, record not found")
self.fo.seek(self.bookPayloadOffset + recordOffset)
tag = bookReadString(self.fo)
if tag != name :
raise TpzDRMError("Parse Error : Invalid Record, record name doesn't match")
raise DrmException("Parse Error : Invalid Record, record name doesn't match")
recordIndex = bookReadEncodedNumber(self.fo)
if recordIndex < 0 :
@@ -237,7 +284,7 @@ class TopazBook:
recordIndex = -recordIndex -1
if recordIndex != index :
raise TpzDRMError("Parse Error : Invalid Record, index doesn't match")
raise DrmException("Parse Error : Invalid Record, index doesn't match")
if (self.bookHeaderRecords[name][index][2] > 0):
compressed = True
@@ -250,7 +297,7 @@ class TopazBook:
ctx = topazCryptoInit(self.bookKey)
record = topazCryptoDecrypt(record,ctx)
else :
raise TpzDRMError("Error: Attempt to decrypt without bookKey")
raise DrmException("Error: Attempt to decrypt without bookKey")
if compressed:
record = zlib.decompress(record)
@@ -262,12 +309,12 @@ class TopazBook:
fixedimage=True
try:
keydata = self.getBookPayloadRecord('dkey', 0)
except TpzDRMError, e:
print "no dkey record found, book may not be encrypted"
print "attempting to extrct files without a book key"
except DrmException, e:
print u"no dkey record found, book may not be encrypted"
print u"attempting to extrct files without a book key"
self.createBookDirectory()
self.extractFiles()
print "Successfully Extracted Topaz contents"
print u"Successfully Extracted Topaz contents"
if inCalibre:
from calibre_plugins.k4mobidedrm import genbook
else:
@@ -275,7 +322,7 @@ class TopazBook:
rv = genbook.generateBook(self.outdir, raw, fixedimage)
if rv == 0:
print "\nBook Successfully generated"
print u"Book Successfully generated."
return rv
# try each pid to decode the file
@@ -283,25 +330,25 @@ class TopazBook:
for pid in pidlst:
# use 8 digit pids here
pid = pid[0:8]
print "\nTrying: ", pid
print u"Trying: {0}".format(pid)
bookKeys = []
data = keydata
try:
bookKeys+=decryptDkeyRecords(data,pid)
except TpzDRMError, e:
except DrmException, e:
pass
else:
bookKey = bookKeys[0]
print "Book Key Found!"
print u"Book Key Found! ({0})".format(bookKey.encode('hex'))
break
if not bookKey:
raise TpzDRMError('Decryption Unsucessful; No valid pid found')
raise DrmException(u"No key found in {0:d} keys tried. Read the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(len(pidlst)))
self.setBookKey(bookKey)
self.createBookDirectory()
self.extractFiles()
print "Successfully Extracted Topaz contents"
print u"Successfully Extracted Topaz contents"
if inCalibre:
from calibre_plugins.k4mobidedrm import genbook
else:
@@ -309,7 +356,7 @@ class TopazBook:
rv = genbook.generateBook(self.outdir, raw, fixedimage)
if rv == 0:
print "\nBook Successfully generated"
print u"Book Successfully generated"
return rv
def createBookDirectory(self):
@@ -317,16 +364,16 @@ class TopazBook:
# create output directory structure
if not os.path.exists(outdir):
os.makedirs(outdir)
destdir = os.path.join(outdir,'img')
destdir = os.path.join(outdir,u"img")
if not os.path.exists(destdir):
os.makedirs(destdir)
destdir = os.path.join(outdir,'color_img')
destdir = os.path.join(outdir,u"color_img")
if not os.path.exists(destdir):
os.makedirs(destdir)
destdir = os.path.join(outdir,'page')
destdir = os.path.join(outdir,u"page")
if not os.path.exists(destdir):
os.makedirs(destdir)
destdir = os.path.join(outdir,'glyphs')
destdir = os.path.join(outdir,u"glyphs")
if not os.path.exists(destdir):
os.makedirs(destdir)
@@ -334,149 +381,148 @@ class TopazBook:
outdir = self.outdir
for headerRecord in self.bookHeaderRecords:
name = headerRecord
if name != "dkey" :
ext = '.dat'
if name == 'img' : ext = '.jpg'
if name == 'color' : ext = '.jpg'
print "\nProcessing Section: %s " % name
if name != 'dkey':
ext = u".dat"
if name == 'img': ext = u".jpg"
if name == 'color' : ext = u".jpg"
print u"Processing Section: {0}\n. . .".format(name),
for index in range (0,len(self.bookHeaderRecords[name])) :
fnum = "%04d" % index
fname = name + fnum + ext
fname = u"{0}{1:04d}{2}".format(name,index,ext)
destdir = outdir
if name == 'img':
destdir = os.path.join(outdir,'img')
destdir = os.path.join(outdir,u"img")
if name == 'color':
destdir = os.path.join(outdir,'color_img')
destdir = os.path.join(outdir,u"color_img")
if name == 'page':
destdir = os.path.join(outdir,'page')
destdir = os.path.join(outdir,u"page")
if name == 'glyphs':
destdir = os.path.join(outdir,'glyphs')
destdir = os.path.join(outdir,u"glyphs")
outputFile = os.path.join(destdir,fname)
print ".",
print u".",
record = self.getBookPayloadRecord(name,index)
if record != '':
file(outputFile, 'wb').write(record)
print " "
print u" "
def getHTMLZip(self, zipname):
def getFile(self, zipname):
htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
htmlzip.write(os.path.join(self.outdir,'book.html'),'book.html')
htmlzip.write(os.path.join(self.outdir,'book.opf'),'book.opf')
if os.path.isfile(os.path.join(self.outdir,'cover.jpg')):
htmlzip.write(os.path.join(self.outdir,'cover.jpg'),'cover.jpg')
htmlzip.write(os.path.join(self.outdir,'style.css'),'style.css')
zipUpDir(htmlzip, self.outdir, 'img')
htmlzip.write(os.path.join(self.outdir,u"book.html"),u"book.html")
htmlzip.write(os.path.join(self.outdir,u"book.opf"),u"book.opf")
if os.path.isfile(os.path.join(self.outdir,u"cover.jpg")):
htmlzip.write(os.path.join(self.outdir,u"cover.jpg"),u"cover.jpg")
htmlzip.write(os.path.join(self.outdir,u"style.css"),u"style.css")
zipUpDir(htmlzip, self.outdir, u"img")
htmlzip.close()
def getBookType(self):
return u"Topaz"
def getBookExtension(self):
return u".htmlz"
def getSVGZip(self, zipname):
svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
svgzip.write(os.path.join(self.outdir,'index_svg.xhtml'),'index_svg.xhtml')
zipUpDir(svgzip, self.outdir, 'svg')
zipUpDir(svgzip, self.outdir, 'img')
svgzip.write(os.path.join(self.outdir,u"index_svg.xhtml"),u"index_svg.xhtml")
zipUpDir(svgzip, self.outdir, u"svg")
zipUpDir(svgzip, self.outdir, u"img")
svgzip.close()
def getXMLZip(self, zipname):
xmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
targetdir = os.path.join(self.outdir,'xml')
zipUpDir(xmlzip, targetdir, '')
zipUpDir(xmlzip, self.outdir, 'img')
xmlzip.close()
def cleanup(self):
if os.path.isdir(self.outdir):
shutil.rmtree(self.outdir, True)
def usage(progname):
print "Removes DRM protection from Topaz ebooks and extract the contents"
print "Usage:"
print " %s [-k <kindle.info>] [-p <pidnums>] [-s <kindleSerialNumbers>] <infile> <outdir> " % progname
print u"Removes DRM protection from Topaz ebooks and extracts the contents"
print u"Usage:"
print u" {0} [-k <kindle.info>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname)
# Main
def main(argv=sys.argv):
global buildXML
def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
k4 = False
pids = []
serials = []
kInfoFiles = []
print u"TopazExtract v{0}.".format(__version__)
try:
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:x")
except getopt.GetoptError, err:
print str(err)
print u"Error in options or arguments: {0}".format(err.args[0])
usage(progname)
return 1
if len(args)<2:
usage(progname)
return 1
for o, a in opts:
if o == "-k":
if a == None :
print "Invalid parameter for -k"
return 1
kInfoFiles.append(a)
if o == "-p":
if a == None :
print "Invalid parameter for -p"
return 1
pids = a.split(',')
if o == "-s":
if a == None :
print "Invalid parameter for -s"
return 1
serials = a.split(',')
k4 = True
infile = args[0]
outdir = args[1]
if not os.path.isfile(infile):
print "Input File Does Not Exist"
print u"Input File {0} Does Not Exist.".format(infile)
return 1
if not os.path.exists(outdir):
print u"Output Directory {0} Does Not Exist.".format(outdir)
return 1
kInfoFiles = []
serials = []
pids = []
for o, a in opts:
if o == '-k':
if a == None :
raise DrmException("Invalid parameter for -k")
kInfoFiles.append(a)
if o == '-p':
if a == None :
raise DrmException("Invalid parameter for -p")
pids = a.split(',')
if o == '-s':
if a == None :
raise DrmException("Invalid parameter for -s")
serials = [serial.replace(" ","") for serial in a.split(',')]
bookname = os.path.splitext(os.path.basename(infile))[0]
tb = TopazBook(infile)
title = tb.getBookTitle()
print "Processing Book: ", title
keysRecord, keysRecordRecord = tb.getPIDMetaInfo()
pidlst = kgenpids.getPidList(keysRecord, keysRecordRecord, k4, pids, serials, kInfoFiles)
print u"Processing Book: {0}".format(title)
md1, md2 = tb.getPIDMetaInfo()
pids.extend(kgenpids.getPidList(md1, md2, serials, kInfoFiles))
try:
print "Decrypting Book"
tb.processBook(pidlst)
print u"Decrypting Book"
tb.processBook(pids)
print " Creating HTML ZIP Archive"
zipname = os.path.join(outdir, bookname + '_nodrm' + '.htmlz')
tb.getHTMLZip(zipname)
print u" Creating HTML ZIP Archive"
zipname = os.path.join(outdir, bookname + u"_nodrm.htmlz")
tb.getFile(zipname)
print " Creating SVG ZIP Archive"
zipname = os.path.join(outdir, bookname + '_SVG' + '.zip')
print u" Creating SVG ZIP Archive"
zipname = os.path.join(outdir, bookname + u"_SVG.zip")
tb.getSVGZip(zipname)
if buildXML:
print " Creating XML ZIP Archive"
zipname = os.path.join(outdir, bookname + '_XML' + '.zip')
tb.getXMLZip(zipname)
# removing internal temporary directory of pieces
tb.cleanup()
except TpzDRMError, e:
print str(e)
# tb.cleanup()
except DrmException, e:
print u"Decryption failed\n{0}".format(traceback.format_exc())
try:
tb.cleanup()
except:
pass
return 1
except Exception, e:
print str(e)
# tb.cleanup
print u"Decryption failed\m{0}".format(traceback.format_exc())
try:
tb.cleanup()
except:
pass
return 1
return 0
if __name__ == '__main__':
sys.stdout=Unbuffered(sys.stdout)
sys.exit(main())
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import zlib
import zipfile
import zipfilerugged
import os
import os.path
import getopt
@@ -15,7 +16,7 @@ _FILENAME_OFFSET = 30
_MAX_SIZE = 64 * 1024
_MIMETYPE = 'application/epub+zip'
class ZipInfo(zipfile.ZipInfo):
class ZipInfo(zipfilerugged.ZipInfo):
def __init__(self, *args, **kwargs):
if 'compress_type' in kwargs:
compress_type = kwargs.pop('compress_type')
@@ -27,8 +28,8 @@ class fixZip:
self.ztype = 'zip'
if zinput.lower().find('.epub') >= 0 :
self.ztype = 'epub'
self.inzip = zipfile.ZipFile(zinput,'r')
self.outzip = zipfile.ZipFile(zoutput,'w')
self.inzip = zipfilerugged.ZipFile(zinput,'r')
self.outzip = zipfilerugged.ZipFile(zoutput,'w')
# open the input zip for reading only as a raw file
self.bzf = file(zinput,'rb')
@@ -76,11 +77,11 @@ class fixZip:
data = None
# if not compressed we are good to go
if zi.compress_type == zipfile.ZIP_STORED:
if zi.compress_type == zipfilerugged.ZIP_STORED:
data = self.bzf.read(zi.file_size)
# if compressed we must decompress it using zlib
if zi.compress_type == zipfile.ZIP_DEFLATED:
if zi.compress_type == zipfilerugged.ZIP_DEFLATED:
cmpdata = self.bzf.read(zi.compress_size)
data = self.uncompress(cmpdata)
@@ -95,7 +96,7 @@ class fixZip:
# if epub write mimetype file first, with no compression
if self.ztype == 'epub':
nzinfo = ZipInfo('mimetype', compress_type=zipfile.ZIP_STORED)
nzinfo = ZipInfo('mimetype', compress_type=zipfilerugged.ZIP_STORED)
self.outzip.writestr(nzinfo, _MIMETYPE)
# write the rest of the files
@@ -105,7 +106,7 @@ class fixZip:
nzinfo = zinfo
try:
data = self.inzip.read(zinfo.filename)
except zipfile.BadZipfile or zipfile.error:
except zipfilerugged.BadZipfile or zipfilerugged.error:
local_name = self.getlocalname(zinfo)
data = self.getfiledata(zinfo)
nzinfo.filename = local_name

View File

@@ -1,9 +1,12 @@
#!/usr/bin/env python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
# -*- coding: utf-8 -*-
# DeDRM.pyw, version 5.5
# By some_updates and Apprentice Alf
import sys
import os, os.path
sys.path.append(sys.path[0]+os.sep+'lib')
sys.path.append(os.path.join(sys.path[0],"lib"))
os.environ['PYTHONIOENCODING'] = "utf-8"
import shutil
@@ -21,7 +24,7 @@ import re
import simpleprefs
__version__ = '5.2'
__version__ = '5.5'
class DrmException(Exception):
pass
@@ -142,21 +145,21 @@ class PrefsDialog(Toplevel):
button = Tkinter.Button(body, text="...", command=self.get_altinfopath)
button.grid(row=2, column=2)
Tkinter.Label(body, text='PID list (10 characters, no spaces, comma separated)').grid(row=3, sticky=Tkconstants.E)
Tkinter.Label(body, text='Mobipocket PID list\n(8 or 10 characters, comma separated)').grid(row=3, sticky=Tkconstants.E)
self.pidnums = Tkinter.StringVar()
self.pidinfo = Tkinter.Entry(body, width=50, textvariable=self.pidnums)
if 'pids' in self.prefs_array:
self.pidnums.set(self.prefs_array['pids'])
self.pidinfo.grid(row=3, column=1, sticky=sticky)
Tkinter.Label(body, text='Kindle Serial Number list (16 characters, no spaces, comma separated)').grid(row=4, sticky=Tkconstants.E)
Tkinter.Label(body, text='eInk Kindle Serial Number list\n(16 characters, first character B, comma separated)').grid(row=4, sticky=Tkconstants.E)
self.sernums = Tkinter.StringVar()
self.serinfo = Tkinter.Entry(body, width=50, textvariable=self.sernums)
if 'serials' in self.prefs_array:
self.sernums.set(self.prefs_array['serials'])
self.serinfo.grid(row=4, column=1, sticky=sticky)
Tkinter.Label(body, text='eReader data list (name:last 8 digits on credit card, comma separated)').grid(row=5, sticky=Tkconstants.E)
Tkinter.Label(body, text='eReader data list\n(name:last 8 digits on credit card, comma separated)').grid(row=5, sticky=Tkconstants.E)
self.sdrmnums = Tkinter.StringVar()
self.sdrminfo = Tkinter.Entry(body, width=50, textvariable=self.sdrmnums)
if 'sdrms' in self.prefs_array:
@@ -287,9 +290,9 @@ class PrefsDialog(Toplevel):
new_prefs = {}
prefdir = self.prefs_array['dir']
new_prefs['dir'] = prefdir
new_prefs['pids'] = self.pidinfo.get().strip()
new_prefs['serials'] = self.serinfo.get().strip()
new_prefs['sdrms'] = self.sdrminfo.get().strip()
new_prefs['pids'] = self.pidinfo.get().replace(" ","")
new_prefs['serials'] = self.serinfo.get().replace(" ","")
new_prefs['sdrms'] = self.sdrminfo.get().strip().replace(", ",",")
new_prefs['outdir'] = self.outpath.get().strip()
adkpath = self.adkpath.get()
if os.path.dirname(adkpath) != prefdir:
@@ -327,7 +330,7 @@ class ConvDialog(Toplevel):
self.running = 'inactive'
self.numgood = 0
self.numbad = 0
self.log = ''
self.log = u""
self.status = Tkinter.Label(self, text='DeDRM processing...')
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
@@ -375,18 +378,16 @@ class ConvDialog(Toplevel):
if len(self.filenames) > 0:
filename = self.filenames.pop(0)
if filename == None:
msg = '\nComplete: '
msg += 'Successes: %d, ' % self.numgood
msg += 'Failures: %d\n' % self.numbad
msg = u"\nComplete: Successes: {0}, Failures: {1}\n".format(self.numgood,self.numbad)
self.showCmdOutput(msg)
if self.numbad == 0:
self.after(2000,self.conversion_done())
logfile = os.path.join(rscpath,'dedrm.log')
file(logfile,'w').write(self.log)
file(logfile,'w').write(self.log.encode('utf8'))
return
infile = filename
bname = os.path.basename(infile)
msg = 'Processing: ' + bname + ' ... '
msg = u"Processing: {0} ... ".format(bname)
self.log += msg
self.showCmdOutput(msg)
outdir = os.path.dirname(filename)
@@ -400,7 +401,7 @@ class ConvDialog(Toplevel):
self.running = 'active'
self.processPipe()
else:
msg = 'Unknown File: ' + bname + '\n'
msg = u"Unknown File: {0}\n".format(bname)
self.log += msg
self.showCmdOutput(msg)
self.numbad += 1
@@ -433,18 +434,17 @@ class ConvDialog(Toplevel):
if poll != None:
self.bar.stop()
if poll == 0:
msg = 'Success\n'
msg = u"\nSuccess\n"
self.numgood += 1
text = self.p2.read()
text += self.p2.readerr()
text = self.p2.read().decode('utf8')
text += self.p2.readerr().decode('utf8')
self.log += text
self.log += msg
if poll != 0:
msg = 'Failed\n'
text = self.p2.read()
text += self.p2.readerr()
else:
text = self.p2.read().decode('utf8')
text += self.p2.readerr().decode('utf8')
msg += text
msg += '\n'
msg += u"\nFailed\n"
self.numbad += 1
self.log += msg
self.showCmdOutput(msg)
@@ -491,7 +491,7 @@ def runit(apphome, ncmd, nparms):
# cmdline = pengine + ' "' + os.path.join(apphome, ncmd) + '" '
cmdline += nparms
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, env = os.environ)
return p2
def processK4MOBI(apphome, infile, outdir, rscpath):

View File

@@ -1,11 +1,18 @@
#! /usr/bin/env python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# crypto library mainly by some_updates
# pbkdf2.py pbkdf2 code taken from pbkdf2.py
# pbkdf2.py Copyright © 2004 Matt Johnston <matt @ ucc asn au>
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
# pbkdf2.py This code may be freely used and modified for any purpose.
import sys, os
import hmac
from struct import pack
import hashlib
# interface to needed routines libalfcrypto
def _load_libalfcrypto():
import ctypes
@@ -27,7 +34,7 @@ def _load_libalfcrypto():
else:
name_of_lib = 'libalfcrypto64.so'
libalfcrypto = sys.path[0] + os.sep + name_of_lib
libalfcrypto = os.path.join(sys.path[0],name_of_lib)
if not os.path.isfile(libalfcrypto):
raise Exception('libalfcrypto not found')
@@ -147,7 +154,7 @@ def _load_libalfcrypto():
topazCryptoDecrypt(ctx, data, out, len(data))
return out.raw
print "Using Library AlfCrypto DLL/DYLIB/SO"
print u"Using Library AlfCrypto DLL/DYLIB/SO"
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
@@ -164,8 +171,7 @@ def _load_python_alfcrypto():
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
print "Bad key length!"
return None
raise Exception('Pukall_Cipher: Bad key length.')
wkey = []
for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
@@ -234,6 +240,7 @@ def _load_python_alfcrypto():
cleartext = self.aes.decrypt(iv + data)
return cleartext
print u"Using Library AlfCrypto Python"
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)

View File

@@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from PyQt4.Qt import QWidget, QVBoxLayout, QLabel, QLineEdit
from calibre.utils.config import JSONConfig
@@ -20,7 +23,7 @@ class ConfigWidget(QWidget):
self.l = QVBoxLayout()
self.setLayout(self.l)
self.serialLabel = QLabel('Kindle Serial numbers (separate with commas, no spaces)')
self.serialLabel = QLabel('eInk Kindle Serial numbers (First character B, 16 characters, use commas if more than one)')
self.l.addWidget(self.serialLabel)
self.serials = QLineEdit(self)
@@ -28,7 +31,7 @@ class ConfigWidget(QWidget):
self.l.addWidget(self.serials)
self.serialLabel.setBuddy(self.serials)
self.pidLabel = QLabel('Mobipocket PIDs (separate with commas, no spaces)')
self.pidLabel = QLabel('Mobipocket PIDs (8 or 10 characters, use commas if more than one)')
self.l.addWidget(self.pidLabel)
self.pids = QLineEdit(self)
@@ -50,8 +53,8 @@ class ConfigWidget(QWidget):
self.wpLabel.setBuddy(self.wineprefix)
def save_settings(self):
prefs['pids'] = str(self.pids.text())
prefs['serials'] = str(self.serials.text())
prefs['pids'] = str(self.pids.text()).replace(" ","")
prefs['serials'] = str(self.serials.text()).replace(" ","")
winepref=str(self.wineprefix.text())
if winepref.strip() != '':
prefs['WINEPREFIX'] = winepref

View File

@@ -230,6 +230,7 @@ class PageParser(object):
'empty' : (1, 'snippets', 1, 0),
'page' : (1, 'snippets', 1, 0),
'page.class' : (1, 'scalar_text', 0, 0),
'page.pageid' : (1, 'scalar_text', 0, 0),
'page.pagelabel' : (1, 'scalar_text', 0, 0),
'page.type' : (1, 'scalar_text', 0, 0),
@@ -238,11 +239,13 @@ class PageParser(object):
'page.startID' : (1, 'scalar_number', 0, 0),
'group' : (1, 'snippets', 1, 0),
'group.class' : (1, 'scalar_text', 0, 0),
'group.type' : (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.class' : (1, 'scalar_text', 0, 0),
'region.type' : (1, 'scalar_text', 0, 0),
'region.x' : (1, 'scalar_number', 0, 0),
'region.y' : (1, 'scalar_number', 0, 0),

View File

@@ -35,7 +35,7 @@ def main(argv=sys.argv):
except ValueError:
print ' Error parsing user supplied social drm data.'
return 1
rv = erdr2pml.decryptBook(infile, outdir, name, cc8, True)
rv = erdr2pml.decryptBook(infile, outdir, True, erdr2pml.getuser_key(name, cc8) )
if rv == 0:
break
return rv

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